Repository navigation
research phase 9 w12 opencode
Status: verified against the official docs (2026-09-16) — opencode.ai
/docs,/docs/acp,/docs/config,/docs/providers. The rig E2E section at the end records what the live install confirmed. OpenCode is SST's open-source terminal coding agent (GitHubsst/opencode, npmopencode-ai, binaryopencode). This note is the ground truth the W12 implementation is built on — same role as the T1 dsh note for deepseek.
- npm package
opencode-ai(NOTopencode);npm i -g opencode-aiships a platform binary via postinstall (needs network on the machine). - Other channels exist (official install script, brew, direct download) —
we use npm only for install/upgrade/pin, same as codex. No native
self-update exception in our job table (
opencode upgradeexists upstream but the npm channel covers all three actions uniformly). - Version pinning =
npm install -g opencode-ai@<exact>; default channel taglatest. -
opencode --versionprints a semver — feeds the W1 runtime probe (first line, ≤64 chars, 5s timeout — our standard shape).
OpenCode speaks the Agent Client Protocol natively: the command is
opencode acp (official docs /docs/acp; also listed in the ACP
registry used by Zed). No third-party wrapper (contrast: claude-code rides
@agentclientprotocol/claude-agent-acp, codex rides zed-industries/codex-acp).
- Dialect: standard ACP (Zed-ecosystem shaped) — the daemon's existing
mapAcpUpdatestandard arm applies. Rig-verified end to end (§5). -
session/list-shaped native session surfaces are NOT exposed over ACP here → the chat rail stayssupported:false(honest default; the sessions handler's generic fall-through already answers that).
-
Global config:
~/.config/opencode/opencode.json(JSON; JSONC with comments accepted by opencode itself). Project configopencode.jsonwalks up to the git root; we only touch the global file. Env overrides exist (OPENCODE_CONFIG,OPENCODE_CONFIG_DIR) — we do not set them; the machine user's default layout wins. -
Config files MERGE (remote → global → env → project → managed); our writes are therefore additive blocks inside the global file.
-
Custom provider block (
provider.<id>):{ "$schema": "https://opencode.ai/config.json", "provider": { "<id>": { "npm": "@ai-sdk/openai-compatible", "name": "Display Name", "options": { "baseURL": "https://api.example.com/v1", "apiKey": "{env:VAR}" }, "models": { "<model-id>": { "name": "Model Display Name", "limit": { "context": 200000 } } } } } }-
npm— the AI SDK package driving the wire:@ai-sdk/openai-compatiblefor/v1/chat/completions,@ai-sdk/openaifor/v1/responses,@ai-sdk/anthropicfor the anthropic wire. This is a FINER axis than our W3apienum → ourPROVIDER_API_KINDmaps onto it exactly (openai-chat→ compatible,openai-responses→ openai,anthropic→ anthropic). -
options.baseURL— custom endpoint; omitted → the built-in provider default (models.dev). -
options.apiKey— variable substitution:{env:VAR}(resolved from opencode's process env — misses the user's own TUI runs) or{file:~/path}(resolved by every opencode invocation — TUI, ACP, headless). We use{file:}+ a 0600 key file so the pushed key works for ALL launch modes, not just our daemon's spawns. -
modelsis an object map (not an array); unknown models default to{}entries.limit.contextoptional (unknown → opencode treats as unlimited; we do not invent numbers).
-
-
Default model: top-level
model="provider/model-id"(alsosmall_modelfor cheap tasks — we leave that alone). -
/connectcredentials live in~/.local/share/opencode/auth.json— never read or written by us (it is the user's own auth store). -
MCP: top-level
mcpblock in opencode.json — local stdio entries (command/args/env) and remote (url) servers. This is the C3 scanner's MCP source.
Global (OPENCODE_CONFIG_DIR default) ~/.config/opencode/:
-
command/*.md— slash commands (frontmatterdescription). -
agent/*.md— custom agents (maps to oursub_agentkind). - Skills: opencode added Agent-Skills-style support (
skill/<name>/SKILL.mdin the config dir); the scanner reads it if present and skips cleanly on versions without it (dir absent).
See docs/dev/test-rig.md for rig mechanics. What the live E2E confirmed
— including two corrections to the docs-derived assumptions:
-
npm i -g opencode-ailandsopencodeon PATH;opencode --versionprints a clean semver line (probe shape OK); realpath classifiesnpm. -
opencode acpinitializes over stdio with the standard dialect; a chat turn streams through our unmodifiedmapAcpUpdate(portal + headless). -
baseURL must end with
/v1(rig-found, the session's one real bug).@ai-sdk/anthropic/@ai-sdk/openai-compatibleexpect the base to end with/v1(their defaults do) and append only the method path. Ark's/api/coding— which claude-code takes VERBATIM, it appends/v1/…itself — therefore posted to/api/coding/messagesand the gateway auth-checks BEFORE routing, so the failure reads "Unauthorized", not 404. The writer now normalizes (opencodeSdkBaseURL). Verified by curling both paths with the same key: right path 200,/v1-less 401 with the EXACT error opencode surfaced. -
The key file must be the RAW secret, no trailing newline. opencode
reads the
{file:…}target verbatim; arides the key and the gateway rejects it (46-byte key += 47 bytes = 401). -
options.apiKeyIS wired for custom providers (provider.ts:1774 — config apiKey withprovider.keyfallback); auth.json is NOT needed for our flow. (~/.local/share/opencode/auth.jsonremains the user's own/connectstore;{type:"api", key}shape, 0600 — read from source packages/opencode/src/auth/index.ts, which also documents theOPENCODE_AUTH_CONTENTenv override.) - The provider block +
{file:}key reference drive live turns on the pushed route (headlessopencode run+ a portal ACP turn both answered), and the viewer showsopencode.jsonmasked where secrets appear with the.keyfile wholesale-redacted.
- Profile install adapter (deploy axis) — opencode is runtime-managed + chatable; local-write profile installs would be a separate wave (it has no hooks surface to install into anyway — plugins are TS code).
-
Native sessions rail — no
session/listsurface over ACP here;supported:false. -
~/.local/share/opencode/auth.json— the user's own credential store; our key file is separate by design. -
JSONC tolerance — if the user's global
opencode.jsoncontains comments and failsJSON.parse, the apply-config job FAILS with an explicit error instead of guessing; hand-managed files are documented as out of bounds for the writer.