diff --git a/bun.lock b/bun.lock index 215c55c66c7..75bdb27c640 100644 --- a/bun.lock +++ b/bun.lock @@ -64,7 +64,6 @@ "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", - "@opencode-ai/sdk": "file:vendor/opencode-ai-sdk-1.18.8-dev.tgz", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -88,6 +87,7 @@ "@tanstack/solid-query": "5.91.4", "@tanstack/solid-virtual": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", + "diff": "catalog:", "effect": "catalog:", "fuzzysort": "catalog:", "ghostty-web": "github:anomalyco/ghostty-web#83c0a07b8628b748aed073b232cb4b52a6ca11c1", @@ -911,7 +911,6 @@ "@opentui/solid": "catalog:", "@solid-primitives/event-bus": "1.1.2", "clipboardy": "4.0.0", - "diff": "catalog:", "effect": "catalog:", "fuzzysort": "catalog:", "get-east-asian-width": "catalog:", @@ -6340,8 +6339,6 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/app/@opencode-ai/sdk": ["@opencode-ai/sdk@vendor/opencode-ai-sdk-1.18.8-dev.tgz", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-C2nfk4x0sPINwE5V6DPkFSuH3PkUmKPWHPzxpXC1j+3Ui5hslLCWJbkk8WcOG1Lyt3C0+yp4ea64v/kmtYCO4w=="], - "@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "@opencode-ai/console-app/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="], diff --git a/packages/app/V1_API_MIGRATION.md b/packages/app/V1_API_MIGRATION.md deleted file mode 100644 index 2850f107400..00000000000 --- a/packages/app/V1_API_MIGRATION.md +++ /dev/null @@ -1,220 +0,0 @@ -# V1 API Migration Checklist - -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. - -## Events - -- [x] Replace `GET /global/event` with `GET /api/event`. - - `src/context/server-sdk.tsx` -- [x] Reduce current granular session and message events into the existing app projections. - - `src/context/server-session-v2-reducer.ts` - - `src/context/server-session.ts` -- [ ] Remove transitional session event dependencies: `session.created`, `session.updated`, `session.diff`, `session.status`, `session.idle`, and `session.error`. - - `src/context/global-sync/event-reducer.ts` - - `src/context/server-session.ts` - - `src/context/notification.tsx` - - `src/pages/session/usage-exceeded-dialogs.tsx` -- [ ] Remove legacy message event compatibility: `message.updated`, `message.removed`, `message.part.updated`, `message.part.removed`, and `message.part.delta`. - - `src/context/global-sync/event-reducer.ts` - - `src/context/server-session.ts` -- [x] Adapt current permission and question events to the existing request model. - - `src/context/global-sync/event-reducer.ts` - - `src/context/permission.tsx` -- [x] Consume current file watcher events. - - `src/context/file.tsx` -- [x] Consume current VCS events. - - `src/context/global-sync/event-reducer.ts` - - `src/pages/session.tsx` -- [x] Consume current `pty.exited` events. - - `src/context/terminal.tsx` -- [ ] Migrate LSP and reference events. - - `src/context/global-sync/event-reducer.ts` - -## Sessions - -- [x] Replace `GET /session/status` with one server-scoped `GET /api/session/active` snapshot plus V2 execution events. - - `src/context/server-sync.tsx` -- [x] Migrate session listing from `GET /session`. - - `src/context/server-sync.tsx` - - `src/context/directory-sync.ts` - - `src/pages/layout.tsx` -- [x] Migrate the remaining direct session read from `GET /session/:sessionID`. - - `src/components/titlebar.tsx` -- [x] Migrate session updates from `PATCH /session/:sessionID`. - - `src/context/directory-sync.ts` - - `src/context/layout.tsx` - - `src/pages/home.tsx` - - `src/pages/layout.tsx` - - `src/pages/session/timeline/message-timeline.tsx` - - `src/components/titlebar-tab-nav.tsx` - - Renames use `POST /api/session/:sessionID/rename`; archival uses `POST /api/session/:sessionID/archive`. -- [x] Migrate session deletion from `DELETE /session/:sessionID`. - - `src/pages/session/timeline/message-timeline.tsx` -- [x] Remove session diff loading from `GET /session/:sessionID/diff`. - - Historical Session diffs remain unavailable until the current API defines their snapshot semantics. -- [x] Migrate abort from `POST /session/:sessionID/abort`. - - `src/components/prompt-input/submit.ts` - - `src/pages/session/use-session-commands.tsx` - - `src/pages/session.tsx` -- [x] Migrate revert and unrevert from `POST /session/:sessionID/revert` and `POST /session/:sessionID/unrevert`. - - `src/pages/session/use-session-commands.tsx` - - `src/pages/session.tsx` -- [x] Replace `POST /session/:sessionID/summarize` with the current compact API. - - `src/pages/session/use-session-commands.tsx` -- [x] Migrate slash commands from `POST /session/:sessionID/command`. - - `src/components/prompt-input/submit.ts` -- [x] Migrate shell execution from `POST /session/:sessionID/shell`. - - `src/components/prompt-input/submit.ts` -- [x] Migrate session fork from `POST /session/:sessionID/fork`. - - `src/components/dialog-fork.tsx` -- [ ] Migrate sharing from `POST /session/:sessionID/share` and `DELETE /session/:sessionID/share`. - - `src/pages/session/use-session-commands.tsx` - - `src/pages/session/timeline/message-timeline.tsx` - - Blocked: the current API has no sharing contract or implementation. - -## Session Compatibility Fallbacks - -These calls are retained as fallback adapters. The current production path supplies the current session and message APIs. - -- [ ] Remove fallback `GET /session/:sessionID` after compatibility support is unnecessary. - - `src/context/server-session.ts` -- [ ] Remove fallback `GET /session/:sessionID/message` after compatibility support is unnecessary. - - `src/context/server-session.ts` -- [ ] Remove fallback `GET /session/:sessionID/message/:messageID` after compatibility support is unnecessary. - - `src/context/server-session.ts` - -## Filesystem - -- [ ] Migrate file listing from `GET /file`. - - `src/context/file.tsx` -- [ ] Migrate file reads from `GET /file/content`. - - `src/context/file.tsx` - - `src/pages/session/review-tab.tsx` - - `src/pages/session/v2/review-panel-v2.tsx` -- [x] Migrate path discovery from `GET /path` to `GET /api/path`. - - `src/context/global-sync/bootstrap.ts` - - `src/components/dialog-select-directory.tsx` - - `src/components/dialog-select-directory-v2.tsx` - -## Projects And Worktrees - -- [x] Migrate project listing from `GET /project` to `GET /api/project`. - - `src/context/global-sync/bootstrap.ts` -- [x] Migrate the current project lookup from `GET /project/current` to `GET /api/project/current`. - - `src/context/global-sync/bootstrap.ts` -- [ ] Migrate Git initialization from `POST /project/git/init`. - - `src/pages/session.tsx` -- [x] Migrate project updates from `PATCH /project/:projectID` to `PATCH /api/project/:projectID`. - - `src/context/layout.tsx` - - `src/components/edit-project.ts` - - `src/pages/layout.tsx` -- [ ] Migrate experimental worktree listing, creation, removal, and reset from `/experimental/worktree`. - - `src/pages/layout.tsx` - - `src/components/prompt-input/submit.ts` - - Listing now uses `GET /api/project/:projectID/directories`; create, removal, and reset remain. -- [ ] Migrate instance disposal from `POST /instance/dispose`. - - `src/pages/layout.tsx` - -## VCS - -- [x] Migrate repository information from `GET /vcs` to `GET /api/vcs`. - - `src/context/global-sync/bootstrap.ts` -- [x] Migrate diffs from `GET /vcs/diff` to `GET /api/vcs/diff`. - - `src/pages/session.tsx` -- [x] Migrate status from `GET /vcs/status` to `GET /api/vcs/status`. - - `src/pages/layout.tsx` - -## Configuration And Authentication - -- [ ] Migrate global configuration reads from `GET /global/config`. - - `src/context/global-sync/bootstrap.ts` -- [ ] Migrate directory configuration reads from `GET /config`. - - `src/context/global-sync/bootstrap.ts` -- [ ] Migrate global configuration updates from `PATCH /global/config`. - - `src/context/server-sync.tsx` -- [x] Migrate provider authentication method discovery from `GET /provider/auth` to `GET /api/integration/:integrationID`. - - `src/components/dialog-connect-provider.tsx` -- [x] Migrate built-in provider OAuth authorization and callbacks to `/api/integration/:integrationID/connect/oauth/*`. - - `src/components/dialog-connect-provider.tsx` -- [ ] Migrate remaining credentials from `PUT /auth/:providerID` and `DELETE /auth/:providerID`. - - Built-in provider key connections now use `POST /api/integration/:integrationID/connect/key`. - - `src/components/dialog-connect-provider.tsx` - - `src/components/dialog-custom-provider.tsx` - - `src/components/settings-providers.tsx` - - `src/components/settings-v2/providers.tsx` -- [ ] Migrate global disposal from `POST /global/dispose`. - - `src/components/dialog-connect-provider.tsx` - - `src/components/settings-providers.tsx` - - `src/components/settings-v2/providers.tsx` - -## Permissions And Questions - -- [x] Migrate permission listing from `GET /permission` to `GET /api/permission/request`. - - `src/context/global-sync/bootstrap.ts` - - `src/context/permission.tsx` -- [x] Migrate permission responses from `/session/:sessionID/permissions/:permissionID`. - - `src/context/permission.tsx` - - `src/pages/session/composer/session-composer-state.ts` -- [x] Migrate question listing from `GET /question` to `GET /api/question/request`. - - `src/context/global-sync/bootstrap.ts` -- [x] Migrate question replies and rejections from `/question/:requestID/*` to `/api/session/:sessionID/question/:requestID/*`. - - `src/pages/session/composer/session-question-dock.tsx` - -## Commands, MCP, LSP, And References - -- [x] Migrate command listing from `GET /command` to `GET /api/command`. - - `src/context/global-sync/bootstrap.ts` - - `src/context/server-sync.tsx` -- [x] Migrate MCP listing, connection, and disconnection from `/mcp` to `/api/mcp`. - - `src/context/server-sync.tsx` -- [ ] Replace legacy MCP authentication with the Integration OAuth workflow. - - `src/context/server-sync.tsx` -- [x] Migrate experimental resource listing from `GET /experimental/resource` to `GET /api/mcp/resource`. - - `src/context/server-sync.tsx` -- [ ] Migrate LSP status from `GET /lsp`. - - `src/context/server-sync.tsx` -- [x] Move `GET /api/reference` off the legacy generated SDK transport. - - `src/context/global-sync/bootstrap.ts` - -## Search - -- [x] Migrate global session search from `GET /experimental/session` to `GET /api/session`. - - `src/components/command-palette.ts` - - `src/components/dialog-command-palette-v2.tsx` - -## PTY And Terminal - -- [x] Migrate PTY creation, reads, updates, and deletion from `/pty` to `/api/pty`. - - `src/context/terminal.tsx` - - `src/components/terminal.tsx` -- [x] Migrate shell listing from `GET /pty/shells` to `GET /api/pty/shells`. - - `src/components/settings-general.tsx` - - `src/components/settings-v2/general.tsx` -- [x] Migrate connection tokens from `POST /pty/:ptyID/connect-token` to `POST /api/pty/:ptyID/connect-token`. - - `src/components/terminal.tsx` -- [x] Migrate the direct WebSocket connection from `/pty/:ptyID/connect` to `/api/pty/:ptyID/connect`. - - `src/components/terminal.tsx` - -## Legacy Types And Adapters - -These are not V1 network requests, but they keep the UI coupled to V1 data contracts. - -- [ ] Replace the current-session-to-legacy-session adapter. - - `src/utils/session.ts` -- [ ] Replace the current-message-to-legacy-message-and-part adapter. - - `src/utils/session-message.ts` -- [ ] Replace current agent, provider, and model adapters to legacy SDK structures. - - `src/context/global-sync/utils.ts` -- [ ] Replace legacy `Session`, `Message`, `Part`, `PermissionRequest`, `QuestionRequest`, `Project`, `FileNode`, `FileDiffInfo`, and `Event` types throughout app state and rendering. -- [ ] Remove the `@opencode-ai/sdk` runtime dependency after all legacy calls and types are gone. - - `package.json` - -## Test Infrastructure - -- [ ] Replace V1 endpoint mocks with current API mocks. - - `e2e/utils/mock-server.ts` -- [x] Replace `/global/event` and `/event` interception with current event transport handling. - - `e2e/utils/sse-transport.ts` -- [ ] Replace `SessionV1` and legacy SDK fixtures in timeline performance tests. - - `e2e/performance/timeline-stability/fixture.ts` -- [ ] Remove remaining legacy SDK type fixtures from unit and browser tests. diff --git a/packages/app/e2e/performance/timeline-stability/fixture.test.ts b/packages/app/e2e/performance/timeline-stability/fixture.test.ts index 1f9c43d2c7b..b003645e640 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.test.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.test.ts @@ -45,10 +45,6 @@ describe("timeline fixture validation", () => { expect(first.payload.id).toMatch(/^evt_timeline_\d{4}$/) expect(Number(second.payload.id.slice(-4))).toBe(Number(first.payload.id.slice(-4)) + 1) }) - - test("uses the projected tool ID as its call ID", () => { - expect(toolPart("call_1", "read", "running", {})).toMatchObject({ id: "call_1", callID: "call_1" }) - }) }) if (false) { diff --git a/packages/app/e2e/performance/timeline-stability/fixture.ts b/packages/app/e2e/performance/timeline-stability/fixture.ts index e9005993404..3020644d955 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.ts @@ -2,15 +2,8 @@ import { base64Encode } from "@opencode-ai/core/util/encode" import { Event } from "@opencode-ai/schema/event" import { SessionStatusEvent } from "@opencode-ai/schema/session-status-event" import { SessionV1 } from "@opencode-ai/schema/session-v1" -import type { - AssistantMessage, - Message, - Part, - ToolPart, - ToolState, - UserMessage, -} from "../../../src/types" import type { SessionV1Info, SessionStatus } from "@opencode-ai/client/promise" +import type { AssistantMessage, Message, Part, ToolPart, ToolState, UserMessage } from "../../../src/types" import { expect, type Page } from "@playwright/test" import { Schema } from "effect" import { mockOpenCodeServer } from "../../utils/mock-server" diff --git a/packages/app/package.json b/packages/app/package.json index 06a91d5efe8..21096c7e7fd 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -56,7 +56,6 @@ "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", - "@opencode-ai/sdk": "file:vendor/opencode-ai-sdk-1.18.8-dev.tgz", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index f47c432e420..c2da99d976a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -238,26 +238,6 @@ function UiI18nBridge(props: ParentProps) { } function LayoutCompatibility(props: ParentProps) { - const global = useGlobal() - const navigate = useNavigate() - const server = useServer() - const settings = useSettings() - - createEffect(() => { - if (settings.general.newLayoutDesigns()) return - const current = server.current - if (!current) return - const protocol = global.ensureServerCtx(current).sdk.protocolKind() - if (protocol !== "v2") return - const next = global.servers.list().find((s) => { - if (ServerConnection.key(s) === ServerConnection.key(current)) return false - return global.ensureServerCtx(s).sdk.protocolKind() !== "v2" - }) - if (!next) return - navigate("/") - queueMicrotask(() => server.setActive(ServerConnection.key(next))) - }) - return <>{props.children} } diff --git a/packages/app/src/components/command-palette.ts b/packages/app/src/components/command-palette.ts index f7757374124..5779c3a423f 100644 --- a/packages/app/src/components/command-palette.ts +++ b/packages/app/src/components/command-palette.ts @@ -146,7 +146,7 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on server: ServerConnection.key(serverSDK.server), opened: serverCtx.projects.list, stored: () => serverCtx.sync.data.project, - load: (search, signal) => serverSDK.currentApi.session.list({ parentID: null, search, limit: 50 }, { signal }), + load: (search, signal) => serverSDK.api.session.list({ parentID: null, search, limit: 50 }, { signal }), untitled: () => language.t("command.session.new"), category: () => language.t("command.category.session"), }) diff --git a/packages/app/src/components/dialog-command-palette-v2.tsx b/packages/app/src/components/dialog-command-palette-v2.tsx index dbf966cf52a..e996fd0be77 100644 --- a/packages/app/src/components/dialog-command-palette-v2.tsx +++ b/packages/app/src/components/dialog-command-palette-v2.tsx @@ -79,7 +79,7 @@ export function DialogHomeCommandPaletteV2(props: { server: ServerConnection.key(props.server), opened: serverCtx.projects.list, stored: () => serverCtx.sync.data.project, - load: (search, signal) => serverCtx.sdk.currentApi.session.list({ parentID: null, search, limit: 50 }, { signal }), + load: (search, signal) => serverCtx.sdk.api.session.list({ parentID: null, search, limit: 50 }, { signal }), untitled: () => language.t("command.session.new"), category: () => language.t("command.category.session"), }) diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 615640089fc..8291bfc9474 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -418,7 +418,7 @@ function ProviderConnection(props: { () => ({ provider: props.provider, directory: directory() }), (input) => serverSDK() - .currentApi.integration.get({ + .api.integration.get({ integrationID: input.provider, location: input.directory ? { directory: input.directory } : undefined, }) @@ -547,7 +547,7 @@ function ProviderConnection(props: { } dispatch({ type: "auth.pending" }) await serverSDK() - .currentApi.integration.oauth.connect({ + .api.integration.oauth.connect({ integrationID: props.provider, methodID: method.id, inputs: inputs ?? {}, @@ -816,7 +816,7 @@ function ProviderConnection(props: { } setFormStore("error", undefined) - await serverSDK().currentApi.integration.connect.key({ + await serverSDK().api.integration.connect.key({ integrationID: props.provider, location: location(), key: apiKey, @@ -947,7 +947,7 @@ function ProviderConnection(props: { setFormStore("error", undefined) const result = await serverSDK() - .currentApi.integration.oauth.complete({ + .api.integration.oauth.complete({ integrationID: props.provider, attemptID: store.authorization!.attemptID, location: location(), @@ -1044,7 +1044,7 @@ function ProviderConnection(props: { const authorization = store.authorization if (!authorization || !alive.value) return const result = await serverSDK() - .currentApi.integration.oauth.status({ + .api.integration.oauth.status({ integrationID: props.provider, attemptID: authorization.attemptID, location: location(), diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index af476f38b79..cb6b531c61f 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -130,26 +130,9 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) { } const saveMutation = useMutation(() => ({ - mutationFn: async (result: NonNullable>) => { - if ((await serverSDK().protocol) !== "v1") throw new Error("Custom providers are unavailable on this server") - const disabledProviders = serverSync().data.config.disabled_providers ?? [] - const nextDisabled = disabledProviders.filter((id) => id !== result.providerID) - - if (result.key) { - await serverSDK().legacy.auth.set({ - providerID: result.providerID, - auth: { - type: "api", - key: result.key, - }, - }) - } - - await serverSync().updateConfig({ - provider: { [result.providerID]: result.config }, - disabled_providers: nextDisabled, - }) - return result + mutationFn: async (result: NonNullable>): Promise => { + // TODO: Restore custom providers when V2 exposes config and arbitrary credential APIs. + throw new Error(`Custom provider ${result.providerID} is unavailable`) }, onSuccess: (result) => { dialog.close() diff --git a/packages/app/src/components/dialog-edit-project-v2.tsx b/packages/app/src/components/dialog-edit-project-v2.tsx index dc9714aa062..77adec7b1ca 100644 --- a/packages/app/src/components/dialog-edit-project-v2.tsx +++ b/packages/app/src/components/dialog-edit-project-v2.tsx @@ -146,7 +146,7 @@ export function DialogEditProjectV2(props: { project: LocalProject; server: Serv {language.t("common.cancel")} - + {model.save.isPending ? language.t("common.saving") : language.t("common.save")} diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index 86a9630359b..58c2a08956a 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -160,7 +160,7 @@ export function DialogEditProject(props: { project: LocalProject; server: Server - diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx index 86c3a21e2c8..6fe0c445601 100644 --- a/packages/app/src/components/dialog-fork.tsx +++ b/packages/app/src/components/dialog-fork.tsx @@ -69,7 +69,7 @@ export const DialogFork: Component = () => { const dir = base64Encode(sdk().directory) sdk() - .currentApi.session.fork({ sessionID, boundary: { type: "before", messageID: item.id } }) + .api.session.fork({ sessionID, boundary: { type: "before", messageID: item.id } }) .then((forked) => { dialog.close() prompt.set(restored, undefined, { dir, id: forked.id }) diff --git a/packages/app/src/components/dialog-select-directory-v2.tsx b/packages/app/src/components/dialog-select-directory-v2.tsx index ada42b7d83e..e3bf4c19af7 100644 --- a/packages/app/src/components/dialog-select-directory-v2.tsx +++ b/packages/app/src/components/dialog-select-directory-v2.tsx @@ -66,23 +66,21 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) { let pathArea: HTMLDivElement | undefined let navigation = 0 - const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory)) const [fallbackPath] = createResource( - () => (missingBase() ? true : undefined), - async (): Promise => { - if ((await sdk.protocol) === "v1") - return sdk.legacy.path.get().catch(() => undefined) - return sdk.api.location + () => (!(sync.data.path.home || sync.data.path.directory) ? true : undefined), + () => + sdk.api.location .get() - .then((location) => ({ - state: "", - config: "", - worktree: location.project.directory, - directory: location.directory, - home: "", - })) - .catch(() => undefined) - }, + .then( + (location): Path => ({ + state: "", + config: "", + worktree: location.project.directory, + directory: location.directory, + home: "", + }), + ) + .catch(() => undefined), { initialValue: undefined }, ) const home = createMemo(() => sync.data.path.home || fallbackPath()?.home || "") @@ -104,7 +102,7 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) { if (!policy.includeFiles) return { query: value, items: directories.slice(0, 5) } const base = pickerRoot(cleaned) || root() || start() if (!base) return { query: value, items: directories.slice(0, 5) } - const files = await sdk.currentApi.file + const files = await sdk.api.file .find({ location: { directory: base }, query: pickerFileSearchQuery(base, value, home()), @@ -134,7 +132,7 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) { existing ?? loads.schedule(`${generation}:${key}`, eager ? "background" : "user", () => { if (!activeTreeNavigation(generation, navigation)) return Promise.resolve(undefined) - return sdk.currentApi.file + return sdk.api.file .list({ location: { directory: absolute } }) .then((result) => result.data.map((entry) => ({ diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx index a6b1894a839..33c8a73d41e 100644 --- a/packages/app/src/components/dialog-select-directory.tsx +++ b/packages/app/src/components/dialog-select-directory.tsx @@ -57,23 +57,21 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) { const [filter, setFilter] = createSignal("") let list: ListRef | undefined - const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory)) const [fallbackPath] = createResource( - () => (missingBase() ? true : undefined), - async (): Promise => { - if ((await sdk.protocol) === "v1") - return sdk.legacy.path.get().catch(() => undefined) - return sdk.api.location + () => (!(sync.data.path.home || sync.data.path.directory) ? true : undefined), + () => + sdk.api.location .get() - .then((location) => ({ - state: "", - config: "", - worktree: location.project.directory, - directory: location.directory, - home: "", - })) - .catch(() => undefined) - }, + .then( + (location): Path => ({ + state: "", + config: "", + worktree: location.project.directory, + directory: location.directory, + home: "", + }), + ) + .catch(() => undefined), { initialValue: undefined }, ) diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index 23102204c6d..aa914775e0b 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -16,7 +16,6 @@ import { useGlobal } from "@/context/global" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" -import { detectServerProtocol } from "@/utils/server-protocol" import { type ServerHealth, useCheckServerHealth } from "@/utils/server-health" import { useSettings } from "@/context/settings" import { useTabs } from "@/context/tabs" @@ -264,13 +263,6 @@ export function useServerManagementController(options: { onSelect?: () => void; setStore("addServer", { error: language.t("dialog.server.add.error") }) return } - if ( - !settings.general.newLayoutDesigns() && - (await detectServerProtocol(conn.http, platform.fetch ?? globalThis.fetch)) === "v2" - ) { - setStore("addServer", { error: language.t("dialog.server.add.error") }) - return - } resetAdd() if (options.navigateOnAdd === false) { @@ -315,13 +307,6 @@ export function useServerManagementController(options: { onSelect?: () => void; setStore("editServer", { error: language.t("dialog.server.add.error") }) return } - if ( - !settings.general.newLayoutDesigns() && - (await detectServerProtocol(conn.http, platform.fetch ?? globalThis.fetch)) === "v2" - ) { - setStore("editServer", { error: language.t("dialog.server.add.error") }) - return - } if (normalized === input.original.http.url) { server.add(conn) } else { @@ -360,9 +345,7 @@ export function useServerManagementController(options: { onSelect?: () => void; const sortedItems = createMemo(() => { const raw = items() - const list = settings.general.newLayoutDesigns() - ? raw - : raw.filter((x) => global.ensureServerCtx(x).sdk.protocolKind() !== "v2") + const list = raw if (!list.length) return list const active = current() const order = new Map(list.map((url, index) => [url, index] as const)) diff --git a/packages/app/src/components/directory-picker-domain.test.ts b/packages/app/src/components/directory-picker-domain.test.ts index 26fb9f2ffef..1bc9af08334 100644 --- a/packages/app/src/components/directory-picker-domain.test.ts +++ b/packages/app/src/components/directory-picker-domain.test.ts @@ -133,7 +133,7 @@ test("scopes file autocomplete to the current browser root", () => { test("resolves directory autocomplete from the current browser root", async () => { const directories: string[] = [] const sdk = { - currentApi: { + api: { file: { find: (input: { location?: { directory?: string } }) => { directories.push(input.location?.directory ?? "") @@ -155,7 +155,7 @@ test("resolves directory autocomplete from the current browser root", async () = test("searches from an absolute root without a default base", async () => { const directories: string[] = [] const sdk = { - currentApi: { + api: { file: { list: (input: { location?: { directory?: string } }) => { directories.push(input.location?.directory ?? "") diff --git a/packages/app/src/components/directory-picker-domain.ts b/packages/app/src/components/directory-picker-domain.ts index 075b16a3144..9539ae1d01d 100644 --- a/packages/app/src/components/directory-picker-domain.ts +++ b/packages/app/src/components/directory-picker-domain.ts @@ -342,7 +342,7 @@ export function createDirectorySearch(args: { sdk: ServerSDK; base: () => string const key = trimPickerPath(directory) const existing = cache.get(key) if (existing) return existing - const request = args.sdk.currentApi.file + const request = args.sdk.api.file .list({ location: { directory: key } }) .then((result) => result.data) .catch(() => []) @@ -374,7 +374,7 @@ export function createDirectorySearch(args: { sdk: ServerSDK; base: () => string const pathInput = raw.startsWith("~") || !!pickerRoot(raw) || raw.includes("/") const query = normalizePickerDrive(input.path) if (!pathInput) { - const results = await args.sdk.currentApi.file + const results = await args.sdk.api.file .find({ location: { directory: input.directory }, query, type: "directory", limit: 50 }) .then((result) => result.data.map((entry) => entry.path)) .catch(() => []) diff --git a/packages/app/src/components/edit-project.ts b/packages/app/src/components/edit-project.ts index efbf80840d4..16b54fc72d2 100644 --- a/packages/app/src/components/edit-project.ts +++ b/packages/app/src/components/edit-project.ts @@ -9,6 +9,7 @@ import { type LocalProject } from "@/context/layout" import { ServerConnection } from "@/context/server" export function createEditProjectModel(props: { project: LocalProject; server: ServerConnection.Any }) { + const supported = !props.project.id || props.project.id === "global" const dialog = useDialog() const global = useGlobal() const serverCtx = createMemo(() => global.ensureServerCtx(props.server)) @@ -71,23 +72,9 @@ export function createEditProjectModel(props: { project: LocalProject; server: S const start = store.startup.trim() if (props.project.id && props.project.id !== "global") { - if ((await serverCtx().sdk.protocol) !== "v1") return - const project = await serverCtx() - .sdk.legacy.project.update({ - projectID: props.project.id, - directory: props.project.worktree, - name, - icon: { color: store.color || "", override: store.iconOverride || "" }, - commands: { start }, - }) - .then((result) => result.data) - if (!project) return - serverCtx().sync.set("project", (items) => - items.map((item) => (item.id === project.id ? normalizeProjectInfo(project) : item)), - ) - serverCtx().sync.project.icon(props.project.worktree, store.iconOverride || undefined) - dialog.close() - return + // TODO: Restore project edits when the V2 client exposes a project update API. + // await serverCtx().sdk.api.project.update({ projectID: props.project.id, name, icon, commands }) + throw new Error(`Project ${props.project.id} cannot be updated`) } serverCtx().sync.project.meta(props.project.worktree, { @@ -101,7 +88,7 @@ export function createEditProjectModel(props: { project: LocalProject; server: S function submit(event: SubmitEvent) { event.preventDefault() - if (save.isPending) return + if (!supported || save.isPending) return save.mutate() } @@ -111,6 +98,7 @@ export function createEditProjectModel(props: { project: LocalProject; server: S folderName, defaultName, save, + supported, submit, drop, dragOver, diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index e79b6617e07..54f33a3d4f7 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -125,13 +125,6 @@ beforeAll(async () => { useSearchParams: () => [search, () => undefined], })) - mock.module("@opencode-ai/sdk/v2/client", () => ({ - createOpencodeClient: (input: { directory: string }) => { - createdClients.push(input.directory) - return clientFor(input.directory) - }, - })) - mock.module("@opencode-ai/ui/toast", () => ({ Toast: { Region: () => null }, showToast: () => 0, @@ -197,13 +190,8 @@ beforeAll(async () => { const sdk = { scope: "local", directory: "/repo/main", - client: rootClient, api: rootClient.api, - currentApi: rootClient.api, url: "http://localhost:4096", - createClient(opts: any) { - return clientFor(opts.directory) - }, } return () => sdk }, diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index e001a63987f..bc421ed58fc 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -15,6 +15,7 @@ import { useSDK, type DirectorySDK } from "@/context/sdk" import { useSync, type DirectorySync } from "@/context/sync" import { Identifier } from "@/utils/id" import { Worktree as WorktreeState } from "@/utils/worktree" +import { getDirectory } from "@opencode-ai/core/util/path" import { buildRequestParts } from "./build-request-parts" import { setCursorPosition } from "./editor-dom" import { formatServerError } from "@/utils/server-errors" @@ -22,7 +23,6 @@ import { ScopedKey } from "@/utils/server-scope" import { createPromptSubmissionState } from "./submission-state" import { normalizeSessionInfo } from "@/utils/session" import { Event } from "@opencode-ai/schema/event" -import { getDirectory } from "@opencode-ai/core/util/path" type PendingPrompt = { abort: AbortController @@ -42,7 +42,7 @@ export type FollowupDraft = { } type FollowupSendInput = { - api: DirectorySDK["currentApi"]["session"] + api: DirectorySDK["api"]["session"] serverSync: ServerSync sync: DirectorySync draft: FollowupDraft @@ -261,7 +261,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { return Promise.resolve() } return sdk() - .currentApi.session.interrupt({ sessionID }) + .api.session.interrupt({ sessionID }) .catch(() => {}) } @@ -345,11 +345,10 @@ export function createPromptSubmit(input: PromptSubmitInput) { const worktreeSelection = input.newSessionWorktree?.() || "main" let sessionDirectory = projectDirectory - if (isNewSession) { if (worktreeSelection === "create") { const createdWorktree = await sdk() - .currentApi.projectCopy.create({ + .api.projectCopy.create({ projectID: sync().data.project, strategy: "git_worktree", directory: getDirectory(projectDirectory), @@ -362,7 +361,6 @@ export function createPromptSubmit(input: PromptSubmitInput) { }) return undefined }) - if (!createdWorktree) return WorktreeState.pending(sdk().scope, createdWorktree.directory) sessionDirectory = createdWorktree.directory @@ -382,7 +380,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { let session = input.info() if (!session && isNewSession) { const created = await sdk() - .currentApi.session.create({ + .api.session.create({ agent: currentAgent.name, model: { id: currentModel.id, providerID: currentModel.provider.id, variant }, location: { directory: sessionDirectory }, @@ -473,7 +471,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { clearInput() const eventID = Event.ID.create() sdk() - .currentApi.session.shell({ + .api.session.shell({ sessionID: session.id, id: eventID, command: text, @@ -497,7 +495,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { const messageID = Identifier.ascending("message") serverSync().session.set("session_status", session.id, { type: "busy" }) sdk() - .currentApi.session.command({ + .api.session.command({ sessionID: session.id, id: messageID, command: commandName, @@ -594,7 +592,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { } void sendFollowupDraft({ - api: sdk().currentApi.session, + api: sdk().api.session, sync: sync(), serverSync: serverSync(), draft, diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 18a2945c963..b238d98795f 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -13,7 +13,7 @@ import { useLanguage } from "@/context/language" import { usePermission } from "@/context/permission" import { usePlatform, type DisplayBackend } from "@/context/platform" import { useServerSync } from "@/context/server-sync" -import { useServerProtocol, useServerSDK } from "@/context/server-sdk" +import { useServerSDK } from "@/context/server-sdk" import { useUpdaterAction } from "./updater-action" import { monoDefault, @@ -125,11 +125,12 @@ export const SettingsGeneral: Component = () => { const serverSync = useServerSync() const serverSdk = useServerSDK() - const protocol = useServerProtocol() const [shells] = createResource( - () => (protocol() === "v1" ? serverSdk() : undefined), - (sdk) => sdk.legacy.pty.shells().catch(() => [] as ShellOption[]), + async () => { + // TODO: Restore executable shell discovery; V2 shell.list only lists shell processes. + return [] as ShellOption[] + }, { initialValue: [] as ShellOption[] }, ) @@ -320,13 +321,13 @@ export const SettingsGeneral: Component = () => { - - +