Skip to content

docs(migration): list v1 to v2 client breaking changes - #15291

Open
marius-kilocode wants to merge 3 commits into
v2from
client-breaking-changes
Open

marius-kilocode wants to merge 3 commits into
v2from
client-breaking-changes

Conversation

@marius-kilocode

Copy link
Copy Markdown
Collaborator

What Problem This Solves

The VS Code extension for the v2 line is a native client of the Kilo host over @opencode/client. There was no single list of what a client must change to move from the v1 server to the v2 server. JetBrains and the cloud consumers need the same facts at the wire level, because they do not use the TypeScript client.

Why This Change Was Made

Adds migration-tracking/technical-notes/v1-v2-client-breaking-changes.md:

  • Wire basics: spawn (serve --port 0 --stdio, one JSON line, stdin as process lifetime), Basic auth user (kilo on v1, opencode on v2), the /api prefix, location[directory] scoping, response envelopes, the volatile event stream at GET /api/event, plugin RPC framing.
  • One row for each of the 129 distinct client operations the v1 extension calls on origin/main (5cc65d1212b): 99 match client.<group>.<method>(, 30 use other call shapes (multi-line chains, aliases, four-level chains). Each row gives the v1 route, then the v2 operation and route, a kilocode.gateway RPC method, needs Kilo RPC with the plan unit, or dropped with the reason. Plus the 7 raw routes outside the client.
  • The eight v1/v2 mismatches (model and agent on the session, replies routed per session, questions as forms, no session.status event, part order, message cursors, a catalog that fills in after startup, tool names and inputs) and how a v2-native client handles each.
  • The session status rules from session.execution.* events, which match @opencode/client/solid as the TUI uses it.

The facts come from reading origin/main and origin/v2 (0311551d521, after the v2.1.0 sync). The vscode-v2-boot branch (a v1-shaped adapter, commits 47b491ef2c2 and 5aa92629895) is cited as a mechanism reference only.

The D-5 decision record (native rebuild for VS Code, no v1 wire facade) and the G2 plan rows are in the coordinator PR on branch plan-foundation, not here.

User Impact

None. Documentation only.

Evidence

Result Operations
v2 operation 55
Kilo Gateway RPC 3
needs Kilo RPC 67 (36 plan units)
dropped 4
  • Operation list: git grep -h -oE "client\.[a-zA-Z]+\.[a-zA-Z]+(\.[a-zA-Z]+)?\(" origin/main -- packages/kilo-vscode/src | sort -u gives 99 names; the 30 others were found by call-site review. A diff of the doc rows against the grep list shows exactly those 30 extra names and no missing name.
  • bun run script/check-md-table-padding.ts <note>: no padded tables. The script exists only on main; it was run from a temporary copy.
  • bun run script/check-shared-patches.ts --worktree: ok.
  • bun run script/check-opencode-annotations.ts --worktree: ok.
  • bun script/upstream/progress.ts --check: up to date (the note does not change the map).

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

➖ v2 migration progress: 0.9% → 0.9% (0 done, 0 total, 0 dropped; 90/9829 cells)

Placement: 0 new marked lines in shared files, 0 new kilo_port annotations, 0 new core-internal imports.

Ratchets: 9 core-internal imports (base 9); shared patches by rung 20 seam, 7 patch (base 20 seam, 7 patch).

No change to the progress map.

v2 migration progress map at this commit

Full map · how cells close

@kilo-code-bot

kilo-code-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 7 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 5
Issue Details (click to expand)

WARNING

File Line Issue
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 19 The password is removed only under its OPENCODE_ name; with KILO_SERVER_PASSWORD, the KILO_ variable stays in process.env and is inherited by shell tools, so the row's claim is wrong.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 212 POST /api/pty/:ptyID/connect-token requires the x-opencode-ticket: 1 header (403 otherwise) and returns { location, data: { ticket, expires_in } }, not { ticket, expires_in }.

SUGGESTION

File Line Issue
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 21 Paths outside /api do not return 404: /auth/connect/:code is a route and other paths serve the Web UI app shell.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 25 GET /api/info returns version, pid, urls, paths and capabilities; there is no server name or channel field.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 26 The note's pinned v2 source (0311551d521) predates kilocode.handshake, which arrived in 6b405b8d9f (PR #15285); the claim is correct for current v2 but not at the cited SHA.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 49 session.update also publishes session.permissions, session.archived and session.unarchived, not only session.renamed/session.metadata.updated.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 131 The handshake response body is { output: { product, version, channel } } (RpcOutput), not the bare object; the row should make the wrapper explicit.
Files Reviewed (1 file)
  • migration-tracking/technical-notes/v1-v2-client-breaking-changes.md - 7 issues

Fix these issues in Kilo Cloud

Previous Review Summary (commit 542c45c)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 542c45c)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 19 The password is removed only under its OPENCODE_ name; with KILO_SERVER_PASSWORD, the KILO_ variable stays in process.env and is inherited by shell tools, so the row's claim is wrong.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 212 POST /api/pty/:ptyID/connect-token requires the x-opencode-ticket: 1 header (403 otherwise) and returns { location, data: { ticket, expires_in } }, not { ticket, expires_in }.

SUGGESTION

File Line Issue
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 21 Paths outside /api do not return 404: /auth/connect/:code is a route and other paths serve the Web UI app shell.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 25 GET /api/info returns version, pid, urls, paths and capabilities; there is no server name or channel field.
migration-tracking/technical-notes/v1-v2-client-breaking-changes.md 49 session.update also publishes session.permissions, session.archived and session.unarchived, not only session.renamed/session.metadata.updated.
Files Reviewed (2 files)
  • migration-tracking/technical-notes/v1-v2-client-breaking-changes.md - 5 issues
  • bun.lock - reordering only, no dependency or patch change, no finding

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 54K · Output: 12.8K · Cached: 1.4M

Review guidance: REVIEW.md from base branch v2

|---|---|---|
| Start | `kilo serve --port 0`; stdout line `kilo server listening on http://127.0.0.1:<port>` | `kilo2 serve --port 0 --stdio`; stdout prints one JSON line `{"url":"http://127.0.0.1:<port>"}` (`packages/cli/src/server-process.ts`) |
| Process lifetime | The server watches `KILO_PARENT_PID` and exits when the parent dies | The server exits when its stdin closes (`waitForStdinClose`). The client keeps the stdin pipe open and closes it to stop the server. There is no parent PID watchdog |
| Password | `KILO_SERVER_PASSWORD` | `KILO_SERVER_PASSWORD` still works: the host copies it to `OPENCODE_SERVER_PASSWORD` (`packages/kilo-cli/src/env.ts`). In `--stdio` mode the server removes it from the environment that tools inherit |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The password is only removed under its OPENCODE_ name

packages/cli/src/server-process.ts:87-90 deletes OPENCODE_PASSWORD and OPENCODE_SERVER_PASSWORD in --stdio mode. The host's translate() copies KILO_SERVER_PASSWORD to the OPENCODE_ name but leaves KILO_SERVER_PASSWORD in process.env (packages/kilo-cli/src/env.ts:28-34), and shell tools inherit process.env (packages/core/src/shell.ts:275-278). A client that follows this row and sets KILO_SERVER_PASSWORD therefore still exposes the credential to every tool it runs. Either the host should delete the KILO_ name as well, or the note should say that only the OPENCODE_ copy is scrubbed.

| Process lifetime | The server watches `KILO_PARENT_PID` and exits when the parent dies | The server exits when its stdin closes (`waitForStdinClose`). The client keeps the stdin pipe open and closes it to stop the server. There is no parent PID watchdog |
| Password | `KILO_SERVER_PASSWORD` | `KILO_SERVER_PASSWORD` still works: the host copies it to `OPENCODE_SERVER_PASSWORD` (`packages/kilo-cli/src/env.ts`). In `--stdio` mode the server removes it from the environment that tools inherit |
| Auth | HTTP Basic, user `kilo` | HTTP Basic, user `opencode` (`packages/server/src/auth.ts`). Browser and WebSocket clients can send the same credential as `?auth_token=<base64 user:password>` |
| Path prefix | none (`/session`, `/provider`) | `/api` (`/api/session`, `/api/provider`). Paths outside `/api` return 404 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Paths outside /api do not return 404

/auth/connect/:code is a route (packages/protocol/src/groups/server.ts:33), and the CLI's Web UI handler serves the app shell for every other non-/api path (packages/cli/src/services/web-ui.ts:30-44, wired in packages/cli/src/server-process.ts:99). A client that treats any non-404 response as an API answer reads HTML. Consider saying that non-/api paths are not JSON API routes, and mention the pairing path if it is in scope for these clients.

| Directory scope | `?directory=<path>` on almost every route, plus `?workspace=` | Location-scoped routes take `?location[directory]=<path>` (deepObject). `POST /api/session` takes `location: { directory }` in the body. Without the query, the header `x-opencode-directory` (URI-encoded) applies, then the server working directory. Session-scoped routes (`/api/session/:sessionID/...`) resolve the location from the session and take no directory |
| Response body | The resource itself | An envelope: `{ data }`, `{ data, cursor: { previous, next } }` for pages, or `{ location, data }` for location-scoped reads (`Location.response`) |
| Events | `GET /global/event`, SSE of `{ directory, project, workspace, payload }`; `payload.type === "sync"` wraps durable events (`session.updated.1`, `message.part.updated.1`) | `GET /api/event`, SSE of `{ id, created, type, location, data, metadata }`. One stream for all locations. Volatile: no replay, a slow consumer fails the stream, and events during a disconnect are lost. After a reconnect the client reloads state with list calls, or replays the durable events of one session with `GET /api/experimental/session/:sessionID/log?after=<seq>&follow=true` (SSE; durable events carry `durable: { aggregateID, seq, version }`) |
| Health | `GET /global/health` | `GET /api/info` (server name, version, channel) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: GET /api/info has no server name or channel

The response is { version, pid, urls, paths: { tmp }, capabilities? } (packages/protocol/src/groups/server.ts:4-20). The app name and channel are passed into the server process but are not exposed here, so the field list should name version, urls, paths and capabilities instead.

| session.get | `GET /session/{id}` | session.get | `GET /api/session/:sessionID`. The session carries `agent`, `model`, `cost`, `tokens`, `location.directory` and `time.archived` |
| session.list | `GET /session` | session.list | `GET /api/session?directory=&project=&subpath=&parentID=null&search=&order=&limit=&cursor=`. `parentID=null` replaces `roots=true` |
| experimental.session.list | `GET /experimental/session` | session.list | Same route, with `archived=true\|false`. v1 `worktrees` and `current` filters have no v2 query |
| session.update | `PATCH /session/{id}` | session.update | `PATCH /api/session/:sessionID` body `{ title?, metadata?, permissions?, archived? }`. Returns 204; the change arrives as `session.renamed` or `session.metadata.updated` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: session.update publishes more than two events

Besides session.renamed and session.metadata.updated, changing permissions publishes session.permissions, and changing archived publishes session.archived or session.unarchived (packages/schema/src/session-event.ts:120-150; the handler applies all four fields at packages/server/src/handlers/session.ts:228-252). A client that listens only for the two named events misses permission and archive changes.

| v1 route | v2 | Notes |
|---|---|---|
| `GET /global/health` | server.info | `GET /api/info`. A health poll is optional: the stdio pipe and the event stream show a dead server |
| WebSocket `/pty/{id}/connect?directory=&cursor=&auth_token=` | pty.connectToken | `POST /api/pty/:ptyID/connect-token` returns `{ ticket, expires_in }`; then WebSocket `/api/pty/:ptyID/connect?ticket=<ticket>&cursor=` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Minting a PTY connect token also needs a header and returns a location envelope

The handler requires the x-opencode-ticket: 1 header and rejects the request with 403 otherwise (packages/server/src/handlers/pty.ts:117-131; the header name is PTY_CONNECT_TOKEN_HEADER in packages/protocol/src/groups/pty.ts:10). The response is Location.response(PtyTicket.ConnectToken), so it is { location, data: { ticket, expires_in } }, not { ticket, expires_in } (packages/protocol/src/groups/pty.ts:103-108). A client built from this row is rejected.

| Response body | The resource itself | An envelope: `{ data }`, `{ data, cursor: { previous, next } }` for pages, or `{ location, data }` for location-scoped reads (`Location.response`) |
| Events | `GET /global/event`, SSE of `{ directory, project, workspace, payload }`; `payload.type === "sync"` wraps durable events (`session.updated.1`, `message.part.updated.1`) | `GET /api/event`, SSE of `{ id, created, type, location, data, metadata }`. One stream for all locations. Volatile: no replay, a slow consumer fails the stream, and events during a disconnect are lost. After a reconnect the client reloads state with list calls, or replays the durable events of one session with `GET /api/experimental/session/:sessionID/log?after=<seq>&follow=true` (SSE; durable events carry `durable: { aggregateID, seq, version }`) |
| Health | `GET /global/health` | `GET /api/info` (server name, version, channel) |
| Kilo routes | Hono routes under `/kilo/*`, `/kilocode/*` and others | Plugin RPC: `POST /api/rpc/:rpcID/:method?location[directory]=<path>`, body `{ "input": ... }`, response `{ "output": ... }`. RPC events arrive on `/api/event` with `type: "rpc.<rpcID>.<event>"`. The Kilo RPCs on v2 today are `kilocode.gateway` and `kilocode.handshake` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The cited v2 source predates the handshake RPC

kilocode.handshake landed on v2 after the commit this note cites as its v2 source (0311551d521): it arrived with 6b405b8d9f (PR #15285), which is on origin/v2 today. The statement is correct for current v2, but a reader who checks the note against the pinned SHA cannot find the RPC. Consider moving the Sources line to a SHA that contains the handshake, or noting that this row is newer than the pin.

| instance.dispose | `POST /instance/dispose` | location.reload | As above |
| instance.reload | `POST /instance/reload` | location.reload | As above |
| global.event | `GET /global/event` | event.subscribe | `GET /api/event`, see "Wire basics" |
| experimental.capabilities.get | `GET /experimental/capabilities` | kilocode.handshake.get for identity; needs Kilo RPC for capabilities | `POST /api/rpc/kilocode.handshake/get` body `{ "input": {} }` returns `{ product: "kilo", version, channel }`. An upstream server answers `rpc.unavailable`, a v1 server 404. Feature capabilities: U-E1-08, U-E6-02, shape per Q-G2-02 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The HTTP body wraps the output

The route success schema is { output: unknown } (packages/protocol/src/groups/rpc.ts:8-18), so the response body is { "output": { "product": "kilo", "version": ..., "channel": ... } }, not the bare object. The wire-basics row already says { "output": ... }, but this row reads as a bare response. Consider saying it returns the output { product, version, channel } to make the wrapper explicit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant