@khalilgharbaoui/opencode-claude-code-plugin 0.14.1 → 0.15.1
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 +89 -3
- package/dist/index.d.ts +49 -1
- package/dist/index.js +2376 -1687
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -176,6 +176,66 @@ CLAUDE_CONFIG_DIR="$HOME/.claude-work" claude auth login
|
|
|
176
176
|
|
|
177
177
|
The account model IDs are internally suffixed, for example `claude-sonnet-4-6@work`, so long-lived Claude subprocess sessions do not collide across accounts. The generated wrapper strips the suffix before calling `claude --model`.
|
|
178
178
|
|
|
179
|
+
### Subagents: your account, their model
|
|
180
|
+
|
|
181
|
+
opencode's agent config cannot express "inherit the account, choose the model". A subagent that omits `model` inherits the invoking agent's whole model string; one that pins `model` inherits neither half, so pinning Opus also pins whichever account was written into it. This plugin closes that gap, because it is the piece that knows the account is the *provider* while the model is only a `--model` flag.
|
|
182
|
+
|
|
183
|
+
Write an agent markdown file. Nothing goes in `opencode.json`.
|
|
184
|
+
|
|
185
|
+
```markdown
|
|
186
|
+
---
|
|
187
|
+
description: Designs and builds UI work
|
|
188
|
+
mode: subagent
|
|
189
|
+
---
|
|
190
|
+
You are a designer...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`@designer` now runs on **the account of the session that invoked it**, on whatever model you point it at. Which model comes from one of two places.
|
|
194
|
+
|
|
195
|
+
Per agent, in the agent's own file:
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
forceModel: claude-haiku-4-5
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Or once, for every subagent that pins nothing, in the provider options:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{ "provider": { "claude-code": { "options": { "defaultSubagentModel": "claude-opus-5" } } } }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The rules, in order:
|
|
208
|
+
|
|
209
|
+
| The agent | Runs on |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `forceModel: <id>` | the caller's account, that model |
|
|
212
|
+
| `mode: subagent`, no model, `defaultSubagentModel` set | the caller's account, that model |
|
|
213
|
+
| `mode: subagent`, no model, no default set | untouched, inherits the caller's model |
|
|
214
|
+
| `model: <provider>/<id>` | exactly that, account and all (untouched) |
|
|
215
|
+
| anything opencode ships (`explore`, `general`, `compaction`) | untouched |
|
|
216
|
+
|
|
217
|
+
**`defaultSubagentModel` is unset by default and nothing is overridden without it.** That is deliberate: this feature rewrites what the model picker said would run, so an existing setup that upgrades the plugin has to behave exactly as it did before. Built-ins are excluded for the same reason, since forcing Opus onto a cheap exploration agent would be an expensive surprise nobody asked for. An unknown model id is refused and the original kept, rather than spawning the CLI with a `--model` it will reject.
|
|
218
|
+
|
|
219
|
+
Two things worth knowing. The overridden model is part of the Claude session key, so a subagent forced to Opus never shares a `claude` process with a Fable parent in the same directory. And opencode still prices the turn against the model *it* routed, so a cost readout attributes the work to the caller's model, not the one that actually ran.
|
|
220
|
+
|
|
221
|
+
### The effort an agent runs at
|
|
222
|
+
|
|
223
|
+
The same file can state its own thinking budget:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
reasoningEffort: high
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
That beats whatever effort the call arrived with. It has to, because opencode resolves one effort for a session and a subagent inherits it, which is wrong in the expensive direction: a caller who picked `max` for their own turn otherwise hands `max` to every worker it dispatches, and a mechanical lane burns a weekly cap at the costliest setting available. Model and effort together are what a turn costs, so both belong with the agent rather than with whoever happened to dispatch it.
|
|
230
|
+
|
|
231
|
+
An agent that declares nothing keeps the inherited effort, so this changes nothing until a file asks for it. An unrecognised level is refused and the inherited one kept, since the CLI rejects a level it does not know. Compaction is exempt: its summary always gets the full budget.
|
|
232
|
+
|
|
233
|
+
To force an **account** rather than a model, pin the full string. Both halves are needed, because the provider selects the account's config dir and the `@account` marker is what the model was registered under for that provider:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
model: claude-code-appical/claude-opus-5@appical
|
|
237
|
+
```
|
|
238
|
+
|
|
179
239
|
### Options reference
|
|
180
240
|
|
|
181
241
|
```json
|
|
@@ -439,6 +499,10 @@ Every proxied tool call has a deadline: if opencode hasn't resolved it (run the
|
|
|
439
499
|
|
|
440
500
|
The `task` and `question` defaults are deliberately generous. Subagents routinely run 20–40 min, and a question can sit on a slow operator; under the old flat 10-minute ceiling the proxy fired mid-call, Claude believed its dispatch had failed, and the subagent's eventual result was dropped (the parent turn had already ended on the timeout error). If a `task` call *does* time out, the error tells Claude not to "schedule a wake-up" — that is a Claude Code affordance which cannot fire in this headless/proxy context, so deferring silently loses the work.
|
|
441
501
|
|
|
502
|
+
Starting with 0.15.0, clients advertising SSE receive immediate headers and keepalive comments every 15 seconds while a proxy call runs. This prevents long unanswered HTTP requests from being abandoned before the configured tool deadline; JSON-only clients retain their existing response format. Keepalives do not extend the tool deadline.
|
|
503
|
+
|
|
504
|
+
If Claude nevertheless abandons the HTTP call, the plugin preserves narration emitted while opencode was running the tool, renders it on return, and delivers the late completion as a plain-text continuation naming the original call. It tells Claude not to run the tool again. A silent post-tool continuation gets one resumed-process retry, preserving the original model, account, effort, and proxy configuration; a second failure ends with an error rather than an indefinite hang. Buffered narration is capped at 500 lines and 2 MiB, with a warning if output was dropped.
|
|
505
|
+
|
|
442
506
|
```json
|
|
443
507
|
"options": {
|
|
444
508
|
"proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task"],
|
|
@@ -448,6 +512,25 @@ The `task` and `question` defaults are deliberately generous. Subagents routinel
|
|
|
448
512
|
|
|
449
513
|
---
|
|
450
514
|
|
|
515
|
+
## Side questions with /btw
|
|
516
|
+
|
|
517
|
+
After a normal Claude Code turn, use:
|
|
518
|
+
|
|
519
|
+
```text
|
|
520
|
+
/btw Why did you choose that approach?
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
The plugin registers the command without replacing an existing user-defined `btw` command. It calls Claude Code's native `side_question` control protocol on the current process, using the same model and account. The answer renders in the opencode conversation, but neither the question nor answer is sent as a normal Claude user turn or included in plugin-generated history and compaction transcripts.
|
|
524
|
+
|
|
525
|
+
- Requires Claude Code CLI **2.1.258 or newer**, the oldest verified version.
|
|
526
|
+
- Requires an existing, idle **headless** session with the same model and effort. Send a normal message first if the process has not started or was evicted. Interactive transport is not supported.
|
|
527
|
+
- This is not a concurrent TUI overlay: opencode may queue the command while a turn runs, and the plugin refuses it while a tool or another aside is outstanding.
|
|
528
|
+
- Each aside sees the main conversation, not previous aside exchanges. Include the relevant detail explicitly when asking a follow-up.
|
|
529
|
+
- The control response has no token/cost usage fields. Aside usage is not reported in opencode's counters; this does not mean the request is free.
|
|
530
|
+
- A request times out after two minutes. Abort and timeout cancel that side request without killing the main session.
|
|
531
|
+
|
|
532
|
+
Fully restart opencode after upgrading to load the command and runtime changes. Other providers do not gain Claude's native side-question behavior from this command.
|
|
533
|
+
|
|
451
534
|
## WebSearch routing
|
|
452
535
|
|
|
453
536
|
Claude Code ships a built-in `WebSearch` tool. The `webSearch` option controls who actually executes those calls:
|
|
@@ -618,16 +701,19 @@ The plugin forwards Claude's thinking blocks (`thinking_delta` stream events) to
|
|
|
618
701
|
|
|
619
702
|
What you see is a **summary** of the model's thinking, not the raw chain-of-thought. Anthropic [stopped exposing raw thinking on the Claude 4 family](https://platform.claude.com/docs/en/build-with-claude/extended-thinking#summarized-thinking) and ships a server-generated digest instead. For Claude Opus 4.7 specifically, [thinking content is omitted from responses by default](https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default); the plugin opts back in by passing `--thinking-display summarized` on every spawn. Claude Code CLI 2.1.142+ is required for that flag to take effect; older CLIs skip it silently.
|
|
620
703
|
|
|
621
|
-
### Reasoning effort
|
|
704
|
+
### Reasoning effort
|
|
705
|
+
|
|
706
|
+
Each model exposes `low` / `medium` / `high` / `xhigh` / `max` variants, and an agent can set `reasoningEffort` in its own frontmatter (`minimal` is also accepted and maps to the CLI's `low`). The plugin hands the level to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn, which Claude Code treats as the session-wide override: it beats the `effortLevel` in that account's `settings.json` and a shell export of the same variable. Effort is fixed for the life of a `claude` process, so it is part of the session key. Changing effort retires the previous effort's process and remembered transcript ID before replaying the conversation into a fresh process. Switching back cannot resume stale context; same-effort streaming turns still reuse their process. This reset is scoped to the same directory, model, provider/account, agent, and conversation. If the previous effort still has pending work (including tool results, plan approval, recovery, or `/btw`), the switch is rejected: finish that work at its original effort first. Title, compaction, and `/btw` calls do not trigger effort resets.
|
|
622
707
|
|
|
623
|
-
|
|
708
|
+
Earlier versions injected a thinking keyword such as `(ultrathink)` into the user message instead. Claude Code stopped recognising every keyword except `ultrathink`, so that path is gone and nothing is appended to your messages any more. Compaction skips request and agent effort overrides, but still inherits a shell-level `CLAUDE_CODE_EFFORT_LEVEL` when set.
|
|
624
709
|
|
|
625
710
|
### Env-var overrides
|
|
626
711
|
|
|
627
|
-
The plugin respects the standard Claude Code thinking env vars. If you set them in your shell, they pass through to the spawned process untouched.
|
|
712
|
+
The plugin respects the standard Claude Code thinking env vars. If you set them in your shell, they pass through to the spawned process untouched, with the one exception in the first row.
|
|
628
713
|
|
|
629
714
|
| Env var | Effect |
|
|
630
715
|
|---|---|
|
|
716
|
+
| `CLAUDE_CODE_EFFORT_LEVEL=<level>` | Session effort override. Passes through when no effort was requested; a variant or an agent's `reasoningEffort` replaces it for that spawn. |
|
|
631
717
|
| `CLAUDE_CODE_DISABLE_THINKING=1` | Disable thinking entirely. |
|
|
632
718
|
| `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` | Disable adaptive thinking only. |
|
|
633
719
|
| `CLAUDE_CODE_SHOW_THINKING_SUMMARIES=0` | Suppress summaries (the plugin sets this to `1` by default when unset). |
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,14 @@ type OpenCodeProvider = {
|
|
|
62
62
|
models: Record<string, OpenCodeModel>;
|
|
63
63
|
};
|
|
64
64
|
type OpenCodeConfig = {
|
|
65
|
+
command?: Record<string, {
|
|
66
|
+
template: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
agent?: string;
|
|
69
|
+
model?: string;
|
|
70
|
+
variant?: string;
|
|
71
|
+
subtask?: boolean;
|
|
72
|
+
}>;
|
|
65
73
|
provider?: Record<string, {
|
|
66
74
|
name?: string;
|
|
67
75
|
npm?: string;
|
|
@@ -69,6 +77,7 @@ type OpenCodeConfig = {
|
|
|
69
77
|
options?: Record<string, unknown>;
|
|
70
78
|
models?: Record<string, unknown>;
|
|
71
79
|
}>;
|
|
80
|
+
agent?: Record<string, Record<string, unknown>>;
|
|
72
81
|
};
|
|
73
82
|
/**
|
|
74
83
|
* Bus events surface to plugins. Shape mirrors what opencode core publishes
|
|
@@ -227,6 +236,12 @@ interface ClaudeCodeProviderSettings {
|
|
|
227
236
|
account?: string;
|
|
228
237
|
configDir?: string;
|
|
229
238
|
accounts?: string[];
|
|
239
|
+
/**
|
|
240
|
+
* Model that subagents run on when their own definition pins nothing.
|
|
241
|
+
* Unset means no implicit override at all, so an agent keeps inheriting the
|
|
242
|
+
* caller's model exactly as opencode intends. See `src/agent-models.ts`.
|
|
243
|
+
*/
|
|
244
|
+
defaultSubagentModel?: string;
|
|
230
245
|
skipPermissions?: boolean;
|
|
231
246
|
permissionMode?: PermissionMode;
|
|
232
247
|
mcpConfig?: string | string[];
|
|
@@ -665,6 +680,38 @@ type RuntimeMcpStatus = Record<string, string>;
|
|
|
665
680
|
*/
|
|
666
681
|
declare function bridgeOpencodeMcp(cwd: string, runtimeStatus?: RuntimeMcpStatus, excludeServers?: ReadonlySet<string>): BridgedMcp | null;
|
|
667
682
|
|
|
683
|
+
type AgentRecord = {
|
|
684
|
+
mode?: string;
|
|
685
|
+
/** A fully-qualified `provider/model` the agent pinned for itself. */
|
|
686
|
+
model?: string;
|
|
687
|
+
/** Model NAME this agent wants, on whatever account the caller is using. */
|
|
688
|
+
forceModel?: string;
|
|
689
|
+
/** Thinking budget this agent wants, whatever the caller's picker says. */
|
|
690
|
+
reasoningEffort?: string;
|
|
691
|
+
};
|
|
692
|
+
declare function getAgentRegistry(): Record<string, AgentRecord>;
|
|
693
|
+
declare function getDefaultSubagentModel(): string | undefined;
|
|
694
|
+
/**
|
|
695
|
+
* The model a request should actually spawn with.
|
|
696
|
+
*
|
|
697
|
+
* Order, first match wins:
|
|
698
|
+
* 1. The agent declared `forceModel`.
|
|
699
|
+
* 2. The agent is a discovered subagent and `defaultSubagentModel` is set.
|
|
700
|
+
* 3. Anything else: the id opencode asked for, untouched.
|
|
701
|
+
*
|
|
702
|
+
* An agent that pinned a full `provider/model` is out of scope entirely:
|
|
703
|
+
* opencode already routed the call to that provider, and second-guessing it
|
|
704
|
+
* here would silently undo a choice the user made explicitly.
|
|
705
|
+
*
|
|
706
|
+
* Fails closed. An id that is not in the model registry is refused and the
|
|
707
|
+
* original kept, because the alternative is spawning the CLI with a `--model`
|
|
708
|
+
* it will reject, on a turn someone is waiting for.
|
|
709
|
+
*/
|
|
710
|
+
declare function resolveAgentModel(agent: string | undefined, modelId: string, overrides?: {
|
|
711
|
+
records?: Record<string, AgentRecord>;
|
|
712
|
+
defaultSubagentModel?: string;
|
|
713
|
+
}): string;
|
|
714
|
+
|
|
668
715
|
declare const defaultModels: Record<string, OpenCodeModel>;
|
|
669
716
|
|
|
670
717
|
interface ClaudeCodeProvider {
|
|
@@ -673,6 +720,7 @@ interface ClaudeCodeProvider {
|
|
|
673
720
|
languageModel(modelId: string): LanguageModelV3;
|
|
674
721
|
}
|
|
675
722
|
declare const DEFAULT_PROXY_TOOL_NAMES: string[];
|
|
723
|
+
declare function registerSideQuestionCommand(config: OpenCodeConfig): void;
|
|
676
724
|
declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
|
|
677
725
|
/**
|
|
678
726
|
* Build models in OpenCode's config schema format (flat properties like
|
|
@@ -691,4 +739,4 @@ declare const _default: {
|
|
|
691
739
|
server: OpenCodePlugin;
|
|
692
740
|
};
|
|
693
741
|
|
|
694
|
-
export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels };
|
|
742
|
+
export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, getAgentRegistry, getDefaultSubagentModel, registerSideQuestionCommand, resolveAgentModel };
|