@agentproto/adapter-opencode 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/OPENCODE.md CHANGED
@@ -36,11 +36,14 @@ models:
36
36
  - openai/gpt-5
37
37
  - openai/gpt-5-mini
38
38
  - openrouter/anthropic/claude-sonnet-4-6
39
+ - opencode-go/glm-5.3
40
+ - opencode/claude-sonnet-4-6
39
41
  env:
40
42
  anthropic: ANTHROPIC_API_KEY
41
43
  openai: OPENAI_API_KEY
42
44
  openrouter: OPENROUTER_API_KEY
43
45
  opencode: OPENCODE_API_KEY
46
+ opencode-go: OPENCODE_API_KEY
44
47
  groq: GROQ_API_KEY
45
48
  capabilities:
46
49
  streaming: true
@@ -90,4 +93,37 @@ provider the operator's `models.default` is pinned to:
90
93
  | OpenAI | `OPENAI_API_KEY` |
91
94
  | OpenRouter | `OPENROUTER_API_KEY` |
92
95
  | Groq | `GROQ_API_KEY` |
93
- | OpenCode SaaS| `OPENCODE_API_KEY` |
96
+ | OpenCode Zen | `OPENCODE_API_KEY` |
97
+ | OpenCode Go | `OPENCODE_API_KEY` |
98
+
99
+ ## OpenCode Go / OpenCode Zen
100
+
101
+ OpenCode's own two hosted endpoints are first-class billing routes in the
102
+ catalog, so the generated model menu offers them directly:
103
+
104
+ | Route | Endpoint | Models | Billing |
105
+ |-------|----------|--------|---------|
106
+ | `opencode-go` | `https://opencode.ai/zen/go/v1` | 36 | Flat **OpenCode Go** subscription ($10/mo), metered against dollar caps at each model's own per-token price |
107
+ | `opencode` | `https://opencode.ai/zen/v1` | 102 | **OpenCode Zen**, pay-as-you-go (includes the whole Claude family, the gpt-5.x/codex family, Gemini, and many `-free` variants at a real $0) |
108
+
109
+ Model ids are `opencode-go/<id>` / `opencode/<id>` — opencode's own config
110
+ spelling, and exactly what goes on the wire. For these two the route IS the
111
+ id's leading segment, so there is no `@route` suffix; the runtime derives the
112
+ billing endpoint from that prefix and injects `OPENCODE_API_KEY`, the same
113
+ `modelDerivedApiKey` path the other providers use.
114
+
115
+ ```bash
116
+ agentproto auth provider set opencode-go <key> # or: opencode, for Zen
117
+ agentproto sessions start --adapter opencode --model opencode-go/glm-5.3
118
+ ```
119
+
120
+ Both routes read the **same** env var (`OPENCODE_API_KEY`) — opencode's own
121
+ convention. A Zen key and a Go key are different secrets sharing one name, so a
122
+ host that stores both can only inject one; pin the rail you mean with an auth
123
+ profile whose `endpoint` is `opencode` or `opencode-go` (`--access-profile`),
124
+ which is what the spawn-time eligibility check joins on.
125
+
126
+ Neither endpoint is a login: **Go and Zen are API keys, not OAuth**. The
127
+ adapter's two `authSubscription` surfaces remain opencode's own
128
+ `opencode auth login` flows for Claude Pro/Max and ChatGPT, untouched by these
129
+ routes.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { homedir } from 'os';
2
2
  import { join } from 'path';
3
3
  import { defineAgentCli, createAgentCliRuntime } from '@agentproto/driver-agent-cli';
4
- import { listModels } from '@agentproto/model-catalog';
4
+ import { getModelsByProvider, listModels } from '@agentproto/model-catalog';
5
5
 
6
6
  /**
7
7
  * @agentproto/adapter-opencode v0.1.0-alpha
@@ -10,14 +10,17 @@ import { listModels } from '@agentproto/model-catalog';
10
10
 
11
11
  function buildOpencodeModelMenu() {
12
12
  const supported = [
13
- { provider: "anthropic", prefix: "anthropic" },
14
- { provider: "openai", prefix: "openai" },
15
- { provider: "openrouter", prefix: "openrouter" }
13
+ { provider: "anthropic", prefix: "anthropic", router: false },
14
+ { provider: "openai", prefix: "openai", router: false },
15
+ { provider: "openrouter", prefix: "openrouter", router: false },
16
+ { provider: "opencode-go", prefix: "opencode-go", router: true },
17
+ { provider: "opencode", prefix: "opencode", router: true }
16
18
  ];
17
19
  const seen = /* @__PURE__ */ new Set();
18
20
  const out = [];
19
- for (const { provider, prefix } of supported) {
20
- for (const model of listModels({ kind: "llm", provider })) {
21
+ for (const { provider, prefix, router } of supported) {
22
+ const models = router ? getModelsByProvider(provider).filter((model) => model.kind === "llm") : listModels({ kind: "llm", provider });
23
+ for (const model of models) {
21
24
  const bareId = model.id;
22
25
  const canonicalId = bareId.includes("/") ? bareId : `${prefix}/${bareId}`;
23
26
  const id = provider === "openrouter" ? `openrouter/${bareId}` : canonicalId;
@@ -115,15 +118,22 @@ var opencode = defineAgentCli({
115
118
  default: "anthropic/claude-sonnet-4-5",
116
119
  // Generated from the shared provider catalog so the Configuration Lab /
117
120
  // harness picker shows genuinely runnable Anthropic / OpenAI / OpenRouter
118
- // models instead of a hardcoded 3-item list. Groq and OpenCode-hosted are
119
- // omitted because they are not represented in the catalog/profile system
120
- // today; the free-form `model` option and `models.env` still support them.
121
+ // / OpenCode Go / OpenCode Zen models instead of a hardcoded 3-item list.
122
+ // Groq stays omitted — it is not a billing/auth CatalogProvider, so there
123
+ // is nothing to enumerate; the free-form `model` option and `models.env`
124
+ // still support it.
121
125
  allowed: buildOpencodeModelMenu(),
122
126
  env: {
123
127
  anthropic: "ANTHROPIC_API_KEY",
124
128
  openai: "OPENAI_API_KEY",
125
129
  openrouter: "OPENROUTER_API_KEY",
130
+ // Both OpenCode endpoints read the SAME var — opencode's own convention
131
+ // (models.dev records `env: ["OPENCODE_API_KEY"]` for each), mirrored in
132
+ // the catalog's `PROVIDER_KEY_ENV`. Two DIFFERENT secrets share the name,
133
+ // so which balance a spawn bills is decided by the resolved auth profile's
134
+ // endpoint (`opencode` vs `opencode-go`), not by this map.
126
135
  opencode: "OPENCODE_API_KEY",
136
+ "opencode-go": "OPENCODE_API_KEY",
127
137
  groq: "GROQ_API_KEY"
128
138
  }
129
139
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @agentproto/adapter-opencode — AIP-45 adapter for sst/opencode.\n *\n * OpenCode ships first-party ACP support (`opencode acp`) so no\n * third-party wrapper is needed. We spawn it via `npx -y opencode-ai\n * acp` and drive it over stdio JSON-RPC the same way the claude-code\n * adapter drives @agentclientprotocol/claude-agent-acp.\n *\n * import { opencode, opencodeRuntime } from \"@agentproto/adapter-opencode\"\n * const session = await opencodeRuntime().start({\n * env: { ANTHROPIC_API_KEY: \"sk-...\" },\n * })\n * for await (const evt of session.send({ role: \"user\", content: \"...\" })) {\n * console.log(evt)\n * }\n * await session.close()\n */\n\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport {\n createAgentCliRuntime,\n defineAgentCli,\n type AgentCliHandle,\n type AgentCliRuntime,\n} from \"@agentproto/driver-agent-cli\"\nimport { listModels } from \"@agentproto/model-catalog\"\n\n/**\n * Build OpenCode's model menu from the shared provider catalog instead of a\n * hand-maintained allowlist. OpenCode routes by the model id's own\n * `<provider>/<id>` prefix, so every entry carries the billing provider the\n * runtime's eligibility projection needs.\n *\n * Only providers OpenCode genuinely supports today are included:\n * - Anthropic and OpenAI (direct vendor prefixes)\n * - OpenRouter (`openrouter/<vendor>/<id>` router prefix)\n *\n * Groq and OpenCode-hosted are omitted from the generated menu because they\n * are not represented in the shared catalog / profile system today. The\n * `models.env` map still carries their key env vars, so free-form `model`\n * overrides and manually-curated profiles continue to work.\n */\nfunction buildOpencodeModelMenu(): Array<{ id: string; provider: string }> {\n const supported = [\n { provider: \"anthropic\", prefix: \"anthropic\" },\n { provider: \"openai\", prefix: \"openai\" },\n { provider: \"openrouter\", prefix: \"openrouter\" },\n ] as const\n\n const seen = new Set<string>()\n const out: Array<{ id: string; provider: string }> = []\n\n for (const { provider, prefix } of supported) {\n for (const model of listModels({ kind: \"llm\", provider })) {\n const bareId = model.id\n const canonicalId = bareId.includes(\"/\") ? bareId : `${prefix}/${bareId}`\n const id = provider === \"openrouter\" ? `openrouter/${bareId}` : canonicalId\n if (seen.has(id)) continue\n seen.add(id)\n out.push({ id, provider })\n }\n }\n\n return out.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider)\n return a.id.localeCompare(b.id)\n })\n}\n\nexport const opencode: AgentCliHandle = defineAgentCli({\n name: \"opencode\",\n id: \"opencode\",\n description:\n \"sst/opencode — open-source coding agent with first-party ACP mode. Spawned via `npx -y opencode-ai acp` and driven over stdio JSON-RPC. Multi-provider (Anthropic / OpenAI / OpenRouter / Groq / OpenCode hosted).\",\n version: \"0.1.0\",\n bin: \"npx\",\n bin_args: [\"-y\", \"opencode-ai\", \"acp\"],\n install: [\n { method: \"npm\", package: \"opencode-ai\", global: true },\n { method: \"curl\", url: \"https://opencode.ai/install\" },\n ],\n version_check: {\n // PRESENCE probe — local binary check, not a registry query. Covers BOTH\n // install methods: the npm -g package and the curl installer both put an\n // `opencode` executable on PATH, and it runs `--version` fully offline.\n cmd: \"opencode --version\",\n parse: \"(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)\",\n range: \">=1.0.0\",\n timeout_ms: 15_000,\n },\n auth: {\n ref: \"./SECRETS.md\",\n state: {\n env: [\n \"OPENCODE_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"GROQ_API_KEY\",\n ],\n },\n },\n sandbox: \"./SANDBOX.md\",\n protocol: \"acp\",\n acp: \"./opencode-acp.ACP.md\",\n session: {\n mode: \"persistent\",\n idle_timeout_ms: 1_800_000,\n context_carryover: true,\n },\n // opencode routes by the model id's own `<provider>/<id>` prefix itself, no\n // adapter mode — unlike claude-sdk/claude-code, which need ANTHROPIC_BASE_URL\n // pre-wired by a mode. So the route falls out of the chosen model →\n // derived-from-model. The API key env var is also derived from the model's\n // provider prefix, so the runtime must know to present api-key auth on the\n // model-derived direct endpoint.\n routeSelection: \"derived-from-model\",\n modelDerivedApiKey: true,\n // opencode ships TWO native OAuth logins reached via `opencode auth login`\n // (provider selection), both stored in its own auth.json — the runtime\n // declares one provider-scoped `authSubscription` surface per login (see\n // `subscriptionSurfaceFor` in `@agentproto/runtime`'s spawn-defaults.ts,\n // which resolves the matching entry for a spawn's resolved provider).\n // Both are EXTERNAL: the runtime injects no bearer — an agentproto-held\n // `sk-ant-oat…`/ChatGPT access token presented on opencode's x-api-key\n // channel is rejected upstream as an invalid key, so the ONLY working\n // subscription path is the CLI's own login. Each entry verifies its own\n // login is present (fail-loud, via the `opencode` provision recipe's\n // `anthropic-oauth`/`openai-oauth` methods) and scrubs the matching\n // api-key var so a leftover ANTHROPIC_API_KEY/OPENAI_API_KEY can't\n // override it.\n // - anthropic: \"Claude Pro/Max\" OAuth login (browser/headless).\n // - openai: \"ChatGPT Pro/Plus\" OAuth login (browser/headless, against\n // auth.openai.com). Reverse-engineered from the shipped binary (no\n // OSS source available for this build): opencode keys BOTH the\n // ChatGPT OAuth login and the plain API-key credential for this\n // provider under the SAME auth.json key `openai` (there is no\n // separate \"chatgpt\" key) — the generic `Cli.providers.login` /\n // `Auth.set(provider.id, …)` write path is provider-id-keyed, not\n // method-keyed. See the `opencode` provision recipe's `openai-oauth`\n // method docblock for the full trace.\n // Each is scoped to its own `provider`, so neither ever lights up the\n // other's (or openrouter/groq's) models.\n authSubscription: [\n { external: true, provider: \"anthropic\" },\n { external: true, provider: \"openai\" },\n ],\n models: {\n // Default to a canonical catalog model (claude-sonnet-4-5). The legacy\n // alias `claude-sonnet-4-6` still resolves to the same model, but the\n // generated menu uses canonical ids from the shared catalog.\n default: \"anthropic/claude-sonnet-4-5\",\n // Generated from the shared provider catalog so the Configuration Lab /\n // harness picker shows genuinely runnable Anthropic / OpenAI / OpenRouter\n // models instead of a hardcoded 3-item list. Groq and OpenCode-hosted are\n // omitted because they are not represented in the catalog/profile system\n // today; the free-form `model` option and `models.env` still support them.\n allowed: buildOpencodeModelMenu(),\n env: {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n opencode: \"OPENCODE_API_KEY\",\n groq: \"GROQ_API_KEY\",\n },\n },\n capabilities: {\n streaming: true,\n tool_calls: true,\n sub_agents: false,\n file_io: true,\n // OpenCode forwards ACP image content blocks to the underlying\n // provider (Anthropic Messages API for Claude models, OpenAI vision\n // for GPT-5). Hosts SHOULD send `{type: \"image\", data, mimeType}`\n // blocks alongside text in `session.send`.\n multimodal: true,\n // OpenCode persists session state internally and the ACP server\n // implements newSession/loadSession/resumeSession. Pair with the\n // `native-resume` continuation strategy for cold-start reattach.\n resumable: true,\n bidirectional: true,\n },\n // No manifest `modes[]`: opencode's operation profiles (default / plan /\n // build) are POSTURE, which no longer lives in the manifest (SPEC §3.4a).\n // opencode's own ACP server already advertises these as native session modes\n // and switches them on the wire via `session/set_config_option`\n // (configId:\"mode\") / `session/set_mode` — precisely the harness ACP mode\n // registry (`SessionModeState.availableModes`) posture is now sourced from,\n // so declaring them here would only duplicate the harness's own truth. route\n // comes from the model catalog and opencode has no context mode, so the array\n // would be empty — omit it.\n options: [\n {\n id: \"model\",\n type: \"string\",\n description:\n \"Provider/model override for this operator binding (e.g. `openrouter/anthropic/claude-sonnet-4-6`). Applied via ACP \" +\n \"session/set_config_option after the session is created (see \" +\n \"`models.apply`, default \\\"config\\\"); no CLI flag for this exists on \" +\n \"`opencode acp`. An id the server can't resolve REFUSES the spawn \" +\n \"with the server's reason (derived-from-model guard in \" +\n \"`define-agent-cli.ts`): opencode routes AND bills by the id's own \" +\n \"provider prefix, so silently keeping the server's default model \" +\n \"would run a model (and a bill) the operator didn't ask for.\",\n },\n ],\n continuation: {\n default: \"native-resume\",\n supported: [\"native-resume\", \"pinned-session\", \"transcript\", \"none\"],\n pinned_session: {\n idle_timeout_ms: 1_800_000,\n key_scope: [\"conversation\", \"operator\"],\n },\n },\n tags: [\"opencode\", \"sst\", \"acp\", \"agent-runtime\", \"coding\"],\n})\n\nexport function opencodeRuntime(): AgentCliRuntime {\n return createAgentCliRuntime(opencode)\n}\n\n/**\n * Best-effort per-session usage reader, mirroring `readHermesUsage` in\n * `@agentproto/adapter-hermes`. OpenCode's live ACP `usage_update` event only\n * ever carries `{used, size, cost}` — no token fields exist on that wire\n * event at all (confirmed by disassembling the installed opencode acp\n * binary) — so `session_usage` can't get tokensIn/tokensOut from the live\n * stream the way it does for other adapters. OpenCode does persist full\n * token detail to its own sqlite store though (the same store\n * `exportOpenCodeSession` in `@agentproto/runtime`'s transcript-export.ts\n * reads for `sessions export --json`), so this hook re-reads it directly.\n */\nexport async function readOpenCodeUsage(\n sessionId: string,\n): Promise<{ costUsd?: number; tokensIn?: number; tokensOut?: number } | null> {\n try {\n // node:sqlite is a Node 22+ builtin. Build the specifier at runtime so the\n // bundler (esbuild/tsup) can't statically rewrite it — it strips the\n // `node:` prefix off this not-yet-recognised builtin, turning the import\n // into a missing `sqlite` package that throws and silently yields null.\n const sqliteSpecifier = [\"node\", \"sqlite\"].join(\":\")\n const { DatabaseSync } = (await import(sqliteSpecifier)) as unknown as {\n DatabaseSync: new (p: string, o?: { readOnly?: boolean }) => {\n prepare(sql: string): { get(...a: unknown[]): unknown }\n close(): void\n }\n }\n const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\")\n const dbPath = join(dataHome, \"opencode\", \"opencode.db\")\n const db = new DatabaseSync(dbPath, { readOnly: true })\n try {\n const row = db\n .prepare(\"SELECT cost, tokens_input AS ti, tokens_output AS to_ FROM session WHERE id = ?\")\n .get(sessionId) as { cost?: number; ti?: number; to_?: number } | undefined\n if (!row) return null\n return {\n ...(row.cost != null ? { costUsd: Number(row.cost) } : {}),\n ...(row.ti != null ? { tokensIn: row.ti } : {}),\n ...(row.to_ != null ? { tokensOut: row.to_ } : {}),\n }\n } finally {\n db.close()\n }\n } catch {\n return null\n }\n}\n\nexport type { AgentCliHandle, AgentCliRuntime }\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @agentproto/adapter-opencode — AIP-45 adapter for sst/opencode.\n *\n * OpenCode ships first-party ACP support (`opencode acp`) so no\n * third-party wrapper is needed. We spawn it via `npx -y opencode-ai\n * acp` and drive it over stdio JSON-RPC the same way the claude-code\n * adapter drives @agentclientprotocol/claude-agent-acp.\n *\n * import { opencode, opencodeRuntime } from \"@agentproto/adapter-opencode\"\n * const session = await opencodeRuntime().start({\n * env: { ANTHROPIC_API_KEY: \"sk-...\" },\n * })\n * for await (const evt of session.send({ role: \"user\", content: \"...\" })) {\n * console.log(evt)\n * }\n * await session.close()\n */\n\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport {\n createAgentCliRuntime,\n defineAgentCli,\n type AgentCliHandle,\n type AgentCliRuntime,\n} from \"@agentproto/driver-agent-cli\"\nimport { getModelsByProvider, listModels } from \"@agentproto/model-catalog\"\n\n/**\n * Build OpenCode's model menu from the shared provider catalog instead of a\n * hand-maintained allowlist. OpenCode routes by the model id's own\n * `<provider>/<id>` prefix, so every entry carries the billing provider the\n * runtime's eligibility projection needs.\n *\n * Only providers OpenCode genuinely supports today are included:\n * - Anthropic and OpenAI (direct vendor prefixes)\n * - OpenRouter (`openrouter/<vendor>/<id>` router prefix)\n * - OpenCode's own two hosted endpoints — `opencode-go/<id>` (the flat Go\n * subscription) and `opencode/<id>` (Zen, pay-as-you-go), both matching\n * opencode's own config spelling\n *\n * Groq is still omitted: it is not a billing/auth `CatalogProvider` (no\n * pricing generator), so there is nothing in the shared catalog to enumerate.\n * The `models.env` map carries its key env var, so a free-form `model`\n * override and a manually-curated profile continue to work. (The two OpenCode\n * endpoints USED to be omitted for the same reason — they are now first-class\n * catalog providers with their own generated route tables, which is what makes\n * them enumerable here.)\n *\n * `router: true` marks a provider whose surface lives ONLY in its generated\n * route table and is deliberately not spread into `LLM_PRICING_CATALOG` (a\n * bare `claude-sonnet-5` must keep meaning direct Anthropic, not a Zen-priced\n * route). `listModels` returns nothing for those, so they are read through\n * `getModelsByProvider`, which folds the route table in.\n */\nfunction buildOpencodeModelMenu(): Array<{ id: string; provider: string }> {\n const supported = [\n { provider: \"anthropic\", prefix: \"anthropic\", router: false },\n { provider: \"openai\", prefix: \"openai\", router: false },\n { provider: \"openrouter\", prefix: \"openrouter\", router: false },\n { provider: \"opencode-go\", prefix: \"opencode-go\", router: true },\n { provider: \"opencode\", prefix: \"opencode\", router: true },\n ] as const\n\n const seen = new Set<string>()\n const out: Array<{ id: string; provider: string }> = []\n\n for (const { provider, prefix, router } of supported) {\n const models = router\n ? getModelsByProvider(provider).filter(model => model.kind === \"llm\")\n : listModels({ kind: \"llm\", provider })\n for (const model of models) {\n const bareId = model.id\n const canonicalId = bareId.includes(\"/\") ? bareId : `${prefix}/${bareId}`\n const id = provider === \"openrouter\" ? `openrouter/${bareId}` : canonicalId\n if (seen.has(id)) continue\n seen.add(id)\n out.push({ id, provider })\n }\n }\n\n return out.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider)\n return a.id.localeCompare(b.id)\n })\n}\n\nexport const opencode: AgentCliHandle = defineAgentCli({\n name: \"opencode\",\n id: \"opencode\",\n description:\n \"sst/opencode — open-source coding agent with first-party ACP mode. Spawned via `npx -y opencode-ai acp` and driven over stdio JSON-RPC. Multi-provider (Anthropic / OpenAI / OpenRouter / Groq / OpenCode hosted).\",\n version: \"0.1.0\",\n bin: \"npx\",\n bin_args: [\"-y\", \"opencode-ai\", \"acp\"],\n install: [\n { method: \"npm\", package: \"opencode-ai\", global: true },\n { method: \"curl\", url: \"https://opencode.ai/install\" },\n ],\n version_check: {\n // PRESENCE probe — local binary check, not a registry query. Covers BOTH\n // install methods: the npm -g package and the curl installer both put an\n // `opencode` executable on PATH, and it runs `--version` fully offline.\n cmd: \"opencode --version\",\n parse: \"(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)\",\n range: \">=1.0.0\",\n timeout_ms: 15_000,\n },\n auth: {\n ref: \"./SECRETS.md\",\n state: {\n env: [\n \"OPENCODE_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"GROQ_API_KEY\",\n ],\n },\n },\n sandbox: \"./SANDBOX.md\",\n protocol: \"acp\",\n acp: \"./opencode-acp.ACP.md\",\n session: {\n mode: \"persistent\",\n idle_timeout_ms: 1_800_000,\n context_carryover: true,\n },\n // opencode routes by the model id's own `<provider>/<id>` prefix itself, no\n // adapter mode — unlike claude-sdk/claude-code, which need ANTHROPIC_BASE_URL\n // pre-wired by a mode. So the route falls out of the chosen model →\n // derived-from-model. The API key env var is also derived from the model's\n // provider prefix, so the runtime must know to present api-key auth on the\n // model-derived direct endpoint.\n routeSelection: \"derived-from-model\",\n modelDerivedApiKey: true,\n // opencode ships TWO native OAuth logins reached via `opencode auth login`\n // (provider selection), both stored in its own auth.json — the runtime\n // declares one provider-scoped `authSubscription` surface per login (see\n // `subscriptionSurfaceFor` in `@agentproto/runtime`'s spawn-defaults.ts,\n // which resolves the matching entry for a spawn's resolved provider).\n // Both are EXTERNAL: the runtime injects no bearer — an agentproto-held\n // `sk-ant-oat…`/ChatGPT access token presented on opencode's x-api-key\n // channel is rejected upstream as an invalid key, so the ONLY working\n // subscription path is the CLI's own login. Each entry verifies its own\n // login is present (fail-loud, via the `opencode` provision recipe's\n // `anthropic-oauth`/`openai-oauth` methods) and scrubs the matching\n // api-key var so a leftover ANTHROPIC_API_KEY/OPENAI_API_KEY can't\n // override it.\n // - anthropic: \"Claude Pro/Max\" OAuth login (browser/headless).\n // - openai: \"ChatGPT Pro/Plus\" OAuth login (browser/headless, against\n // auth.openai.com). Reverse-engineered from the shipped binary (no\n // OSS source available for this build): opencode keys BOTH the\n // ChatGPT OAuth login and the plain API-key credential for this\n // provider under the SAME auth.json key `openai` (there is no\n // separate \"chatgpt\" key) — the generic `Cli.providers.login` /\n // `Auth.set(provider.id, …)` write path is provider-id-keyed, not\n // method-keyed. See the `opencode` provision recipe's `openai-oauth`\n // method docblock for the full trace.\n // Each is scoped to its own `provider`, so neither ever lights up the\n // other's (or openrouter/groq's) models.\n authSubscription: [\n { external: true, provider: \"anthropic\" },\n { external: true, provider: \"openai\" },\n ],\n models: {\n // Default to a canonical catalog model (claude-sonnet-4-5). The legacy\n // alias `claude-sonnet-4-6` still resolves to the same model, but the\n // generated menu uses canonical ids from the shared catalog.\n default: \"anthropic/claude-sonnet-4-5\",\n // Generated from the shared provider catalog so the Configuration Lab /\n // harness picker shows genuinely runnable Anthropic / OpenAI / OpenRouter\n // / OpenCode Go / OpenCode Zen models instead of a hardcoded 3-item list.\n // Groq stays omitted — it is not a billing/auth CatalogProvider, so there\n // is nothing to enumerate; the free-form `model` option and `models.env`\n // still support it.\n allowed: buildOpencodeModelMenu(),\n env: {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n // Both OpenCode endpoints read the SAME var — opencode's own convention\n // (models.dev records `env: [\"OPENCODE_API_KEY\"]` for each), mirrored in\n // the catalog's `PROVIDER_KEY_ENV`. Two DIFFERENT secrets share the name,\n // so which balance a spawn bills is decided by the resolved auth profile's\n // endpoint (`opencode` vs `opencode-go`), not by this map.\n opencode: \"OPENCODE_API_KEY\",\n \"opencode-go\": \"OPENCODE_API_KEY\",\n groq: \"GROQ_API_KEY\",\n },\n },\n capabilities: {\n streaming: true,\n tool_calls: true,\n sub_agents: false,\n file_io: true,\n // OpenCode forwards ACP image content blocks to the underlying\n // provider (Anthropic Messages API for Claude models, OpenAI vision\n // for GPT-5). Hosts SHOULD send `{type: \"image\", data, mimeType}`\n // blocks alongside text in `session.send`.\n multimodal: true,\n // OpenCode persists session state internally and the ACP server\n // implements newSession/loadSession/resumeSession. Pair with the\n // `native-resume` continuation strategy for cold-start reattach.\n resumable: true,\n bidirectional: true,\n },\n // No manifest `modes[]`: opencode's operation profiles (default / plan /\n // build) are POSTURE, which no longer lives in the manifest (SPEC §3.4a).\n // opencode's own ACP server already advertises these as native session modes\n // and switches them on the wire via `session/set_config_option`\n // (configId:\"mode\") / `session/set_mode` — precisely the harness ACP mode\n // registry (`SessionModeState.availableModes`) posture is now sourced from,\n // so declaring them here would only duplicate the harness's own truth. route\n // comes from the model catalog and opencode has no context mode, so the array\n // would be empty — omit it.\n options: [\n {\n id: \"model\",\n type: \"string\",\n description:\n \"Provider/model override for this operator binding (e.g. `openrouter/anthropic/claude-sonnet-4-6`). Applied via ACP \" +\n \"session/set_config_option after the session is created (see \" +\n \"`models.apply`, default \\\"config\\\"); no CLI flag for this exists on \" +\n \"`opencode acp`. An id the server can't resolve REFUSES the spawn \" +\n \"with the server's reason (derived-from-model guard in \" +\n \"`define-agent-cli.ts`): opencode routes AND bills by the id's own \" +\n \"provider prefix, so silently keeping the server's default model \" +\n \"would run a model (and a bill) the operator didn't ask for.\",\n },\n ],\n continuation: {\n default: \"native-resume\",\n supported: [\"native-resume\", \"pinned-session\", \"transcript\", \"none\"],\n pinned_session: {\n idle_timeout_ms: 1_800_000,\n key_scope: [\"conversation\", \"operator\"],\n },\n },\n tags: [\"opencode\", \"sst\", \"acp\", \"agent-runtime\", \"coding\"],\n})\n\nexport function opencodeRuntime(): AgentCliRuntime {\n return createAgentCliRuntime(opencode)\n}\n\n/**\n * Best-effort per-session usage reader, mirroring `readHermesUsage` in\n * `@agentproto/adapter-hermes`. OpenCode's live ACP `usage_update` event only\n * ever carries `{used, size, cost}` — no token fields exist on that wire\n * event at all (confirmed by disassembling the installed opencode acp\n * binary) — so `session_usage` can't get tokensIn/tokensOut from the live\n * stream the way it does for other adapters. OpenCode does persist full\n * token detail to its own sqlite store though (the same store\n * `exportOpenCodeSession` in `@agentproto/runtime`'s transcript-export.ts\n * reads for `sessions export --json`), so this hook re-reads it directly.\n */\nexport async function readOpenCodeUsage(\n sessionId: string,\n): Promise<{ costUsd?: number; tokensIn?: number; tokensOut?: number } | null> {\n try {\n // node:sqlite is a Node 22+ builtin. Build the specifier at runtime so the\n // bundler (esbuild/tsup) can't statically rewrite it — it strips the\n // `node:` prefix off this not-yet-recognised builtin, turning the import\n // into a missing `sqlite` package that throws and silently yields null.\n const sqliteSpecifier = [\"node\", \"sqlite\"].join(\":\")\n const { DatabaseSync } = (await import(sqliteSpecifier)) as unknown as {\n DatabaseSync: new (p: string, o?: { readOnly?: boolean }) => {\n prepare(sql: string): { get(...a: unknown[]): unknown }\n close(): void\n }\n }\n const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\")\n const dbPath = join(dataHome, \"opencode\", \"opencode.db\")\n const db = new DatabaseSync(dbPath, { readOnly: true })\n try {\n const row = db\n .prepare(\"SELECT cost, tokens_input AS ti, tokens_output AS to_ FROM session WHERE id = ?\")\n .get(sessionId) as { cost?: number; ti?: number; to_?: number } | undefined\n if (!row) return null\n return {\n ...(row.cost != null ? { costUsd: Number(row.cost) } : {}),\n ...(row.ti != null ? { tokensIn: row.ti } : {}),\n ...(row.to_ != null ? { tokensOut: row.to_ } : {}),\n }\n } finally {\n db.close()\n }\n } catch {\n return null\n }\n}\n\nexport type { AgentCliHandle, AgentCliRuntime }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/adapter-opencode",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "@agentproto/adapter-opencode — AIP-45 AGENT-CLI adapter for sst/opencode. Spawns `opencode acp` (via npx -y opencode-ai) and drives OpenCode as an agentproto agent CLI over stdio JSON-RPC.",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -41,8 +41,8 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@agentproto/driver-agent-cli": "2.4.2",
45
- "@agentproto/model-catalog": "0.9.4"
44
+ "@agentproto/driver-agent-cli": "2.4.4",
45
+ "@agentproto/model-catalog": "0.10.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^25.9.5",