assess opencode's newer api, and find two live defects while doing it
Tonight's brief was to read everything about "OpenCode API 2.0" and write down what moving to
it would change and what it would buy. Two things fell out of the measuring that are not
migration concerns at all — they are broken in production right now:
The two surfaces are MUTUALLY BLIND. A session created through /api reads as [] on the legacy
GET /session/{id}/message, and a legacy session 500s on GET /api/session/{id}/message. We run
turns through /api since Phase D and read transcripts through legacy, so every opencode
conversation created since 2026-08-10 opens empty — the row carries its title and directory
from the session record, and the transcript underneath it is nothing.
GET /api/session defaults to 50 rows and hands back a cursor.next. We send neither limit nor
cursor, so the oldest sessions silently stop appearing once the store passes 50. The local
store is at exactly 50 today. That is this morning's commit.
On the name: there is no "2.0" in the running server, and "API 2.0" turns out to mean two
different things. The /api/* surface in 1.18.16 has operation ids literally called v2.*, and
we already run every turn on it — so it is not something to adopt, it is something to finish.
OpenCode 2.0 the product is a separate beta (binary opencode2, npm @next) whose docs warn it
may wipe data, and which REMOVES the two durable routes the restart-recovery work would depend
on, in favour of an experimental/ path. Worth knowing before building on them.
Verified by driving a real turn end to end: the durable event log replays from a cursor
(?after=5 returned exactly 6-10, and the SSE at ?after=7 replayed 8,9,10 then held the socket),
which is the answer to the gap Phase B left open. But deltas are live-only BY SCHEMA — the
durable oneOf has 28 members and omits text.delta, tool.input.delta, reasoning.delta,
compaction.delta — so both streams are needed, not one.
Also reproduced a second silent-failure mode with the same signature as the missing credential:
a session with no model, on a serve with no configured default, sits at admitted -> prompted
forever. Our runner only sets a model when one was asked for.
Probes cleaned up after themselves; the session store is back to the 50 rows it started with.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,353 @@
|
|||||||
|
# OpenCode's newer API — what it is, what it would cost, what it buys
|
||||||
|
|
||||||
|
Written 2026-08-11 against **opencode 1.18.16**, from three sources: the running server's own OpenAPI
|
||||||
|
document (`GET /doc` on `opencode serve`), live probes against a real serve, and upstream docs/npm.
|
||||||
|
Every claim below is marked by where it came from. Measurements were taken on the local serve
|
||||||
|
(port 49698, the `officer-opencode` sidecar's own) and cleaned up afterwards — the session store is
|
||||||
|
back to the 50 rows it started with.
|
||||||
|
|
||||||
|
Read this before starting any opencode work. Two live defects fell out of writing it (§1), and the
|
||||||
|
naming is actively misleading (§2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Two live defects, found while measuring
|
||||||
|
|
||||||
|
Neither is a migration concern. Both are broken right now, in production, and both are consequences of
|
||||||
|
being half-migrated.
|
||||||
|
|
||||||
|
### 1a. Every OpenCode conversation created since 2026-08-10 opens EMPTY
|
||||||
|
|
||||||
|
Since Phase D, turns run through `POST /api/session/{id}/prompt`, so the session belongs to the newer
|
||||||
|
engine. But `loadOpenCodeSession` reads the transcript through the legacy route
|
||||||
|
(`client.ts:51` → `GET /session/{id}/message`).
|
||||||
|
|
||||||
|
**The two surfaces are mutually blind.** Measured, both directions, on a session created via `/api` and
|
||||||
|
run to completion with a real model reply:
|
||||||
|
|
||||||
|
| read | api-created session | legacy-created session |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET /session/{id}/message` (what we call) | **`[]` — 0 messages** | 200, full transcript |
|
||||||
|
| `GET /api/session/{id}/message` | 200, 3 messages | **500** |
|
||||||
|
| `GET /session/{id}` (the record) | 200, title + directory | 200 |
|
||||||
|
|
||||||
|
So the row appears in the list with its title and directory, and opens with nothing in it. And the
|
||||||
|
inverse is equally true: switching the reader to `/api` without keeping the old one would empty every
|
||||||
|
conversation from before 2026-08-10.
|
||||||
|
|
||||||
|
The fix is not "swap the endpoint" — it is "route by which engine owns the session", and there is no
|
||||||
|
field that says so. The one usable discriminator found tonight is that the legacy read returns `[]`
|
||||||
|
rather than erroring.
|
||||||
|
|
||||||
|
### 1b. The session list silently truncates at 50
|
||||||
|
|
||||||
|
`GET /api/session` defaults to **50 rows** and returns a `cursor.next`. Measured: with 50 sessions in
|
||||||
|
the store the list returns 50 *and still offers a next cursor*; adding a 51st and asking `?limit=200`
|
||||||
|
returns 51 (and `limit` is capped at 100 — 200 is accepted for the list but `/history` rejects >100
|
||||||
|
with `Expected a value less than or equal to 100`).
|
||||||
|
|
||||||
|
`client.ts:36` sends neither `limit` nor `cursor`, so **once the store passes 50 sessions the oldest
|
||||||
|
stop appearing in `/chat`**. The local store is at exactly 50 today. This is in code shipped this
|
||||||
|
morning (`adaaba6`).
|
||||||
|
|
||||||
|
The same endpoint takes `directory=` — verified filtering correctly (`?directory=/tmp/oc-cap` → 11
|
||||||
|
rows, all in that directory). We fetch everything and filter client-side in `opencode-sessions.ts:49`.
|
||||||
|
Pushing the filter down fixes the normal case and brings `search=`, `order=`, `project=` with it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The naming, because "API 2.0" means two different things
|
||||||
|
|
||||||
|
There is no version string "2.0" in the running server. `GET /doc` self-reports
|
||||||
|
`{"openapi":"3.1.0","info":{"title":"opencode","version":"1.0.0"}}`. What actually exists:
|
||||||
|
|
||||||
|
| | **legacy** | **the `/api/*` surface** | **OpenCode 2.0 beta** |
|
||||||
|
|---|---|---|---|
|
||||||
|
| where | in 1.18.16 | in 1.18.16 | separate product, binary `opencode2`, npm `@next` |
|
||||||
|
| routes | 111 paths | 51 paths | ~100 paths, still moving |
|
||||||
|
| operationIds | `session.list` | **`v2.session.list`** | — |
|
||||||
|
| we use it | reads: transcript, delete, rename | writes: every turn since 2026-08-10 | not at all |
|
||||||
|
| docs | opencode.ai/docs/server (stale — never mentions `/api/*`) | undocumented publicly | opencode.ai/v2/docs |
|
||||||
|
|
||||||
|
So "API 2.0" most likely means **the `/api/*` surface — which we already run on for turns**. Its
|
||||||
|
operation ids are literally `v2.*`. It is not something to adopt; it is something to *finish*.
|
||||||
|
|
||||||
|
**OpenCode 2.0 the product is a different question**, and the answer tonight is not yet: the beta docs
|
||||||
|
carry the banner *"we may wipe your data, things may break, and APIs, configuration, and plugin APIs
|
||||||
|
may change"*, releases ship ~6/day, and the migration guide states three intentional breaking changes
|
||||||
|
(plugin API, server API contracts, TUI config), with *"Integrations that call the V1 server API must
|
||||||
|
migrate to the V2 API"*. No deprecation date for the legacy surface is published anywhere.
|
||||||
|
|
||||||
|
Two facts worth knowing regardless:
|
||||||
|
|
||||||
|
- **The repo moved.** `github.com/sst/opencode` 301s to **`github.com/anomalyco/opencode`**. Every npm
|
||||||
|
package now points there. No announcement was found explaining it.
|
||||||
|
- **There is already a typed client for the surface we run.** `@opencode-ai/sdk@1.18.16` ships two
|
||||||
|
generated clients: the default export covers legacy only, and **`@opencode-ai/sdk/v2` covers all 51
|
||||||
|
`/api/*` routes**. We have no opencode dependency at all today — every call is hand-rolled `fetch`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. What we call today
|
||||||
|
|
||||||
|
Two of our processes talk to one serve, with no shared client.
|
||||||
|
|
||||||
|
**Sidecar (`src/servers/sidecar/opencode/`) — already on `/api/*`:** `POST /api/session`
|
||||||
|
(`serve-runner.ts:222`), `POST …/model` (`:236`), `POST …/prompt` (`:266`, `:199`), `POST …/interrupt`
|
||||||
|
(`:328`), `GET /api/event` (`:86`), `GET /api/health` (`index.ts:79`),
|
||||||
|
`POST /api/integration/{provider}/connect/key` (`connect-credential.ts:66`).
|
||||||
|
|
||||||
|
**API server (`src/servers/api/chat/opencode/client.ts`) — still legacy:** `GET /session/{id}` (`:45`),
|
||||||
|
`GET /session/{id}/message` (`:51`), `DELETE /session/{id}` (`:57`), `PATCH /session/{id}` (`:62`),
|
||||||
|
plus `GET /config/providers` for the model list (`list-models.ts:58`). The one exception is
|
||||||
|
`GET /api/session` for the list (`:36`), moved this morning.
|
||||||
|
|
||||||
|
51 routes exist. We call 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. What the newer surface has that we don't use
|
||||||
|
|
||||||
|
### 4a. Adding context to a turn that is already running
|
||||||
|
|
||||||
|
The capability the subprocess path could never have, and the reason the migration happened.
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/session/{id}/prompt
|
||||||
|
{ "id": "msg_…", "prompt": { "text", "files": [{uri,name,description,source}],
|
||||||
|
"agents": [{name,source}] },
|
||||||
|
"delivery": "steer" | "queue", "resume": true|false }
|
||||||
|
```
|
||||||
|
|
||||||
|
Spec description: *"Durably admit one session input and schedule agent-loop execution unless resume is
|
||||||
|
false."*
|
||||||
|
|
||||||
|
- **`delivery: "steer"` injects into the RUNNING turn** — the model takes the new text as part of the
|
||||||
|
work in flight. No kill, no restart, no lost context. We already send it (`serve-runner.ts:199`) but
|
||||||
|
only on the accidental path: a message that happens to arrive mid-turn. Nothing in the UI *asks* for
|
||||||
|
it, and nothing distinguishes "add this to what you're doing" from "here's my next message".
|
||||||
|
- **`delivery: "queue"`** runs after the current turn. It must be stated explicitly — **the field
|
||||||
|
defaults to `steer`** — or two quick messages merge into one turn (`serve-runner.ts:268`).
|
||||||
|
- **`prompt.files[]`** attaches content to that same input; measured last night, it must be a `data:`
|
||||||
|
URI (a `file://` one is accepted with 200 and dies inside the provider). Each attachment also takes
|
||||||
|
a `description`, which we don't send.
|
||||||
|
- **`prompt.agents[]`** attaches an agent to the input. Unused, unexplored.
|
||||||
|
- **`id`** lets the caller mint the `msg_…` id, which is how a send survives a retry without
|
||||||
|
double-posting. We let the server mint it and therefore can't.
|
||||||
|
|
||||||
|
Measured: the POST returns in **22 ms** with `{"admittedSeq":1,"id":"msg_…","delivery":"queue"}`. It is
|
||||||
|
an admission receipt, not a turn — and `admittedSeq` is the durable cursor for everything that follows.
|
||||||
|
|
||||||
|
### 4b. Surviving a restart mid-turn — verified working
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/session/{id}/event?after=<seq> "Replay durable events after an aggregate sequence,
|
||||||
|
then continue with new durable events."
|
||||||
|
GET /api/session/{id}/history?limit=&after= "Read one finite page of public durable Session events
|
||||||
|
after an exclusive aggregate sequence."
|
||||||
|
```
|
||||||
|
|
||||||
|
Driven end to end tonight on a real turn (free model, "Reply with exactly: hi"):
|
||||||
|
|
||||||
|
```
|
||||||
|
seq 1 session.next.prompt.admitted seq 6 session.next.context.updated
|
||||||
|
seq 2 session.next.prompted seq 7 session.next.step.started
|
||||||
|
seq 3 session.next.model.switched seq 8 session.next.text.started
|
||||||
|
seq 4 session.next.prompt.admitted seq 9 session.next.text.ended
|
||||||
|
seq 5 session.next.prompted seq 10 session.next.step.ended
|
||||||
|
```
|
||||||
|
|
||||||
|
`?after=5` returned exactly 6–10. `GET …/event?after=7` replayed 8, 9, 10 and then held the socket open
|
||||||
|
for more. Every durable event carries `{aggregateID, seq: integer, version}`, so `after=` is that
|
||||||
|
integer. This is the documented, working answer to the gap Phase B left open and
|
||||||
|
`docs/opencode-testing-checklist.md` calls the most likely thing to be broken.
|
||||||
|
|
||||||
|
**But the two streams are not interchangeable, and the schema says why.** `SessionDurableEvent` is a
|
||||||
|
`oneOf` of exactly 28 members, and the five it omits are `text.delta`, `tool.input.delta`,
|
||||||
|
`reasoning.delta`, `compaction.delta` and the retry error. **Deltas are live-only by design; the
|
||||||
|
durable log stores whole values.** So a client that wants both token streaming and restart recovery
|
||||||
|
must read both streams: the global live one for deltas, the per-session durable one for the replayable
|
||||||
|
spine. Last night's 13-vs-21 event count was this same fact, found by counting instead of by reading.
|
||||||
|
|
||||||
|
### 4c. Knowing what is running, without having started it
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/session/active "Retrieve foreground Session drains currently owned by this OpenCode
|
||||||
|
process. Sessions absent from the result are inactive."
|
||||||
|
POST /api/session/{id}/wait "Wait for a session agent loop to become idle."
|
||||||
|
```
|
||||||
|
|
||||||
|
Today "what is running" is an in-memory map in our sidecar (`serve-runner.ts:66`). Restart the sidecar
|
||||||
|
and the truth is gone — which is why `/chat/live` can be wrong after a restart. `session/active` is the
|
||||||
|
server's own answer and survives us.
|
||||||
|
|
||||||
|
### 4d. Permissions and questions — nothing in officer models this
|
||||||
|
|
||||||
|
```
|
||||||
|
GET|POST /api/session/{id}/permission POST …/permission/{requestID}/reply
|
||||||
|
GET /api/permission/saved DELETE /api/permission/saved/{id}
|
||||||
|
GET /api/session/{id}/question POST …/question/{requestID}/reply | /reject
|
||||||
|
```
|
||||||
|
|
||||||
|
Plus `permission.v2.asked` / `question.v2.asked` events (the v1 families still exist alongside; the
|
||||||
|
only `deprecated: true` operation in the entire document is `POST /session/{id}/permissions/{id}`).
|
||||||
|
|
||||||
|
An opencode agent that wants consent, or that asks a question mid-turn, gets no answer from officer. We
|
||||||
|
don't subscribe to those events and have no route to reply on. Claude's harness runs
|
||||||
|
`--dangerously-skip-permissions`, so this has never been modelled for either harness. Largest single
|
||||||
|
behavioural gap.
|
||||||
|
|
||||||
|
### 4e. Undo, compaction, context
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/session/{id}/revert/stage {messageID, files?} …/revert/commit …/revert/clear
|
||||||
|
POST /api/session/{id}/compact GET /api/session/{id}/context
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage a revert to a message, then commit or discard. Explicit compaction with
|
||||||
|
`compaction.started/delta/ended` events, and a readable context state. Officer has none of this.
|
||||||
|
|
||||||
|
### 4f. The rest
|
||||||
|
|
||||||
|
`GET /api/agent`, `/api/skill`, `/api/command`, `/api/model`, `/api/provider`, `/api/fs/{list,find,read}`,
|
||||||
|
`GET|POST /api/pty` (+`connect`, `connect-token`), `POST /api/session/{id}/agent` (switch agent
|
||||||
|
mid-session), `/api/reference`, `/api/location`, `/api/integration`, `/api/credential/{id}`.
|
||||||
|
|
||||||
|
`GET /api/model` and `/api/provider` are the `/api` equivalents of the `/config/providers` call our
|
||||||
|
model list is built on (87 models locally). `/api/pty` overlaps our own pty sidecar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. What the event stream carries that we drop
|
||||||
|
|
||||||
|
Our mapper recognises 18 names and maps 7. The server emits **130 event type strings**, 32 in the
|
||||||
|
`session.next.*` family plus eight plain `session.*` (`idle`, `status`, `error`, `compacted`,
|
||||||
|
`created`, `deleted`, `updated`, `diff`).
|
||||||
|
|
||||||
|
| dropped | what it would give |
|
||||||
|
|---|---|
|
||||||
|
| `reasoning.started/delta/ended` | thinking, streamed — we show none for opencode |
|
||||||
|
| `tool.input.delta` / `.started` / `.ended` | a tool call rendering as its arguments arrive |
|
||||||
|
| `tool.progress` | long tools reporting instead of appearing hung |
|
||||||
|
| `shell.started/ended` | shell commands as a first-class thing |
|
||||||
|
| `compaction.*` | telling the user the context was compacted |
|
||||||
|
| `revert.*` | §4e |
|
||||||
|
| `retried` | a retry that currently looks like a stall |
|
||||||
|
| `prompt.admitted` / `prompted` | acknowledgement — the exact window where silence has twice cost an afternoon |
|
||||||
|
| `session.idle` | the real turn-end signal (see below) |
|
||||||
|
|
||||||
|
We end a turn on `step.ended` with `finish !== 'tool-calls'` (`serve-runner.ts:139`), because there is
|
||||||
|
no turn-ended event in what we read. `session.idle` looks like what that rule approximates, and it is
|
||||||
|
not in our `KNOWN` set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Two silent-failure modes, both reproduced tonight
|
||||||
|
|
||||||
|
Both produce the identical signature — `prompt.admitted`, `prompted`, then **nothing, forever**:
|
||||||
|
|
||||||
|
1. **No credential connected** for the `/api` surface. Already known and fixed at boot
|
||||||
|
(`connect-credential.ts`), but the failure has no error.
|
||||||
|
2. **No model on the session and no server default.** New tonight: my first probe sat at
|
||||||
|
`admitted → prompted` and stopped. `GET /config` reports `model: None`, and the session had no model
|
||||||
|
because I hadn't set one. `POST …/model` then re-prompting produced the full 10-event turn above.
|
||||||
|
|
||||||
|
Our runner only sends `POST …/model` when `params.model` is set (`serve-runner.ts:231`). **A turn sent
|
||||||
|
with no model, against a serve with no configured default, hangs silently.** Worth an explicit check.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. What finishing the migration would cost
|
||||||
|
|
||||||
|
- **Both readers stay.** §1a: `/api` reads 500 on legacy-owned sessions, legacy reads `[]` on
|
||||||
|
api-owned ones. Routing by ownership is required, and no field declares ownership.
|
||||||
|
- **Delete and rename cannot move.** `/api/session/{sessionID}` is **GET only**; `DELETE` and `PATCH`
|
||||||
|
exist only on the legacy route (spec-verified, and a live `DELETE` returned 200).
|
||||||
|
- **The transcript shape differs.** Legacy items are `{info:{role,…}, parts:[…]}` — what
|
||||||
|
`opencode-sessions.ts:81` parses. `/api` items are
|
||||||
|
`{id, time, type:'assistant', agent, model:{id,providerID,variant}, content:[{type:'text',id,text}],
|
||||||
|
finish, cost, tokens}`. A second mapper, or a shared normaliser.
|
||||||
|
- **The SSE parser needs to grow up.** `serve-runner.ts:89` is `data:`-only: no `event:`, no `id:`, no
|
||||||
|
comments, no `retry:`, no multi-line frames, fixed 1 s reconnect with no backoff. A cursored stream
|
||||||
|
must resume at `?after=<last seq>`, not restart.
|
||||||
|
- **Two envelope unwrappers and three hand-written type sets** (`serve-runner.ts:161`, `client.ts:38`;
|
||||||
|
types pinned by comment to two different opencode versions, 1.17.9 and 1.18.16). `@opencode-ai/sdk/v2`
|
||||||
|
would replace most of this — a dependency decision, and note our install is frozen, so it is a
|
||||||
|
deliberate lockfile change.
|
||||||
|
- **Stale comments in at least nine files** still describe the deleted `opencode run` subprocess path
|
||||||
|
(`protocol.ts:198`, `serve-events.ts:5`, `connect-credential.ts:24`, `index.ts:137`,
|
||||||
|
`websocket.ts:454`, `chat.ts:127`, `list-models.ts:75`, `sidecar-server.ts:8`, `send-opencode.ts:7`).
|
||||||
|
Two of them actively lie: they say turns read `auth.json` and don't depend on the credential connect.
|
||||||
|
They now do.
|
||||||
|
|
||||||
|
Unrelated but found while inventorying: the settings UI writes provider keys to `~/.pi/agent/auth.json`
|
||||||
|
(`chat-providers.ts:10`) while the credential connect reads `~/.local/share/opencode/auth.json`
|
||||||
|
(`connect-credential.ts:29`). Two different files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. What we'd gain immediately
|
||||||
|
|
||||||
|
Ordered by value over effort. 1–3 are bug fixes, not features.
|
||||||
|
|
||||||
|
1. **Transcripts that aren't empty** — route the read by session ownership. This is broken in
|
||||||
|
production now. (§1a)
|
||||||
|
2. **A list that doesn't stop at 50**, filtered server-side by `?directory=`. One call site. (§1b)
|
||||||
|
3. **A turn that can't hang silently** — set a model explicitly, or check `/config` for a default, and
|
||||||
|
say so out loud when neither exists. (§6)
|
||||||
|
4. **"Add to what you're doing" as a real control** — `delivery: "steer"` on a deliberate trigger
|
||||||
|
rather than only when a message happens to land mid-turn. The plumbing already exists. (§4a)
|
||||||
|
5. **Idempotent sends** — mint our own `msg_…`. One field. (§4a)
|
||||||
|
6. **Restart recovery** — subscribe `?after=<seq>` alongside the live stream. Verified working. (§4b)
|
||||||
|
7. **A truthful live panel** — `GET /api/session/active`. (§4c)
|
||||||
|
8. **Richer streaming for free** — reasoning deltas, tool-input deltas, tool progress, retries. Already
|
||||||
|
arriving on the socket we already read, and dropped in a `default:` case. (§5)
|
||||||
|
9. **Explicit compaction and context** instead of a long conversation quietly getting more expensive.
|
||||||
|
|
||||||
|
Then the two that are real features needing UI: **permissions/questions** (§4d) and **revert** (§4e).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. What this does NOT get us
|
||||||
|
|
||||||
|
- It does not retire the legacy surface: delete, rename and every pre-2026-08-10 transcript stay there,
|
||||||
|
with no deprecation date published.
|
||||||
|
- It does not touch the Claude harness — a different sidecar, a different protocol. Every gain above
|
||||||
|
lands on one harness only, while the chat UI assumes the two behave alike.
|
||||||
|
- It does not put us on OpenCode 2.0. Note the direction of travel there: the beta **removes**
|
||||||
|
`/api/session/{id}/history` and `/api/session/{id}/event` — the two durable routes item 6 depends on
|
||||||
|
— replacing them with `GET /api/experimental/session/{id}/log?after=&follow=`. Same idea, new path,
|
||||||
|
`experimental/` prefix. So item 6 is worth doing *and* worth writing behind one function.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Open questions
|
||||||
|
|
||||||
|
1. Is there a field that says which engine owns a session? Tonight's only discriminator is behavioural
|
||||||
|
(legacy returns `[]`). If not, we need our own record — we already store `sessionKey → ses_…` in
|
||||||
|
`opencode/state.ts` and could record the surface with it.
|
||||||
|
2. Do the `v2` permission/question events replace the v1 ones or run alongside? Both are emitted;
|
||||||
|
which one a 1.18.16 agent actually uses is unconfirmed.
|
||||||
|
3. What is `/api/*`'s auth story? The spec declares no `securitySchemes` yet every route declares a
|
||||||
|
`401`. The v1 docs describe HTTP Basic via `OPENCODE_SERVER_PASSWORD`; we run with none, on
|
||||||
|
loopback. In the 2.0 beta this is formalised as basic auth read from
|
||||||
|
`~/.local/state/opencode/service.json`.
|
||||||
|
4. Does `@opencode-ai/sdk/v2` work against 1.18.16 exactly? Generated from the same surface, but
|
||||||
|
unverified by running it.
|
||||||
|
5. When did `/api/*` first appear in the 1.x line, and when does the legacy surface actually go? Both
|
||||||
|
UNKNOWN — the published changelog names no `/api/` additions at all.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Sources
|
||||||
|
|
||||||
|
- Live: `GET /doc` on `opencode serve` 1.18.16 (162 paths, 51 under `/api/`), plus the probes recorded
|
||||||
|
above against the local sidecar's serve on port 49698.
|
||||||
|
- Code: `src/servers/sidecar/opencode/*`, `src/servers/api/chat/opencode/*`, `opencode-sessions.ts`,
|
||||||
|
`list-models.ts`, `send-opencode.ts`.
|
||||||
|
- Upstream: opencode.ai/v2/docs/migrate-v1, opencode.ai/v2/docs, opencode.ai/docs/server,
|
||||||
|
github.com/anomalyco/opencode (formerly sst/opencode), npm `@opencode-ai/sdk` 1.18.16,
|
||||||
|
`@opencode-ai/client@next`.
|
||||||
|
- Prior art in this repo: `docs/opencode-parity.md`, `-fork-decision.md`, `-serve-migration-plan.md`,
|
||||||
|
`-serve-path.md`, `-testing-checklist.md`, `-phase0-review.md`, `-phase1-report.md`,
|
||||||
|
`-phase1-review.md`.
|
||||||
Reference in New Issue
Block a user