@khalilgharbaoui/opencode-claude-code-plugin 0.11.1 → 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 +93 -14
- package/dist/index.d.ts +33 -10
- package/dist/index.js +218 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
|
@@ -281,7 +282,21 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
|
|
|
281
282
|
- **Nested tasks:** current opencode defaults `subagent_depth` to `1`, so a first-level child cannot launch another child. Increase top-level `subagent_depth` to permit deeper nesting, and explicitly grant `permission.task` on every subagent that should delegate; opencode otherwise adds a task deny to spawned subagent sessions.
|
|
282
283
|
- **Background:** `background: true` returns after starting the child and lets opencode notify the parent when it finishes. Current opencode requires `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` in the environment of the opencode process. Foreground is the default.
|
|
283
284
|
|
|
284
|
-
|
|
285
|
+
**Steering models to it.** Headless Claude Code CLIs expose no `Agent`/`Task`
|
|
286
|
+
dispatch tool of their own (verified on 2.1.211), while they *do* expose
|
|
287
|
+
`TaskCreate` — a todo tool. So "use a subagent" requests get mis-resolved:
|
|
288
|
+
a todo appears, nothing runs, and the model may still narrate a successful
|
|
289
|
+
dispatch. Two spawn-time countermeasures prevent that. The plugin injects
|
|
290
|
+
opencode's live agent-type list into the `task` proxy description (so the model
|
|
291
|
+
picks a real `subagent_type` instead of guessing a Claude Code name like
|
|
292
|
+
`general-purpose`, and doesn't grep configs to check a subagent exists), and
|
|
293
|
+
appends a system-prompt note naming
|
|
294
|
+
`mcp__opencode_proxy__task` as the only dispatch path — with the ToolSearch
|
|
295
|
+
recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
|
|
296
|
+
process at spawn, and provider options are read once at opencode startup, so
|
|
297
|
+
`proxyTools` changes need a full opencode restart.
|
|
298
|
+
|
|
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.
|
|
285
300
|
|
|
286
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:
|
|
287
302
|
|
|
@@ -289,6 +304,48 @@ Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled a
|
|
|
289
304
|
"options": { "proxyTools": [] }
|
|
290
305
|
```
|
|
291
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
|
+
|
|
292
349
|
### What you get with proxying on
|
|
293
350
|
|
|
294
351
|
- opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call (the default `claude --dangerously-skip-permissions` is NOT applied to proxied tools).
|
|
@@ -397,7 +454,29 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
|
|
|
397
454
|
|
|
398
455
|
## AskUserQuestion
|
|
399
456
|
|
|
400
|
-
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:
|
|
401
480
|
|
|
402
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`).
|
|
403
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.)
|
|
@@ -469,7 +548,7 @@ The plugin respects the standard Claude Code thinking env vars. If you set them
|
|
|
469
548
|
|
|
470
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`).
|
|
471
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`.
|
|
472
|
-
- **`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)).
|
|
473
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.
|
|
474
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.
|
|
475
554
|
- **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
|
|
@@ -602,8 +681,8 @@ Workaround for autonomous compression: trigger it manually with `/dcp compress`
|
|
|
602
681
|
- No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
|
|
603
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.
|
|
604
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.
|
|
605
|
-
- **Foreground Task calls have a
|
|
606
|
-
- **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.
|
|
607
686
|
|
|
608
687
|
---
|
|
609
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
|
|
@@ -505,6 +511,23 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
505
511
|
* client is unavailable, or no MCP servers are configured.
|
|
506
512
|
*/
|
|
507
513
|
private resolvedProxyMcpTools;
|
|
514
|
+
/**
|
|
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.
|
|
529
|
+
*/
|
|
530
|
+
private fetchLiveToolInfo;
|
|
508
531
|
/**
|
|
509
532
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
510
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) {
|
|
@@ -2117,6 +2119,51 @@ function buildProxyTimeoutError(toolName, ms) {
|
|
|
2117
2119
|
}
|
|
2118
2120
|
return new Error(base);
|
|
2119
2121
|
}
|
|
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).";
|
|
2123
|
+
var AGENT_TYPES_HEADING = "Available agent types";
|
|
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.";
|
|
2126
|
+
function extractAgentTypeList(liveDescription) {
|
|
2127
|
+
const live = liveDescription?.trim();
|
|
2128
|
+
if (!live) return void 0;
|
|
2129
|
+
const start = live.indexOf(AGENT_TYPES_HEADING);
|
|
2130
|
+
if (start === -1) return void 0;
|
|
2131
|
+
const entries = [];
|
|
2132
|
+
for (const raw of live.slice(start).split("\n")) {
|
|
2133
|
+
const match = /^-\s*([^:]+):\s*(.+)$/.exec(raw.trim());
|
|
2134
|
+
if (!match) continue;
|
|
2135
|
+
const name = match[1].trim();
|
|
2136
|
+
const blurb = match[2].trim();
|
|
2137
|
+
entries.push(
|
|
2138
|
+
`- ${name}: ${blurb.length > AGENT_BLURB_LIMIT ? `${blurb.slice(0, AGENT_BLURB_LIMIT).trimEnd()}\u2026` : blurb}`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
if (entries.length === 0) return void 0;
|
|
2142
|
+
return `Valid subagent_type values, from opencode's live registry \u2014 anything else fails:
|
|
2143
|
+
${entries.join("\n")}`;
|
|
2144
|
+
}
|
|
2145
|
+
function overlayTaskProxyDescription(tools, liveDescription) {
|
|
2146
|
+
const agentTypes = extractAgentTypeList(liveDescription);
|
|
2147
|
+
if (!agentTypes) return tools;
|
|
2148
|
+
return tools.map(
|
|
2149
|
+
(t) => t.name === "task" ? { ...t, description: `${agentTypes}
|
|
2150
|
+
|
|
2151
|
+
${t.description}` } : t
|
|
2152
|
+
);
|
|
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
|
+
}
|
|
2120
2167
|
var DEFAULT_PROXY_TOOLS = [
|
|
2121
2168
|
{
|
|
2122
2169
|
name: "bash",
|
|
@@ -2209,7 +2256,7 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
2209
2256
|
},
|
|
2210
2257
|
{
|
|
2211
2258
|
name: "task",
|
|
2212
|
-
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json).
|
|
2259
|
+
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json). " + TASK_PROXY_NOTE,
|
|
2213
2260
|
inputSchema: {
|
|
2214
2261
|
type: "object",
|
|
2215
2262
|
properties: {
|
|
@@ -2240,6 +2287,56 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
2240
2287
|
},
|
|
2241
2288
|
required: ["description", "prompt", "subagent_type"]
|
|
2242
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
|
+
}
|
|
2243
2340
|
}
|
|
2244
2341
|
];
|
|
2245
2342
|
async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverrides) {
|
|
@@ -2357,23 +2454,14 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverride
|
|
|
2357
2454
|
if (timer) clearTimeout(timer);
|
|
2358
2455
|
pending.delete(callId);
|
|
2359
2456
|
});
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
jsonrpc: "2.0",
|
|
2363
|
-
id: requestId,
|
|
2364
|
-
result: {
|
|
2365
|
-
content: [{ type: "text", text: result.message }],
|
|
2366
|
-
isError: true
|
|
2367
|
-
}
|
|
2368
|
-
});
|
|
2369
|
-
return;
|
|
2370
|
-
}
|
|
2457
|
+
const text = result.kind === "error" ? result.message : result.text;
|
|
2458
|
+
const isError = result.kind === "error" || result.isError === true;
|
|
2371
2459
|
writeJson(res, {
|
|
2372
2460
|
jsonrpc: "2.0",
|
|
2373
2461
|
id: requestId,
|
|
2374
2462
|
result: {
|
|
2375
|
-
content: [{ type: "text", text
|
|
2376
|
-
isError
|
|
2463
|
+
content: [{ type: "text", text }],
|
|
2464
|
+
isError
|
|
2377
2465
|
}
|
|
2378
2466
|
});
|
|
2379
2467
|
return;
|
|
@@ -2501,7 +2589,13 @@ function disallowedToolFlags(tools) {
|
|
|
2501
2589
|
glob: ["Glob"],
|
|
2502
2590
|
grep: ["Grep"],
|
|
2503
2591
|
webfetch: ["WebFetch"],
|
|
2504
|
-
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"]
|
|
2505
2599
|
};
|
|
2506
2600
|
const out = [];
|
|
2507
2601
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2884,6 +2978,22 @@ than pausing for user confirmation between subtasks. End the turn only
|
|
|
2884
2978
|
when the task is done, you need clarification on intent, or you hit a real
|
|
2885
2979
|
blocker. The user can interrupt or abort at any time; turn endings should
|
|
2886
2980
|
mark meaningful checkpoints, not every completed substep.`;
|
|
2981
|
+
var SUBAGENT_DISPATCH_HINT = `## opencode subagents
|
|
2982
|
+
|
|
2983
|
+
Subagent dispatch in this environment goes through exactly one tool: \`mcp__opencode_proxy__task\`.
|
|
2984
|
+
|
|
2985
|
+
- When the user mentions \`@<agent>\` or an instruction says "call the task tool with subagent: <name>", call \`mcp__opencode_proxy__task\` with \`subagent_type: "<name>"\`.
|
|
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.
|
|
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.
|
|
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.`;
|
|
2887
2997
|
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
2888
2998
|
|
|
2889
2999
|
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
@@ -3058,6 +3168,35 @@ var ClaudeCodeLanguageModel = class {
|
|
|
3058
3168
|
}
|
|
3059
3169
|
return out.length > 0 ? out : null;
|
|
3060
3170
|
}
|
|
3171
|
+
/**
|
|
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.
|
|
3186
|
+
*/
|
|
3187
|
+
async fetchLiveToolInfo() {
|
|
3188
|
+
const items = await fetchOpencodeToolList(
|
|
3189
|
+
this.config.provider,
|
|
3190
|
+
this.modelId,
|
|
3191
|
+
this.config.cwd
|
|
3192
|
+
);
|
|
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
|
+
};
|
|
3199
|
+
}
|
|
3061
3200
|
/**
|
|
3062
3201
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
3063
3202
|
* The process lifecycle owns the server lifecycle via session-manager.
|
|
@@ -3929,11 +4068,53 @@ ${plan}
|
|
|
3929
4068
|
discovery.allEnabledServerNames
|
|
3930
4069
|
);
|
|
3931
4070
|
const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
|
|
3932
|
-
const
|
|
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
|
+
};
|
|
4078
|
+
let enrichedProxy = resolvedProxy;
|
|
4079
|
+
if (enrichedProxy && taskProxyEnabled) {
|
|
4080
|
+
enrichedProxy = overlayTaskProxyDescription(
|
|
4081
|
+
enrichedProxy,
|
|
4082
|
+
liveToolInfo.taskDescription
|
|
4083
|
+
);
|
|
4084
|
+
log.info("task proxy description overlay", {
|
|
4085
|
+
applied: Boolean(liveToolInfo.taskDescription),
|
|
4086
|
+
liveDescriptionLength: liveToolInfo.taskDescription?.length ?? 0,
|
|
4087
|
+
listsAgentTypes: Boolean(
|
|
4088
|
+
liveToolInfo.taskDescription?.includes(
|
|
4089
|
+
"Available agent types"
|
|
4090
|
+
)
|
|
4091
|
+
)
|
|
4092
|
+
});
|
|
4093
|
+
}
|
|
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;
|
|
3933
4113
|
if (!proxyServer && combinedProxyTools) {
|
|
3934
4114
|
proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
|
|
3935
4115
|
}
|
|
3936
|
-
const
|
|
4116
|
+
const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
|
|
4117
|
+
const proxyDisallowed = enrichedProxy ? disallowedToolFlags(enrichedProxy) : [];
|
|
3937
4118
|
const extraDisallowed = [];
|
|
3938
4119
|
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
3939
4120
|
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
@@ -3946,7 +4127,11 @@ ${plan}
|
|
|
3946
4127
|
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3947
4128
|
cwd,
|
|
3948
4129
|
self.config.multiStepContinuation !== false,
|
|
3949
|
-
|
|
4130
|
+
[
|
|
4131
|
+
...extractSystemMessages(options.prompt),
|
|
4132
|
+
...taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : [],
|
|
4133
|
+
...questionProxyActive ? [QUESTION_PROXY_HINT] : []
|
|
4134
|
+
]
|
|
3950
4135
|
);
|
|
3951
4136
|
cliArgs = buildCliArgs({
|
|
3952
4137
|
sessionKey: sk,
|
|
@@ -5162,21 +5347,21 @@ var defaultModels = {
|
|
|
5162
5347
|
family: "haiku",
|
|
5163
5348
|
reasoning: false,
|
|
5164
5349
|
context: 2e5,
|
|
5165
|
-
output:
|
|
5350
|
+
output: 64e3,
|
|
5166
5351
|
cost: haikuCost,
|
|
5167
5352
|
multiplier: 1,
|
|
5168
|
-
releaseDate: "
|
|
5353
|
+
releaseDate: "2025-10-01"
|
|
5169
5354
|
}),
|
|
5170
5355
|
"claude-sonnet-4-5": defineModel({
|
|
5171
5356
|
id: "claude-sonnet-4-5",
|
|
5172
5357
|
name: "Claude Sonnet 4.5",
|
|
5173
5358
|
family: "sonnet",
|
|
5174
5359
|
reasoning: true,
|
|
5175
|
-
context:
|
|
5176
|
-
output:
|
|
5360
|
+
context: 2e5,
|
|
5361
|
+
output: 64e3,
|
|
5177
5362
|
cost: sonnetCost,
|
|
5178
5363
|
multiplier: 3,
|
|
5179
|
-
releaseDate: "2025-
|
|
5364
|
+
releaseDate: "2025-09-29"
|
|
5180
5365
|
}),
|
|
5181
5366
|
"claude-sonnet-4-6": defineModel({
|
|
5182
5367
|
id: "claude-sonnet-4-6",
|
|
@@ -5184,7 +5369,7 @@ var defaultModels = {
|
|
|
5184
5369
|
family: "sonnet",
|
|
5185
5370
|
reasoning: true,
|
|
5186
5371
|
context: 1e6,
|
|
5187
|
-
output:
|
|
5372
|
+
output: 128e3,
|
|
5188
5373
|
cost: sonnetCost,
|
|
5189
5374
|
multiplier: 3,
|
|
5190
5375
|
releaseDate: "2025-06-19"
|
|
@@ -5205,11 +5390,11 @@ var defaultModels = {
|
|
|
5205
5390
|
name: "Claude Opus 4.5",
|
|
5206
5391
|
family: "opus",
|
|
5207
5392
|
reasoning: true,
|
|
5208
|
-
context:
|
|
5209
|
-
output:
|
|
5393
|
+
context: 2e5,
|
|
5394
|
+
output: 64e3,
|
|
5210
5395
|
cost: opusCost,
|
|
5211
5396
|
multiplier: 5,
|
|
5212
|
-
releaseDate: "2025-
|
|
5397
|
+
releaseDate: "2025-11-01"
|
|
5213
5398
|
}),
|
|
5214
5399
|
"claude-opus-4-6": defineModel({
|
|
5215
5400
|
id: "claude-opus-4-6",
|
|
@@ -5217,7 +5402,7 @@ var defaultModels = {
|
|
|
5217
5402
|
family: "opus",
|
|
5218
5403
|
reasoning: true,
|
|
5219
5404
|
context: 1e6,
|
|
5220
|
-
output:
|
|
5405
|
+
output: 128e3,
|
|
5221
5406
|
cost: opusCost,
|
|
5222
5407
|
multiplier: 5,
|
|
5223
5408
|
releaseDate: "2025-06-19"
|
|
@@ -5228,7 +5413,7 @@ var defaultModels = {
|
|
|
5228
5413
|
family: "opus",
|
|
5229
5414
|
reasoning: true,
|
|
5230
5415
|
context: 1e6,
|
|
5231
|
-
output:
|
|
5416
|
+
output: 128e3,
|
|
5232
5417
|
cost: opusCost,
|
|
5233
5418
|
multiplier: 5,
|
|
5234
5419
|
releaseDate: "2025-07-16"
|
|
@@ -5239,7 +5424,7 @@ var defaultModels = {
|
|
|
5239
5424
|
family: "opus",
|
|
5240
5425
|
reasoning: true,
|
|
5241
5426
|
context: 1e6,
|
|
5242
|
-
output:
|
|
5427
|
+
output: 128e3,
|
|
5243
5428
|
cost: opusCost,
|
|
5244
5429
|
multiplier: 5,
|
|
5245
5430
|
releaseDate: "2026-05-28"
|
|
@@ -5261,7 +5446,7 @@ var defaultModels = {
|
|
|
5261
5446
|
family: "fable",
|
|
5262
5447
|
reasoning: true,
|
|
5263
5448
|
context: 1e6,
|
|
5264
|
-
output:
|
|
5449
|
+
output: 128e3,
|
|
5265
5450
|
cost: fableCost,
|
|
5266
5451
|
multiplier: 10,
|
|
5267
5452
|
releaseDate: "2026-06-09"
|
|
@@ -5276,7 +5461,7 @@ var defaultModels = {
|
|
|
5276
5461
|
family: "mythos",
|
|
5277
5462
|
reasoning: true,
|
|
5278
5463
|
context: 1e6,
|
|
5279
|
-
output:
|
|
5464
|
+
output: 128e3,
|
|
5280
5465
|
cost: fableCost,
|
|
5281
5466
|
multiplier: 10,
|
|
5282
5467
|
releaseDate: "2026-06-09"
|