@khalilgharbaoui/opencode-claude-code-plugin 0.11.2 → 0.12.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/README.md CHANGED
@@ -70,17 +70,17 @@ The plugin auto-registers the following. They appear in the model picker without
70
70
 
71
71
  | ID | Display name | Context | Output | Reasoning variants | Price × |
72
72
  |---|---|---|---|---|---|
73
- | `claude-haiku-4-5` | Claude Haiku 4.5 | 200k | 8,192 | – | 1× |
74
- | `claude-sonnet-4-5` | Claude Sonnet 4.5 | 1M | 16,384 | low/medium/high/xhigh/max | 3× |
75
- | `claude-sonnet-4-6` | Claude Sonnet 4.6 | 1M | 16,384 | low/medium/high/xhigh/max | 3× |
73
+ | `claude-haiku-4-5` | Claude Haiku 4.5 | 200k | 64,000 | – | 1× |
74
+ | `claude-sonnet-4-5` | Claude Sonnet 4.5 | 200k | 64,000 | low/medium/high/xhigh/max | 3× |
75
+ | `claude-sonnet-4-6` | Claude Sonnet 4.6 | 1M | 128,000 | low/medium/high/xhigh/max | 3× |
76
76
  | `claude-sonnet-5` | Claude Sonnet 5 | 1M | 128,000 | low/medium/high/xhigh/max | 2×* |
77
- | `claude-opus-4-5` | Claude Opus 4.5 | 1M | 16,384 | low/medium/high/xhigh/max | 5× |
78
- | `claude-opus-4-6` | Claude Opus 4.6 | 1M | 16,384 | low/medium/high/xhigh/max | 5× |
79
- | `claude-opus-4-7` | Claude Opus 4.7 | 1M | 16,384 | low/medium/high/xhigh/max | 5× |
80
- | `claude-opus-4-8` | Claude Opus 4.8 | 1M | 16,384 | low/medium/high/xhigh/max | 5× |
77
+ | `claude-opus-4-5` | Claude Opus 4.5 | 200k | 64,000 | low/medium/high/xhigh/max | 5× |
78
+ | `claude-opus-4-6` | Claude Opus 4.6 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
79
+ | `claude-opus-4-7` | Claude Opus 4.7 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
80
+ | `claude-opus-4-8` | Claude Opus 4.8 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
81
81
  | `claude-opus-5` | Claude Opus 5 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
82
- | `claude-fable-5` | Claude Fable 5 | 1M | 16,384 | low/medium/high/xhigh/max | 10× |
83
- | `claude-mythos-5` | Claude Mythos 5 | 1M | 16,384 | low/medium/high/xhigh/max | 10× |
82
+ | `claude-fable-5` | Claude Fable 5 | 1M | 128,000 | low/medium/high/xhigh/max | 10× |
83
+ | `claude-mythos-5` | Claude Mythos 5 | 1M | 128,000 | low/medium/high/xhigh/max | 10× |
84
84
 
85
85
  `claude-mythos-5` is Mythos-class like Fable 5 but without safety classifiers, and is **limited availability via [Project Glasswing](https://anthropic.com/glasswing)**. It's registered unconditionally; if your Claude account lacks access, `claude --model claude-mythos-5` just errors. Use `claude-fable-5` (generally available) otherwise.
86
86
 
@@ -271,6 +271,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
271
271
  | `"Write"` | `Write` | `mcp__opencode_proxy__write` |
272
272
  | `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
273
273
  | `"Task"` | `Agent` | `mcp__opencode_proxy__task` |
274
+ | `"Question"` | `AskUserQuestion` | `mcp__opencode_proxy__question` |
274
275
 
275
276
  ### OpenCode-native subagents
276
277
 
@@ -295,7 +296,7 @@ recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
295
296
  process at spawn, and provider options are read once at opencode startup, so
296
297
  `proxyTools` changes need a full opencode restart.
297
298
 
298
- Only those five values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI.
299
+ Only those six values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI. The `"Question"` proxy is version-gated on opencode's built-in `question` tool: on builds that lack the registry entry the def is silently dropped (a forwarded call would otherwise render as `⚙ invalid`), so add it only on opencode versions that ship the `question` tool.
299
300
 
300
301
  Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled and Claude orchestrates subagents internally with no opencode child-session visibility. To opt out of all proxying, including Task, use an explicit empty list:
301
302
 
@@ -303,6 +304,48 @@ Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled a
303
304
  "options": { "proxyTools": [] }
304
305
  ```
305
306
 
307
+ ### Subagent todos
308
+
309
+ When Claude works through a multi-step task it emits `TaskCreate` / `TaskUpdate` calls. The plugin translates those into opencode's full-list `todowrite` so the todo panel populates. Inside a **subagent** that translation is blocked unless you say otherwise: opencode's task tool injects `todowrite: false` into the tools dict for any subagent without an explicit rule, so the plugin's synthetic emissions surface as `⚙ invalid todowrite` rows instead of todos. The built-in `general` subagent denies it by default.
310
+
311
+ Grant it per subagent definition in `opencode.json`:
312
+
313
+ ```json
314
+ {
315
+ "agent": {
316
+ "multistep": {
317
+ "description": "Multi-step worker whose progress should be visible as todos",
318
+ "mode": "subagent",
319
+ "model": "claude-code-default/claude-opus-5",
320
+ "permission": {
321
+ "todowrite": "allow",
322
+ "todoread": "allow",
323
+ "task": "deny"
324
+ }
325
+ }
326
+ }
327
+ }
328
+ ```
329
+
330
+ Notes on that example:
331
+
332
+ - `todowrite: "allow"` is the load-bearing line. Without it you get `⚙ invalid` rows, not a broken run.
333
+ - `todoread` is worth allowing too so the subagent can re-read its own list across turns.
334
+ - `task: "deny"` is explicit rather than implied. Leave it denied unless this subagent should itself delegate, in which case set `"allow"` and raise the top-level `subagent_depth` (opencode defaults it to `1`, so a child cannot spawn a grandchild).
335
+ - Provider and agent config are read at startup, so restart opencode fully after editing.
336
+
337
+ The todos render in the **subagent's own session view**, not the parent's panel. Navigate to it in the TUI with `session.child.next` (and back with `session.parent`); run `opencode --print-logs` or check the keybindings if those actions are unbound in your setup.
338
+
339
+ To confirm the data actually landed rather than trusting the UI:
340
+
341
+ ```bash
342
+ sqlite3 ~/.local/share/opencode/opencode.db \
343
+ "select id, parent_id from session order by rowid desc limit 5;"
344
+ # then, with the child session id:
345
+ sqlite3 ~/.local/share/opencode/opencode.db \
346
+ "select tool, state from part where session_id='<child-id>' and tool='todowrite';"
347
+ ```
348
+
306
349
  ### What you get with proxying on
307
350
 
308
351
  - opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call (the default `claude --dangerously-skip-permissions` is NOT applied to proxied tools).
@@ -411,7 +454,29 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
411
454
 
412
455
  ## AskUserQuestion
413
456
 
414
- opencode has no native structured ask-question executor to proxy through (unlike `Bash`/`Task`), so the plugin handles `AskUserQuestion` specially:
457
+ opencode ships a built-in `question` tool (`packages/opencode/src/tool/question.ts`) that renders a real TUI form with options and a custom-answer field — near-identical to Claude Code's `AskUserQuestion` (`multiSelect` `multiple`). The plugin can route `AskUserQuestion` through it so the prompt becomes an actual form instead of plain text. Two modes:
458
+
459
+ ### With `"Question"` in `proxyTools` (currently blocked upstream — leave it off)
460
+
461
+ > **Known upstream breakage (opencode 1.15.x through at least 1.18.5).** opencode's `question` TUI form does not render, so the tool blocks until you interrupt the turn. This is not specific to this plugin: native providers hit it identically, and a `--pure` headless server drives the same question end to end successfully (`question.asked` → `GET /question` → `POST /question/{id}/reply` → tool completes), which isolates the fault to the TUI. Tracked upstream as [anomalyco/opencode#36604](https://github.com/anomalyco/opencode/issues/36604) with fix [PR #36603](https://github.com/anomalyco/opencode/pull/36603) (unmerged). Until that lands, enabling `"Question"` trades the working fallback below for a hang. The instructions here describe the intended behavior for when it is fixed.
462
+
463
+ Add `"Question"` to `proxyTools` and grant `permission.question: allow` to the calling agent. Claude's built-in `AskUserQuestion` is disabled via `--disallowedTools`, and the plugin exposes `mcp__opencode_proxy__question` in its place. The model calls the proxy, opencode renders the form, and the operator's answers come back as arrays of selected labels. On builds that lack the `question` registry entry the def is silently dropped at spawn (version gate), and the deny/markdown fallback below applies instead.
464
+
465
+ `proxyTools` replaces the default list rather than adding to it, so repeat the defaults you still want:
466
+
467
+ ```json
468
+ "options": {
469
+ "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task", "Question"]
470
+ }
471
+ ```
472
+
473
+ To turn it back off, drop `"Question"` from the list. It is **not** in the default list, so no configuration means the deny/markdown fallback below stays in force.
474
+
475
+ The same spawn-time caveat as `"Task"` applies: provider options are read once at opencode startup, so restart opencode fully after adding it. Question calls get a 30-minute proxy deadline (raise it with `proxyToolTimeoutMs` if you expect to be AFK longer; an expired call comes back as an error, not an answer).
476
+
477
+ ### Without the proxy (default fallback)
478
+
479
+ When `"Question"` is not in `proxyTools` (or the opencode version lacks the `question` tool), the plugin handles `AskUserQuestion` as follows:
415
480
 
416
481
  1. **It renders the full question.** The tool's payload — every question, header, option label, and option description — is emitted as readable markdown into the assistant stream so the user actually sees the choices (same approach as `ExitPlanMode`).
417
482
  2. **It is never auto-allowed at the CLI gate.** Allowing it would let the headless Claude CLI resolve its own question (no TTY → fabricated/empty answer) and proceed on a guess. `controlRequestBehaviorForTool` hard-denies `AskUserQuestion` and returns a message telling the model to **stop and wait for the operator's answer** — end the turn, call no further tools, and never self-answer. (Before v0.7.0 this message also offered an "if the run is non-interactive, proceed with a reasonable guess" fallback. The model could not reliably tell interactive opencode from a headless run and routinely took it, so questions appeared to be skipped — [issue #8](https://github.com/khalilgharbaoui/opencode-claude-code-plugin/issues/8). For genuinely unattended runs, use the `controlRequestToolBehaviors` override below instead.)
@@ -483,7 +548,7 @@ The plugin respects the standard Claude Code thinking env vars. If you set them
483
548
 
484
549
  - **Empty text blocks are dropped.** Claude sometimes opens a `content_block_start` for text but never sends a delta. The plugin no longer emits the empty block (which was triggering Anthropic 400s like `cache_control cannot be set for empty text blocks`).
485
550
  - **Smart incomplete-turn continuation.** By default, the plugin keeps the current opencode stream open and feeds Claude CLI a small internal continuation message when Claude emits a `result` after reasoning/tool activity without a useful visible answer. It still stops normally on final-looking answers, questions, blockers, errors, aborts, or internal safety-budget exhaustion. Disable with `"autoContinueIncompleteTurns": false`.
486
- - **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
551
+ - **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call — unless `"Question"` is in `proxyTools`, in which case it is routed through opencode's native `question` tool (see [AskUserQuestion](#askuserquestion)).
487
552
  - **Wire-inactivity watchdog.** Once the CLI has produced any content, the stream closes gracefully if stdout goes silent for 60 seconds without a `result` message arriving. Resets on every line received, so long mid-turn pauses (Sonnet between text-end and the next tool_use, for example) are tolerated. On a user-initiated abort, the watchdog shortens to 5 seconds.
488
553
  - **Per-iteration usage.** When the CLI internally retries with tools, the plugin only counts the last iteration's usage so opencode's context accounting stays accurate.
489
554
  - **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
@@ -616,8 +681,8 @@ Workaround for autonomous compression: trigger it manually with `/dcp compress`
616
681
  - No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
617
682
  - Raw chain-of-thought is not available. Claude 4 family models ship summarized thinking only. See [Extended thinking](#extended-thinking) for the full picture.
618
683
  - Recommended Claude Code CLI: **2.1.142+**. Older CLIs work for everything else but skip the `--thinking-display` flag, so Claude Opus 4.7 turns may render empty Thinking rows. If something breaks after a Claude Code update, the CLI version is the first thing to check.
619
- - **Foreground Task calls have a 30-minute proxy timeout.** The same timeout is written into Claude's generated HTTP MCP configuration so long-running opencode subagents are not cut off by Claude's 60-second default. For independent longer work, use `background: true` after enabling opencode's experimental background-subagent flag.
620
- - **Subagent todos require explicit permission.** opencode's task tool gates `todowrite` per subagent: without a `permission: { todowrite: "allow" }` rule on the subagent definition, opencode injects `todowrite: false` into the tools dict and the plugin's synthetic `todowrite` emissions surface as `⚙ invalid todowrite` rows. The built-in `general` subagent denies `todowrite` by default; use a custom subagent for parallel work that needs todo visibility. Subagent todos render inline in the **subagent's** session view (navigate with the TUI's `session.child.next` / `session.parent` commands), not in the parent session's panel.
684
+ - **Foreground Task calls have a 60-minute proxy deadline** (configurable via [`proxyToolTimeoutMs`](#per-tool-proxy-timeouts)). A ceiling covering the longest configured deadline is written into Claude's generated HTTP MCP configuration so long-running opencode subagents are not cut off by Claude's 60-second default. For independent longer work, use `background: true` after enabling opencode's experimental background-subagent flag.
685
+ - **Subagent todos require explicit permission.** See [Subagent todos](#subagent-todos) for the rule and a working config.
621
686
 
622
687
  ---
623
688
 
package/dist/index.d.ts CHANGED
@@ -258,16 +258,22 @@ interface ClaudeCodeProviderSettings {
258
258
  * opencode's tool executor (with its native permission UI) and returns
259
259
  * the result.
260
260
  *
261
- * Supported: `bash`, `write`, `edit`, `webfetch`, `task`. Leave empty or unset to disable proxying.
262
- *
263
- * `task` proxies Claude CLI's `Agent` (subagent dispatch) tool through
264
- * opencode's `task` tool, so subagent calls run under opencode's
265
- * configured subagent set (build/general/custom) with opencode's
266
- * permission and lifecycle handling, instead of Claude CLI's
267
- * internal-only general-purpose / Explore / Plan options. The calling
268
- * agent must have `permission.task: allow` for the target subagent
269
- * (see opencode's agent docs).
270
- */
261
+ * Supported: `bash`, `write`, `edit`, `webfetch`, `task`, `question`. Leave empty or unset to disable proxying.
262
+ *
263
+ * `task` proxies Claude CLI's `Agent` (subagent dispatch) tool through
264
+ * opencode's `task` tool, so subagent calls run under opencode's
265
+ * configured subagent set (build/general/custom) with opencode's
266
+ * permission and lifecycle handling, instead of Claude CLI's
267
+ * internal-only general-purpose / Explore / Plan options. The calling
268
+ * agent must have `permission.task: allow` for the target subagent
269
+ * (see opencode's agent docs).
270
+ *
271
+ * `question` proxies Claude CLI's `AskUserQuestion` through opencode's
272
+ * native `question` tool (TUI form with options + custom answer). The
273
+ * calling agent must have `permission.question: allow`. Version-gated:
274
+ * silently dropped on opencode builds that lack the `question` registry
275
+ * entry, in which case the deny/markdown fallback applies.
276
+ */
271
277
  proxyTools?: string[];
272
278
  /**
273
279
  * Per-tool proxy call timeouts in milliseconds, keyed by the proxy tool
@@ -506,16 +512,22 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
506
512
  */
507
513
  private resolvedProxyMcpTools;
508
514
  /**
509
- * Live description of opencode's `task` tool for the current
510
- * provider/model, exactly as opencode's registry renders it for native
511
- * models including the "Available agent types" list (built from the
512
- * default agent's permissions). Overlaid onto the static `task` proxy
513
- * def so Claude sees the same subagent catalog native opencode models
514
- * see, instead of hunting through config files. Returns undefined when
515
- * the SDK client is unavailable (direct AI-SDK use, tests) so the
516
- * static def stands.
515
+ * Live tool info derived from a single `client.tool.list()` fetch:
516
+ *
517
+ * - `taskDescription`: opencode's `task` tool description exactly as the
518
+ * registry renders it for native models, including the "Available
519
+ * agent types" list. Overlaid onto the static `task` proxy def so
520
+ * Claude sees the same subagent catalog native models see, instead
521
+ * of hunting through config files.
522
+ * - `questionDescription` / `hasQuestion`: opencode's `question` tool
523
+ * description and whether the registry has the entry at all. Older
524
+ * builds lack it, in which case a `mcp__opencode_proxy__question`
525
+ * call resolves to `⚙ invalid`; the version gate drops the def.
526
+ *
527
+ * Returns undefined/false when the SDK client is unavailable (direct
528
+ * AI-SDK use, tests) so the static defs stand.
517
529
  */
518
- private fetchLiveTaskDescription;
530
+ private fetchLiveToolInfo;
519
531
  /**
520
532
  * Create a proxy MCP server for a single active Claude process/session.
521
533
  * The process lifecycle owns the server lifecycle via session-manager.
package/dist/index.js CHANGED
@@ -2071,8 +2071,10 @@ var SERVER_NAME = "opencode_proxy";
2071
2071
  var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
2072
2072
  var PROXY_DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
2073
2073
  var PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS = {
2074
- task: 60 * 60 * 1e3
2074
+ task: 60 * 60 * 1e3,
2075
2075
  // 60 min
2076
+ question: 30 * 60 * 1e3
2077
+ // 30 min
2076
2078
  };
2077
2079
  var MAX_PROXY_TIMEOUT_MS = 2 ** 31 - 1;
2078
2080
  function resolveProxyCallTimeoutMs(toolName, input, overrides) {
@@ -2120,6 +2122,7 @@ function buildProxyTimeoutError(toolName, ms) {
2120
2122
  var TASK_PROXY_NOTE = "This is the ONLY tool that dispatches opencode subagents (including user @-mentions). Claude Code's built-in TaskCreate/TaskUpdate manage a local todo list and cannot dispatch subagents. Do not search config files to verify a subagent type exists \u2014 invalid types fail fast with a clear error. Foreground calls block until the subagent finishes; set `background` to request opencode's background execution mode. Task calls get a 60-minute proxy deadline by default (configurable via proxyToolTimeoutMs).";
2121
2123
  var AGENT_TYPES_HEADING = "Available agent types";
2122
2124
  var AGENT_BLURB_LIMIT = 140;
2125
+ var QUESTION_PROXY_NOTE = "This routes structured questions through opencode's native `question` tool, which renders a TUI form with the options you provide and blocks until the operator answers. Claude Code's built-in AskUserQuestion is disabled in this environment; this proxy is the ONLY way to ask the operator for a decision or clarification. Answers come back as arrays of selected labels (set `multiple: true` to allow more than one). If the operator dismisses the form the call returns an error \u2014 treat that as 'no answer' and stop, do not guess. Question calls get a 30-minute proxy deadline by default (configurable via proxyToolTimeoutMs); for long-AFK scenarios prefer fewer, high-signal questions.";
2123
2126
  function extractAgentTypeList(liveDescription) {
2124
2127
  const live = liveDescription?.trim();
2125
2128
  if (!live) return void 0;
@@ -2148,6 +2151,19 @@ function overlayTaskProxyDescription(tools, liveDescription) {
2148
2151
  ${t.description}` } : t
2149
2152
  );
2150
2153
  }
2154
+ function overlayQuestionProxyDescription(tools, liveDescription) {
2155
+ const live = liveDescription?.trim();
2156
+ if (!live) return tools;
2157
+ return tools.map(
2158
+ (t) => t.name === "question" ? { ...t, description: `${live}
2159
+
2160
+ ${QUESTION_PROXY_NOTE}` } : t
2161
+ );
2162
+ }
2163
+ function filterQuestionProxyByOpencodeSupport(tools, opencodeHasQuestion) {
2164
+ if (opencodeHasQuestion) return tools;
2165
+ return tools.filter((t) => t.name !== "question");
2166
+ }
2151
2167
  var DEFAULT_PROXY_TOOLS = [
2152
2168
  {
2153
2169
  name: "bash",
@@ -2271,6 +2287,56 @@ var DEFAULT_PROXY_TOOLS = [
2271
2287
  },
2272
2288
  required: ["description", "prompt", "subagent_type"]
2273
2289
  }
2290
+ },
2291
+ {
2292
+ name: "question",
2293
+ description: "Ask the operator structured questions with options and receive their answers back. Routed through opencode's native `question` tool so the prompt renders as a real TUI form (with options and a custom-answer field) instead of a plain text turn. Use this when you need a decision, clarification, or preference from the operator mid-task. " + QUESTION_PROXY_NOTE,
2294
+ inputSchema: {
2295
+ type: "object",
2296
+ properties: {
2297
+ questions: {
2298
+ type: "array",
2299
+ description: "Questions to ask.",
2300
+ items: {
2301
+ type: "object",
2302
+ properties: {
2303
+ question: {
2304
+ type: "string",
2305
+ description: "Complete question."
2306
+ },
2307
+ header: {
2308
+ type: "string",
2309
+ description: "Very short label (max 30 chars)."
2310
+ },
2311
+ options: {
2312
+ type: "array",
2313
+ description: "Available choices.",
2314
+ items: {
2315
+ type: "object",
2316
+ properties: {
2317
+ label: {
2318
+ type: "string",
2319
+ description: "Display text (1-5 words, concise)."
2320
+ },
2321
+ description: {
2322
+ type: "string",
2323
+ description: "Explanation of choice."
2324
+ }
2325
+ },
2326
+ required: ["label", "description"]
2327
+ }
2328
+ },
2329
+ multiple: {
2330
+ type: "boolean",
2331
+ description: "Allow selecting multiple choices. Defaults to false."
2332
+ }
2333
+ },
2334
+ required: ["question", "header", "options"]
2335
+ }
2336
+ }
2337
+ },
2338
+ required: ["questions"]
2339
+ }
2274
2340
  }
2275
2341
  ];
2276
2342
  async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverrides) {
@@ -2388,23 +2454,14 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverride
2388
2454
  if (timer) clearTimeout(timer);
2389
2455
  pending.delete(callId);
2390
2456
  });
2391
- if (result.kind === "error") {
2392
- writeJson(res, {
2393
- jsonrpc: "2.0",
2394
- id: requestId,
2395
- result: {
2396
- content: [{ type: "text", text: result.message }],
2397
- isError: true
2398
- }
2399
- });
2400
- return;
2401
- }
2457
+ const text = result.kind === "error" ? result.message : result.text;
2458
+ const isError = result.kind === "error" || result.isError === true;
2402
2459
  writeJson(res, {
2403
2460
  jsonrpc: "2.0",
2404
2461
  id: requestId,
2405
2462
  result: {
2406
- content: [{ type: "text", text: result.text }],
2407
- isError: result.isError === true
2463
+ content: [{ type: "text", text }],
2464
+ isError
2408
2465
  }
2409
2466
  });
2410
2467
  return;
@@ -2532,7 +2589,13 @@ function disallowedToolFlags(tools) {
2532
2589
  glob: ["Glob"],
2533
2590
  grep: ["Grep"],
2534
2591
  webfetch: ["WebFetch"],
2535
- task: ["Agent"]
2592
+ task: ["Agent"],
2593
+ // `question` disables Claude Code's built-in `AskUserQuestion` so the
2594
+ // structured-questions path flows through opencode's native `question`
2595
+ // tool instead — same UI/permission/audit benefits as the other
2596
+ // proxies. Without this, the model can call both and the two paths
2597
+ // diverge (opencode's form vs the headless deny-and-render fallback).
2598
+ question: ["AskUserQuestion"]
2536
2599
  };
2537
2600
  const out = [];
2538
2601
  const seen = /* @__PURE__ */ new Set();
@@ -2923,6 +2986,14 @@ Subagent dispatch in this environment goes through exactly one tool: \`mcp__open
2923
2986
  - If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__task\`), then call it.
2924
2987
  - Claude Code's built-in TaskCreate/TaskUpdate/TaskList manage a local todo list. They cannot dispatch subagents; creating a task there runs nothing. Never report a subagent as dispatched unless \`mcp__opencode_proxy__task\` returned its result.
2925
2988
  - Do not verify a subagent's existence by searching config files \u2014 the tool's description lists the available agent types, and invalid types fail fast with a clear error.`;
2989
+ var QUESTION_PROXY_HINT = `## Asking the operator questions
2990
+
2991
+ Structured questions in this environment go through exactly one tool: \`mcp__opencode_proxy__question\`.
2992
+
2993
+ - When you need to ask the operator a question with options, call \`mcp__opencode_proxy__question\` with a \`questions\` array (each item has \`question\`, \`header\`, \`options\` of \`{label, description}\`, and optional \`multiple\`).
2994
+ - If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__question\`), then call it by its FULL name.
2995
+ - Do NOT call bare \`question\` \u2014 that is not a tool. Always use the full \`mcp__opencode_proxy__question\` name when invoking it.
2996
+ - Claude Code's built-in \`AskUserQuestion\` is disabled in this environment; the proxy is the only way to ask structured questions.`;
2926
2997
  var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
2927
2998
 
2928
2999
  You are running via the Claude Code CLI (not a direct API call). This affects context management:
@@ -3098,22 +3169,33 @@ var ClaudeCodeLanguageModel = class {
3098
3169
  return out.length > 0 ? out : null;
3099
3170
  }
3100
3171
  /**
3101
- * Live description of opencode's `task` tool for the current
3102
- * provider/model, exactly as opencode's registry renders it for native
3103
- * models including the "Available agent types" list (built from the
3104
- * default agent's permissions). Overlaid onto the static `task` proxy
3105
- * def so Claude sees the same subagent catalog native opencode models
3106
- * see, instead of hunting through config files. Returns undefined when
3107
- * the SDK client is unavailable (direct AI-SDK use, tests) so the
3108
- * static def stands.
3172
+ * Live tool info derived from a single `client.tool.list()` fetch:
3173
+ *
3174
+ * - `taskDescription`: opencode's `task` tool description exactly as the
3175
+ * registry renders it for native models, including the "Available
3176
+ * agent types" list. Overlaid onto the static `task` proxy def so
3177
+ * Claude sees the same subagent catalog native models see, instead
3178
+ * of hunting through config files.
3179
+ * - `questionDescription` / `hasQuestion`: opencode's `question` tool
3180
+ * description and whether the registry has the entry at all. Older
3181
+ * builds lack it, in which case a `mcp__opencode_proxy__question`
3182
+ * call resolves to `⚙ invalid`; the version gate drops the def.
3183
+ *
3184
+ * Returns undefined/false when the SDK client is unavailable (direct
3185
+ * AI-SDK use, tests) so the static defs stand.
3109
3186
  */
3110
- async fetchLiveTaskDescription() {
3187
+ async fetchLiveToolInfo() {
3111
3188
  const items = await fetchOpencodeToolList(
3112
3189
  this.config.provider,
3113
3190
  this.modelId,
3114
3191
  this.config.cwd
3115
3192
  );
3116
- return items?.find((item) => item.id === "task")?.description || void 0;
3193
+ const question = items?.find((item) => item.id === "question");
3194
+ return {
3195
+ taskDescription: items?.find((item) => item.id === "task")?.description,
3196
+ questionDescription: question?.description,
3197
+ hasQuestion: !!question
3198
+ };
3117
3199
  }
3118
3200
  /**
3119
3201
  * Create a proxy MCP server for a single active Claude process/session.
@@ -3987,26 +4069,52 @@ ${plan}
3987
4069
  );
3988
4070
  const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
3989
4071
  const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
4072
+ const questionProxyEnabled = resolvedProxy?.some((t) => t.name === "question") ?? false;
4073
+ const liveToolInfo = taskProxyEnabled || questionProxyEnabled ? await self.fetchLiveToolInfo() : {
4074
+ taskDescription: void 0,
4075
+ questionDescription: void 0,
4076
+ hasQuestion: false
4077
+ };
3990
4078
  let enrichedProxy = resolvedProxy;
3991
- if (resolvedProxy && taskProxyEnabled) {
3992
- const liveTaskDescription = await self.fetchLiveTaskDescription();
4079
+ if (enrichedProxy && taskProxyEnabled) {
3993
4080
  enrichedProxy = overlayTaskProxyDescription(
3994
- resolvedProxy,
3995
- liveTaskDescription
4081
+ enrichedProxy,
4082
+ liveToolInfo.taskDescription
3996
4083
  );
3997
4084
  log.info("task proxy description overlay", {
3998
- applied: Boolean(liveTaskDescription),
3999
- liveDescriptionLength: liveTaskDescription?.length ?? 0,
4085
+ applied: Boolean(liveToolInfo.taskDescription),
4086
+ liveDescriptionLength: liveToolInfo.taskDescription?.length ?? 0,
4000
4087
  listsAgentTypes: Boolean(
4001
- liveTaskDescription?.includes("Available agent types")
4088
+ liveToolInfo.taskDescription?.includes(
4089
+ "Available agent types"
4090
+ )
4002
4091
  )
4003
4092
  });
4004
4093
  }
4005
- const combinedProxyTools = enrichedProxy || proxyMcpTools ? [...enrichedProxy ?? [], ...proxyMcpTools ?? []] : null;
4094
+ if (enrichedProxy && questionProxyEnabled) {
4095
+ enrichedProxy = overlayQuestionProxyDescription(
4096
+ enrichedProxy,
4097
+ liveToolInfo.hasQuestion ? liveToolInfo.questionDescription : void 0
4098
+ );
4099
+ enrichedProxy = filterQuestionProxyByOpencodeSupport(
4100
+ enrichedProxy,
4101
+ liveToolInfo.hasQuestion
4102
+ );
4103
+ log.info("question proxy version gate", {
4104
+ opencodeHasQuestion: liveToolInfo.hasQuestion,
4105
+ kept: liveToolInfo.hasQuestion
4106
+ });
4107
+ }
4108
+ const combinedList = [
4109
+ ...enrichedProxy ?? [],
4110
+ ...proxyMcpTools ?? []
4111
+ ];
4112
+ const combinedProxyTools = combinedList.length > 0 ? combinedList : null;
4006
4113
  if (!proxyServer && combinedProxyTools) {
4007
4114
  proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
4008
4115
  }
4009
- const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
4116
+ const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
4117
+ const proxyDisallowed = enrichedProxy ? disallowedToolFlags(enrichedProxy) : [];
4010
4118
  const extraDisallowed = [];
4011
4119
  if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
4012
4120
  const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
@@ -4021,7 +4129,8 @@ ${plan}
4021
4129
  self.config.multiStepContinuation !== false,
4022
4130
  [
4023
4131
  ...extractSystemMessages(options.prompt),
4024
- ...taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : []
4132
+ ...taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : [],
4133
+ ...questionProxyActive ? [QUESTION_PROXY_HINT] : []
4025
4134
  ]
4026
4135
  );
4027
4136
  cliArgs = buildCliArgs({
@@ -5238,21 +5347,21 @@ var defaultModels = {
5238
5347
  family: "haiku",
5239
5348
  reasoning: false,
5240
5349
  context: 2e5,
5241
- output: 8192,
5350
+ output: 64e3,
5242
5351
  cost: haikuCost,
5243
5352
  multiplier: 1,
5244
- releaseDate: "2024-10-22"
5353
+ releaseDate: "2025-10-01"
5245
5354
  }),
5246
5355
  "claude-sonnet-4-5": defineModel({
5247
5356
  id: "claude-sonnet-4-5",
5248
5357
  name: "Claude Sonnet 4.5",
5249
5358
  family: "sonnet",
5250
5359
  reasoning: true,
5251
- context: 1e6,
5252
- output: 16384,
5360
+ context: 2e5,
5361
+ output: 64e3,
5253
5362
  cost: sonnetCost,
5254
5363
  multiplier: 3,
5255
- releaseDate: "2025-04-14"
5364
+ releaseDate: "2025-09-29"
5256
5365
  }),
5257
5366
  "claude-sonnet-4-6": defineModel({
5258
5367
  id: "claude-sonnet-4-6",
@@ -5260,7 +5369,7 @@ var defaultModels = {
5260
5369
  family: "sonnet",
5261
5370
  reasoning: true,
5262
5371
  context: 1e6,
5263
- output: 16384,
5372
+ output: 128e3,
5264
5373
  cost: sonnetCost,
5265
5374
  multiplier: 3,
5266
5375
  releaseDate: "2025-06-19"
@@ -5281,11 +5390,11 @@ var defaultModels = {
5281
5390
  name: "Claude Opus 4.5",
5282
5391
  family: "opus",
5283
5392
  reasoning: true,
5284
- context: 1e6,
5285
- output: 16384,
5393
+ context: 2e5,
5394
+ output: 64e3,
5286
5395
  cost: opusCost,
5287
5396
  multiplier: 5,
5288
- releaseDate: "2025-04-14"
5397
+ releaseDate: "2025-11-01"
5289
5398
  }),
5290
5399
  "claude-opus-4-6": defineModel({
5291
5400
  id: "claude-opus-4-6",
@@ -5293,7 +5402,7 @@ var defaultModels = {
5293
5402
  family: "opus",
5294
5403
  reasoning: true,
5295
5404
  context: 1e6,
5296
- output: 16384,
5405
+ output: 128e3,
5297
5406
  cost: opusCost,
5298
5407
  multiplier: 5,
5299
5408
  releaseDate: "2025-06-19"
@@ -5304,7 +5413,7 @@ var defaultModels = {
5304
5413
  family: "opus",
5305
5414
  reasoning: true,
5306
5415
  context: 1e6,
5307
- output: 16384,
5416
+ output: 128e3,
5308
5417
  cost: opusCost,
5309
5418
  multiplier: 5,
5310
5419
  releaseDate: "2025-07-16"
@@ -5315,7 +5424,7 @@ var defaultModels = {
5315
5424
  family: "opus",
5316
5425
  reasoning: true,
5317
5426
  context: 1e6,
5318
- output: 16384,
5427
+ output: 128e3,
5319
5428
  cost: opusCost,
5320
5429
  multiplier: 5,
5321
5430
  releaseDate: "2026-05-28"
@@ -5337,7 +5446,7 @@ var defaultModels = {
5337
5446
  family: "fable",
5338
5447
  reasoning: true,
5339
5448
  context: 1e6,
5340
- output: 16384,
5449
+ output: 128e3,
5341
5450
  cost: fableCost,
5342
5451
  multiplier: 10,
5343
5452
  releaseDate: "2026-06-09"
@@ -5352,7 +5461,7 @@ var defaultModels = {
5352
5461
  family: "mythos",
5353
5462
  reasoning: true,
5354
5463
  context: 1e6,
5355
- output: 16384,
5464
+ output: 128e3,
5356
5465
  cost: fableCost,
5357
5466
  multiplier: 10,
5358
5467
  releaseDate: "2026-06-09"