Skip to content

Commit f235ba6

Browse files
committed
Merge branch 'opencode-mirror' into fork/local
2 parents de1e42d + a85d8d2 commit f235ba6

254 files changed

Lines changed: 14978 additions & 4372 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 50 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nix/hashes.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"nodeModules": {
3-
"x86_64-linux": "sha256-zAftZieWR0hWQQVkhEa826iueHaRnKxtyRodhNb+Apc=",
4-
"aarch64-linux": "sha256-cTG+gB7CuiFgaYHSFbzXaXUI1C2kiugV3B9rUDRUwlg=",
5-
"aarch64-darwin": "sha256-ap4Xv5ZliourkI4mcD0ktbzSI88jb4oVBv5mMK3WdUs=",
6-
"x86_64-darwin": "sha256-NGRJMMnaMNrf3FkcYwr5PcfVm5thhlNLGIeMmAK0MeE="
3+
"x86_64-linux": "sha256-0kcwV34P2C3yKg2eG9W2nW+OedrSBb+1TdpuUeYtauY=",
4+
"aarch64-linux": "sha256-yHVygApQchAB34wrtFR4GU0CkmZOlLsl3wsp15u0xzs=",
5+
"aarch64-darwin": "sha256-DyalcwyK2Wn5R6249keFcNVECbgtjYNjscOFqTi88FI=",
6+
"x86_64-darwin": "sha256-BkGw0GWN9W9q+/g4FYR0MqxUuFP80BPoERO+ypz/arQ="
77
}
88
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
150150
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
151151
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
152+
"@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch",
152153
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
153154
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
154155
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",

packages/app/V1_API_MIGRATION.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# V1 API Migration Checklist
2+
3+
The app is currently hybrid. In this document, V1 refers to the legacy unprefixed server APIs used by `@opencode-ai/sdk/v2`, despite the SDK package name.
4+
5+
## Events
6+
7+
- [x] Replace `GET /global/event` with `GET /api/event`.
8+
- `src/context/server-sdk.tsx`
9+
- [x] Reduce current granular session and message events into the existing app projections.
10+
- `src/context/server-session-v2-reducer.ts`
11+
- `src/context/server-session.ts`
12+
- [ ] Remove transitional session event dependencies: `session.created`, `session.updated`, `session.diff`, `session.status`, `session.idle`, and `session.error`.
13+
- `src/context/global-sync/event-reducer.ts`
14+
- `src/context/server-session.ts`
15+
- `src/context/notification.tsx`
16+
- `src/pages/session/usage-exceeded-dialogs.tsx`
17+
- [ ] Remove legacy message event compatibility: `message.updated`, `message.removed`, `message.part.updated`, `message.part.removed`, and `message.part.delta`.
18+
- `src/context/global-sync/event-reducer.ts`
19+
- `src/context/server-session.ts`
20+
- [x] Adapt current permission and question events to the existing request model.
21+
- `src/context/global-sync/event-reducer.ts`
22+
- `src/context/permission.tsx`
23+
- [x] Consume current file watcher events.
24+
- `src/context/file.tsx`
25+
- [x] Consume current VCS events.
26+
- `src/context/global-sync/event-reducer.ts`
27+
- `src/pages/session.tsx`
28+
- [x] Consume current `pty.exited` events.
29+
- `src/context/terminal.tsx`
30+
- [ ] Migrate LSP and reference events.
31+
- `src/context/global-sync/event-reducer.ts`
32+
33+
## Sessions
34+
35+
- [x] Replace `GET /session/status` with one server-scoped `GET /api/session/active` snapshot plus V2 execution events.
36+
- `src/context/server-sync.tsx`
37+
- [x] Migrate session listing from `GET /session`.
38+
- `src/context/server-sync.tsx`
39+
- `src/context/directory-sync.ts`
40+
- `src/pages/layout.tsx`
41+
- [x] Migrate the remaining direct session read from `GET /session/:sessionID`.
42+
- `src/components/titlebar.tsx`
43+
- [x] Migrate session updates from `PATCH /session/:sessionID`.
44+
- `src/context/directory-sync.ts`
45+
- `src/context/layout.tsx`
46+
- `src/pages/home.tsx`
47+
- `src/pages/layout.tsx`
48+
- `src/pages/session/timeline/message-timeline.tsx`
49+
- `src/components/titlebar-tab-nav.tsx`
50+
- Renames use `POST /api/session/:sessionID/rename`; archival uses `POST /api/session/:sessionID/archive`.
51+
- [x] Migrate session deletion from `DELETE /session/:sessionID`.
52+
- `src/pages/session/timeline/message-timeline.tsx`
53+
- [x] Remove session diff loading from `GET /session/:sessionID/diff`.
54+
- Historical Session diffs remain unavailable until the current API defines their snapshot semantics.
55+
- [x] Migrate abort from `POST /session/:sessionID/abort`.
56+
- `src/components/prompt-input/submit.ts`
57+
- `src/pages/session/use-session-commands.tsx`
58+
- `src/pages/session.tsx`
59+
- [x] Migrate revert and unrevert from `POST /session/:sessionID/revert` and `POST /session/:sessionID/unrevert`.
60+
- `src/pages/session/use-session-commands.tsx`
61+
- `src/pages/session.tsx`
62+
- [x] Replace `POST /session/:sessionID/summarize` with the current compact API.
63+
- `src/pages/session/use-session-commands.tsx`
64+
- [x] Migrate slash commands from `POST /session/:sessionID/command`.
65+
- `src/components/prompt-input/submit.ts`
66+
- [x] Migrate shell execution from `POST /session/:sessionID/shell`.
67+
- `src/components/prompt-input/submit.ts`
68+
- [x] Migrate session fork from `POST /session/:sessionID/fork`.
69+
- `src/components/dialog-fork.tsx`
70+
- [ ] Migrate sharing from `POST /session/:sessionID/share` and `DELETE /session/:sessionID/share`.
71+
- `src/pages/session/use-session-commands.tsx`
72+
- `src/pages/session/timeline/message-timeline.tsx`
73+
- Blocked: the current API has no sharing contract or implementation.
74+
75+
## Session Compatibility Fallbacks
76+
77+
These calls are retained as fallback adapters. The current production path supplies the current session and message APIs.
78+
79+
- [ ] Remove fallback `GET /session/:sessionID` after compatibility support is unnecessary.
80+
- `src/context/server-session.ts`
81+
- [ ] Remove fallback `GET /session/:sessionID/message` after compatibility support is unnecessary.
82+
- `src/context/server-session.ts`
83+
- [ ] Remove fallback `GET /session/:sessionID/message/:messageID` after compatibility support is unnecessary.
84+
- `src/context/server-session.ts`
85+
86+
## Filesystem
87+
88+
- [ ] Migrate file listing from `GET /file`.
89+
- `src/context/file.tsx`
90+
- [ ] Migrate file reads from `GET /file/content`.
91+
- `src/context/file.tsx`
92+
- `src/pages/session/review-tab.tsx`
93+
- `src/pages/session/v2/review-panel-v2.tsx`
94+
- [x] Migrate path discovery from `GET /path` to `GET /api/path`.
95+
- `src/context/global-sync/bootstrap.ts`
96+
- `src/components/dialog-select-directory.tsx`
97+
- `src/components/dialog-select-directory-v2.tsx`
98+
99+
## Projects And Worktrees
100+
101+
- [x] Migrate project listing from `GET /project` to `GET /api/project`.
102+
- `src/context/global-sync/bootstrap.ts`
103+
- [x] Migrate the current project lookup from `GET /project/current` to `GET /api/project/current`.
104+
- `src/context/global-sync/bootstrap.ts`
105+
- [ ] Migrate Git initialization from `POST /project/git/init`.
106+
- `src/pages/session.tsx`
107+
- [x] Migrate project updates from `PATCH /project/:projectID` to `PATCH /api/project/:projectID`.
108+
- `src/context/layout.tsx`
109+
- `src/components/edit-project.ts`
110+
- `src/pages/layout.tsx`
111+
- [ ] Migrate experimental worktree listing, creation, removal, and reset from `/experimental/worktree`.
112+
- `src/pages/layout.tsx`
113+
- `src/components/prompt-input/submit.ts`
114+
- Listing now uses `GET /api/project/:projectID/directories`; create, removal, and reset remain.
115+
- [ ] Migrate instance disposal from `POST /instance/dispose`.
116+
- `src/pages/layout.tsx`
117+
118+
## VCS
119+
120+
- [x] Migrate repository information from `GET /vcs` to `GET /api/vcs`.
121+
- `src/context/global-sync/bootstrap.ts`
122+
- [x] Migrate diffs from `GET /vcs/diff` to `GET /api/vcs/diff`.
123+
- `src/pages/session.tsx`
124+
- [x] Migrate status from `GET /vcs/status` to `GET /api/vcs/status`.
125+
- `src/pages/layout.tsx`
126+
127+
## Configuration And Authentication
128+
129+
- [ ] Migrate global configuration reads from `GET /global/config`.
130+
- `src/context/global-sync/bootstrap.ts`
131+
- [ ] Migrate directory configuration reads from `GET /config`.
132+
- `src/context/global-sync/bootstrap.ts`
133+
- [ ] Migrate global configuration updates from `PATCH /global/config`.
134+
- `src/context/server-sync.tsx`
135+
- [x] Migrate provider authentication method discovery from `GET /provider/auth` to `GET /api/integration/:integrationID`.
136+
- `src/components/dialog-connect-provider.tsx`
137+
- [x] Migrate built-in provider OAuth authorization and callbacks to `/api/integration/:integrationID/connect/oauth/*`.
138+
- `src/components/dialog-connect-provider.tsx`
139+
- [ ] Migrate remaining credentials from `PUT /auth/:providerID` and `DELETE /auth/:providerID`.
140+
- Built-in provider key connections now use `POST /api/integration/:integrationID/connect/key`.
141+
- `src/components/dialog-connect-provider.tsx`
142+
- `src/components/dialog-custom-provider.tsx`
143+
- `src/components/settings-providers.tsx`
144+
- `src/components/settings-v2/providers.tsx`
145+
- [ ] Migrate global disposal from `POST /global/dispose`.
146+
- `src/components/dialog-connect-provider.tsx`
147+
- `src/components/settings-providers.tsx`
148+
- `src/components/settings-v2/providers.tsx`
149+
150+
## Permissions And Questions
151+
152+
- [x] Migrate permission listing from `GET /permission` to `GET /api/permission/request`.
153+
- `src/context/global-sync/bootstrap.ts`
154+
- `src/context/permission.tsx`
155+
- [x] Migrate permission responses from `/session/:sessionID/permissions/:permissionID`.
156+
- `src/context/permission.tsx`
157+
- `src/pages/session/composer/session-composer-state.ts`
158+
- [x] Migrate question listing from `GET /question` to `GET /api/question/request`.
159+
- `src/context/global-sync/bootstrap.ts`
160+
- [x] Migrate question replies and rejections from `/question/:requestID/*` to `/api/session/:sessionID/question/:requestID/*`.
161+
- `src/pages/session/composer/session-question-dock.tsx`
162+
163+
## Commands, MCP, LSP, And References
164+
165+
- [x] Migrate command listing from `GET /command` to `GET /api/command`.
166+
- `src/context/global-sync/bootstrap.ts`
167+
- `src/context/server-sync.tsx`
168+
- [x] Migrate MCP listing, connection, and disconnection from `/mcp` to `/api/mcp`.
169+
- `src/context/server-sync.tsx`
170+
- [ ] Replace legacy MCP authentication with the Integration OAuth workflow.
171+
- `src/context/server-sync.tsx`
172+
- [x] Migrate experimental resource listing from `GET /experimental/resource` to `GET /api/mcp/resource`.
173+
- `src/context/server-sync.tsx`
174+
- [ ] Migrate LSP status from `GET /lsp`.
175+
- `src/context/server-sync.tsx`
176+
- [x] Move `GET /api/reference` off the legacy generated SDK transport.
177+
- `src/context/global-sync/bootstrap.ts`
178+
179+
## Search
180+
181+
- [x] Migrate global session search from `GET /experimental/session` to `GET /api/session`.
182+
- `src/components/command-palette.ts`
183+
- `src/components/dialog-command-palette-v2.tsx`
184+
185+
## PTY And Terminal
186+
187+
- [x] Migrate PTY creation, reads, updates, and deletion from `/pty` to `/api/pty`.
188+
- `src/context/terminal.tsx`
189+
- `src/components/terminal.tsx`
190+
- [x] Migrate shell listing from `GET /pty/shells` to `GET /api/pty/shells`.
191+
- `src/components/settings-general.tsx`
192+
- `src/components/settings-v2/general.tsx`
193+
- [x] Migrate connection tokens from `POST /pty/:ptyID/connect-token` to `POST /api/pty/:ptyID/connect-token`.
194+
- `src/components/terminal.tsx`
195+
- [x] Migrate the direct WebSocket connection from `/pty/:ptyID/connect` to `/api/pty/:ptyID/connect`.
196+
- `src/components/terminal.tsx`
197+
198+
## Legacy Types And Adapters
199+
200+
These are not V1 network requests, but they keep the UI coupled to V1 data contracts.
201+
202+
- [ ] Replace the current-session-to-legacy-session adapter.
203+
- `src/utils/session.ts`
204+
- [ ] Replace the current-message-to-legacy-message-and-part adapter.
205+
- `src/utils/session-message.ts`
206+
- [ ] Replace current agent, provider, and model adapters to legacy SDK structures.
207+
- `src/context/global-sync/utils.ts`
208+
- [ ] Replace legacy `Session`, `Message`, `Part`, `PermissionRequest`, `QuestionRequest`, `Project`, `FileNode`, `FileDiffInfo`, and `Event` types throughout app state and rendering.
209+
- [ ] Remove the `@opencode-ai/sdk` runtime dependency after all legacy calls and types are gone.
210+
- `package.json`
211+
212+
## Test Infrastructure
213+
214+
- [ ] Replace V1 endpoint mocks with current API mocks.
215+
- `e2e/utils/mock-server.ts`
216+
- [x] Replace `/global/event` and `/event` interception with current event transport handling.
217+
- `e2e/utils/sse-transport.ts`
218+
- [ ] Replace `SessionV1` and legacy SDK fixtures in timeline performance tests.
219+
- `e2e/performance/timeline-stability/fixture.ts`
220+
- [ ] Remove remaining legacy SDK type fixtures from unit and browser tests.

packages/app/e2e/performance/timeline-stability/fixture.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export async function setupTimeline(
9797
locale?: string
9898
deviceScaleFactor?: number
9999
seedHistory?: boolean
100+
protocol?: "v1" | "v2"
100101
} = {},
101102
) {
102103
const sessions = input.sessions ?? [session()]
@@ -114,6 +115,7 @@ export async function setupTimeline(
114115
retry: input.eventRetry ?? 20,
115116
})
116117
await mockOpenCodeServer(page, {
118+
protocol: input.protocol,
117119
directory,
118120
project: project(),
119121
provider: provider(),

packages/app/e2e/performance/timeline/session-parent-hydration-benchmark.spec.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ const assistants = Array.from({ length: 14 }, (_, index) => {
4141
const messages = [user, ...assistants]
4242
const target = fixture.sessions.find((session) => session.id === fixture.targetID)!
4343
const lastID = userID
44-
const lastPartID = assistants.at(-1)!.parts.at(-1)!.id
44+
const lastAssistant = assistants.at(-1)!
45+
const lastPart = lastAssistant.parts.at(-1)!
46+
const lastPartID =
47+
lastPart.type === "tool"
48+
? lastPart.id
49+
: `${lastAssistant.info.id}:${lastPart.type}:${lastAssistant.parts.filter((part) => part.type === lastPart.type).length - 1}`
4550

4651
benchmark("hydrates an orphaned latest turn after a cold session click", async ({ browser, report }, testInfo) => {
4752
benchmark.setTimeout(180_000)
@@ -107,9 +112,25 @@ async function trial(page: Page, mode: ParentHydrationBenchmarkMode) {
107112
return { items: items.slice(start, end), cursor: start > 0 ? items[start]!.info.id : undefined }
108113
},
109114
})
110-
await page.route(`**/session/${fixture.targetID}`, (route) =>
111-
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(target) }),
112-
)
115+
await page.route(`**/session/${fixture.targetID}`, (route) => {
116+
const current = new URL(route.request().url()).pathname.startsWith("/api/")
117+
return route.fulfill({
118+
status: 200,
119+
contentType: "application/json",
120+
body: JSON.stringify(
121+
current
122+
? {
123+
data: {
124+
...target,
125+
cost: 0,
126+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
127+
location: { directory: target.directory },
128+
},
129+
}
130+
: target,
131+
),
132+
})
133+
})
113134
await installStressSessionTabs(page, { sessionIDs: [fixture.sourceID] })
114135
await page.goto(stressSessionHref(fixture.sourceID))
115136
await expectSessionTitle(page, fixture.expected.sourceTitle)
@@ -144,8 +165,8 @@ async function trial(page: Page, mode: ParentHydrationBenchmarkMode) {
144165
parent: requests.filter((request) => request.type === "parent").length,
145166
}
146167
if (mode === "candidate") {
147-
expect(requestCounts.parent).toBe(1)
148-
expect(historyGates).toBe(1)
168+
expect(requestCounts.parent).toBe(0)
169+
expect(historyGates).toBe(0)
149170
}
150171
return { metrics, requestCounts, historyGateCount: historyGates }
151172
}

packages/app/e2e/regression/cross-server-tab-close.spec.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, test, type Page, type Route } from "@playwright/test"
22
import { base64Encode } from "@opencode-ai/core/util/encode"
3+
import { currentSession } from "../utils/mock-server"
34

45
const serverA = "http://127.0.0.1:4096"
56
const serverB = "http://127.0.0.1:4097"
@@ -33,7 +34,7 @@ test("closing the active server's last tab opens the remaining server tab", asyn
3334
await tabA.locator('[data-slot="tab-close"] button').click()
3435

3536
await expect(page).toHaveURL(new RegExp(`${hrefB.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`))
36-
await expect.poll(() => requests.some((url) => url.startsWith(`${serverB}/session/${sessionB.id}`))).toBe(true)
37+
await expect.poll(() => requests.some((url) => url.startsWith(`${serverB}/api/session/${sessionB.id}`))).toBe(true)
3738
await expect(page.getByText(sessionB.title).first()).toBeVisible()
3839
const sessionBRequests = requests.filter((url) => url.includes(`/session/${sessionB.id}`))
3940
expect(sessionBRequests.every((url) => url.startsWith(serverB))).toBe(true)
@@ -84,17 +85,21 @@ async function mockServers(page: Page, requests: string[]) {
8485
const current = url.origin === serverA ? sessionA : sessionB
8586
const directory = url.searchParams.get("directory")
8687
if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500)
87-
if (url.pathname === "/global/event" || url.pathname === "/event") return sse(route)
88-
if (url.pathname === "/global/health") return json(route, { healthy: true })
89-
if (url.pathname === "/session") return json(route, [current])
88+
if (url.pathname === "/global/event" || url.pathname === "/event" || url.pathname === "/api/event")
89+
return sse(route)
90+
if (url.pathname === "/global/health") return json(route, {}, 404)
91+
if (url.pathname === "/api/health") return json(route, { pid: 1 })
92+
if (url.pathname === "/api/session") return json(route, { data: [currentSession(current)], cursor: {} })
93+
if (url.pathname === "/api/session/active") return json(route, { data: {} })
94+
if (url.pathname === `/api/session/${current.id}`) return json(route, { data: currentSession(current) })
95+
if (url.pathname === `/api/session/${current.id}/message`) return json(route, { data: [], cursor: {} })
9096
if (url.pathname === `/session/${current.id}`) return json(route, current)
9197
if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404)
9298
if (url.pathname === `/session/${current.id}/message`) return json(route, [])
9399
if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, [])
94100
if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname))
95101
return json(route, [])
96-
if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname))
97-
return json(route, {})
102+
if (["/global/config", "/config", "/provider/auth", "/mcp"].includes(url.pathname)) return json(route, {})
98103
if (url.pathname === "/provider")
99104
return json(route, { all: [], connected: [], default: { providerID: "", modelID: "" } })
100105
if (url.pathname === "/agent") return json(route, [{ name: "build", mode: "primary" }])
@@ -116,7 +121,20 @@ async function mockServers(page: Page, requests: string[]) {
116121
directory: current.directory,
117122
home: current.directory,
118123
})
124+
if (url.pathname === "/api/path")
125+
return json(route, {
126+
state: current.directory,
127+
config: current.directory,
128+
worktree: current.directory,
129+
directory: current.directory,
130+
home: current.directory,
131+
})
119132
if (url.pathname === "/vcs") return json(route, { branch: "main", default_branch: "main" })
133+
if (url.pathname === "/api/vcs")
134+
return json(route, {
135+
location: { directory: current.directory },
136+
data: { branch: "main", defaultBranch: "main" },
137+
})
120138
return json(route, {})
121139
})
122140
}

0 commit comments

Comments
 (0)