@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 +79 -14
- package/dist/index.d.ts +31 -19
- package/dist/index.js +158 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 |
|
|
74
|
-
| `claude-sonnet-4-5` | Claude Sonnet 4.5 |
|
|
75
|
-
| `claude-sonnet-4-6` | Claude Sonnet 4.6 | 1M |
|
|
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 |
|
|
78
|
-
| `claude-opus-4-6` | Claude Opus 4.6 | 1M |
|
|
79
|
-
| `claude-opus-4-7` | Claude Opus 4.7 | 1M |
|
|
80
|
-
| `claude-opus-4-8` | Claude Opus 4.8 | 1M |
|
|
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 |
|
|
83
|
-
| `claude-mythos-5` | Claude Mythos 5 | 1M |
|
|
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
|
|
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
|
|
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
|
|
620
|
-
- **Subagent todos require explicit permission.**
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
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
|
|
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
|
-
|
|
2392
|
-
|
|
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
|
|
2407
|
-
isError
|
|
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
|
|
3102
|
-
*
|
|
3103
|
-
*
|
|
3104
|
-
*
|
|
3105
|
-
*
|
|
3106
|
-
*
|
|
3107
|
-
*
|
|
3108
|
-
*
|
|
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
|
|
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
|
-
|
|
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 (
|
|
3992
|
-
const liveTaskDescription = await self.fetchLiveTaskDescription();
|
|
4079
|
+
if (enrichedProxy && taskProxyEnabled) {
|
|
3993
4080
|
enrichedProxy = overlayTaskProxyDescription(
|
|
3994
|
-
|
|
3995
|
-
|
|
4081
|
+
enrichedProxy,
|
|
4082
|
+
liveToolInfo.taskDescription
|
|
3996
4083
|
);
|
|
3997
4084
|
log.info("task proxy description overlay", {
|
|
3998
|
-
applied: Boolean(
|
|
3999
|
-
liveDescriptionLength:
|
|
4085
|
+
applied: Boolean(liveToolInfo.taskDescription),
|
|
4086
|
+
liveDescriptionLength: liveToolInfo.taskDescription?.length ?? 0,
|
|
4000
4087
|
listsAgentTypes: Boolean(
|
|
4001
|
-
|
|
4088
|
+
liveToolInfo.taskDescription?.includes(
|
|
4089
|
+
"Available agent types"
|
|
4090
|
+
)
|
|
4002
4091
|
)
|
|
4003
4092
|
});
|
|
4004
4093
|
}
|
|
4005
|
-
|
|
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
|
|
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:
|
|
5350
|
+
output: 64e3,
|
|
5242
5351
|
cost: haikuCost,
|
|
5243
5352
|
multiplier: 1,
|
|
5244
|
-
releaseDate: "
|
|
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:
|
|
5252
|
-
output:
|
|
5360
|
+
context: 2e5,
|
|
5361
|
+
output: 64e3,
|
|
5253
5362
|
cost: sonnetCost,
|
|
5254
5363
|
multiplier: 3,
|
|
5255
|
-
releaseDate: "2025-
|
|
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:
|
|
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:
|
|
5285
|
-
output:
|
|
5393
|
+
context: 2e5,
|
|
5394
|
+
output: 64e3,
|
|
5286
5395
|
cost: opusCost,
|
|
5287
5396
|
multiplier: 5,
|
|
5288
|
-
releaseDate: "2025-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
5464
|
+
output: 128e3,
|
|
5356
5465
|
cost: fableCost,
|
|
5357
5466
|
multiplier: 10,
|
|
5358
5467
|
releaseDate: "2026-06-09"
|