@khalilgharbaoui/opencode-claude-code-plugin 0.30.0 → 0.31.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 +13 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +156 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +15 -5
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ That package spec is the whole install. Do **not** `npm install` the package you
|
|
|
69
69
|
|
|
70
70
|
Quit opencode fully and relaunch it: plugins are loaded once, at process start, so a reload is not enough.
|
|
71
71
|
|
|
72
|
-
In the model picker you should now see a provider called **Claude Code (Default)** holding entries such as `Claude Haiku 4.5 (1×)`, `Claude Sonnet 5 (
|
|
72
|
+
In the model picker you should now see a provider called **Claude Code (Default)** holding entries such as `Claude Haiku 4.5 (1×)`, `Claude Sonnet 5.5 (2×)` and `Claude Opus 5 (5×)`. The `(N×)` suffix is each model's list price relative to Haiku; see [Models](#models). Pick one and send a message.
|
|
73
73
|
|
|
74
74
|
If the provider does not appear, if the models are there but a message fails, or if a version you just upgraded to is missing, go to [Troubleshooting](#troubleshooting). It is keyed on the first thing you see and names one check per symptom.
|
|
75
75
|
|
|
@@ -122,14 +122,15 @@ Verified live on opencode **2.0.11** with Claude Code 2.1.280: chat turns, proxi
|
|
|
122
122
|
|
|
123
123
|
## Models
|
|
124
124
|
|
|
125
|
-
The plugin auto-registers the following, and they appear in the model picker with no extra config: Haiku 4.5, Sonnet 4.5/4.6/5, Opus 4.5/4.6/4.7/4.8/5/5.5 (plus three fast-mode Opus entries), Fable 5/5.1 and Mythos 5/5.1, each except Haiku carrying `low` / `medium` / `high` / `xhigh` / `max` reasoning variants.
|
|
125
|
+
The plugin auto-registers the following, and they appear in the model picker with no extra config: Haiku 4.5, Sonnet 4.5/4.6/5/5.5, Opus 4.5/4.6/4.7/4.8/5/5.5 (plus three fast-mode Opus entries), Fable 5/5.1 and Mythos 5/5.1, each except Haiku carrying `low` / `medium` / `high` / `xhigh` / `max` reasoning variants.
|
|
126
126
|
|
|
127
127
|
| ID | Display name | Context | Output | Reasoning variants | Price × |
|
|
128
128
|
|---|---|---|---|---|---|
|
|
129
129
|
| `claude-haiku-4-5` | Claude Haiku 4.5 | 200k | 64,000 | – | 1× |
|
|
130
130
|
| `claude-sonnet-4-5` | Claude Sonnet 4.5 | 200k | 64,000 | low/medium/high/xhigh/max | 3× |
|
|
131
131
|
| `claude-sonnet-4-6` | Claude Sonnet 4.6 | 1M | 128,000 | low/medium/high/xhigh/max | 3× |
|
|
132
|
-
| `claude-sonnet-5` | Claude Sonnet 5 | 1M | 128,000 | low/medium/high/xhigh/max |
|
|
132
|
+
| `claude-sonnet-5` | Claude Sonnet 5 | 1M | 128,000 | low/medium/high/xhigh/max | 2× |
|
|
133
|
+
| `claude-sonnet-5-5` | Claude Sonnet 5.5 | 1M | 128,000 | low/medium/high/xhigh/max | 2× |
|
|
133
134
|
| `claude-opus-4-5` | Claude Opus 4.5 | 200k | 64,000 | low/medium/high/xhigh/max | 5× |
|
|
134
135
|
| `claude-opus-4-6` | Claude Opus 4.6 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
|
|
135
136
|
| `claude-opus-4-7` | Claude Opus 4.7 | 1M | 128,000 | low/medium/high/xhigh/max | 5× |
|
|
@@ -148,10 +149,12 @@ The plugin auto-registers the following, and they appear in the model picker wit
|
|
|
148
149
|
|
|
149
150
|
Capabilities for every model: text + image input, text output, tool use, attachments. No temperature control, no PDF/audio/video, no interleaved streaming.
|
|
150
151
|
|
|
151
|
-
**Price ×** is each model's per-token list price relative to Haiku, the cheapest model. It's derived exactly from Anthropic's published pricing (input and output ratios both come out the same: Haiku $1/$5 = 1×, Sonnet $3/$15 = 3×, Opus 5.5 $4/$20 = 4×, Opus $5/$25 = 5×, Opus 5.5 fast mode $8/$40 = 8×, Fable/Mythos 5 and 5.1 / Opus 5 and 4.8 fast mode $10/$50 = 10×). So **Fable/Mythos 5 and 5.1, and fast-mode Opus 5 and 4.8, all cost 2× standard Opus 5**, and fast mode is 2× the standard price on every Opus that offers it. The same multiplier is shown as a `(N×)` suffix on the display name in opencode's model picker, since opencode has no dedicated multiplier field. On a flat Max/Pro subscription it doubles as a rough guide to how fast each model drains your usage limit.
|
|
152
|
+
**Price ×** is each model's per-token list price relative to Haiku, the cheapest model. It's derived exactly from Anthropic's published pricing (input and output ratios both come out the same: Haiku $1/$5 = 1×, Sonnet 5 and 5.5 $2/$10 = 2×, Sonnet 4.5/4.6 $3/$15 = 3×, Opus 5.5 $4/$20 = 4×, Opus $5/$25 = 5×, Opus 5.5 fast mode $8/$40 = 8×, Fable/Mythos 5 and 5.1 / Opus 5 and 4.8 fast mode $10/$50 = 10×). So **Fable/Mythos 5 and 5.1, and fast-mode Opus 5 and 4.8, all cost 2× standard Opus 5**, and fast mode is 2× the standard price on every Opus that offers it. The same multiplier is shown as a `(N×)` suffix on the display name in opencode's model picker, since opencode has no dedicated multiplier field. On a flat Max/Pro subscription it doubles as a rough guide to how fast each model drains your usage limit.
|
|
152
153
|
|
|
153
154
|
Fable 5.1 and Mythos 5.1 keep the same $10/M input and $50/M output rates as 5.0, but cache reads cost $0.25/M instead of $1/M. Their cache-write rate remains $12.50/M.
|
|
154
155
|
|
|
156
|
+
Sonnet 5 and Sonnet 5.5 are $2/M input and $10/M output, with cache writes at $2.50/M and cache reads at $0.20/M. Sonnet 5's price was announced as introductory until 2026-08-31, but Anthropic cancelled the increase to $3/$15, so $2/$10 is its standard price. Sonnet 5.5 runs on any recent Claude Code, but **2.1.284 is the first release that knows it**. An older CLI still serves it, on fallback limits (a 200k context window instead of 1M, and an estimated cost). The plugin warns once when the CLI reports that, and `claude update` fixes it.
|
|
157
|
+
|
|
155
158
|
Opus 5.5 is priced below the Opus line at $4/M input and $20/M output, with cache writes at $5/M and cache reads at $0.20/M (0.05× input rather than the usual 0.1×). It needs **Claude Code 2.1.280 or newer**: the API rejects it from an older CLI with a 400 naming that floor, which shows up as a failed turn.
|
|
156
159
|
|
|
157
160
|
The model ID is passed straight through to `claude --model`, so anything Claude Code accepts works. The three `-fast` IDs are the one exception, described below.
|
|
@@ -887,6 +890,8 @@ It carries the startup-diagnostics fields (plugin version, opencode version, `cl
|
|
|
887
890
|
|
|
888
891
|
When Claude Code refused an entry in an `--mcp-config` it was handed, an **MCP config entries Claude Code skipped** section names each one with the CLI's own category and sentence. That section only appears when there is something in it. It matters because a skipped server is absent from the CLI's server list entirely rather than listed as broken, so the model silently does not have those tools; if the skipped name is `opencode_proxy` the report says so plainly, because then it is the plugin's own server and every proxied tool call in the session fails. The same thing is a warning in your terminal when it happens.
|
|
889
892
|
|
|
893
|
+
A **Plugins Claude Code did not load** section works the same way for Claude plugins: one the CLI demoted at load time (for example, a dependency that is not installed) is absent from its plugin list, so its skills, commands and MCP servers are silently missing. The skill bridge is such a plugin (`opencode-skills`), and a failure there is reported as the plugin's own bug rather than your config. A plugin warning only counts when its content did not load; advisory feedback about a plugin that did load stays in the log at INFO.
|
|
894
|
+
|
|
890
895
|
```text
|
|
891
896
|
/claude-code-doctor usage
|
|
892
897
|
```
|
|
@@ -909,7 +914,7 @@ One line at the end of a finished turn, from the numbers the CLI already reports
|
|
|
909
914
|
|
|
910
915
|
- Never on a `/compact` turn (the footer would be appended to what opencode stores as the summary) and never on a turn that ended in error, where the error is the thing to read.
|
|
911
916
|
- It is its own text part led by `▌ **stats:**`, and the plugin strips it again if the conversation is ever replayed into a fresh Claude Code process. The model never reads its own accounting.
|
|
912
|
-
- Token counts are the turn's totals, which is what matches the cost. They are deliberately not the
|
|
917
|
+
- Token counts are the turn's totals, which is what matches the cost. They are deliberately not what opencode records for the message: opencode reads that as context occupancy (its context gauge and its auto-compaction), so it gets the input and cache counts of the turn's last API call, plus the whole turn's output. opencode's own cost figure for a multi-call turn therefore counts only the last call's input and cache; the CLI's real cost is this line and `providerMetadata["claude-code"].costUsd`.
|
|
913
918
|
- The cost is what the CLI reported for the turn, not a billing guarantee.
|
|
914
919
|
|
|
915
920
|
The same numbers are logged at INFO whatever this option is set to, and `total_cost_usd`, `duration_ms`, `duration_api_ms`, `num_turns`, `usage`, `modelUsage` and `permission_denials` always reach `providerMetadata` (denials by tool name and id only, never their inputs).
|
|
@@ -1248,7 +1253,7 @@ The plugin respects the standard Claude Code thinking env vars. If you set them
|
|
|
1248
1253
|
- **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. It also resumes an answer the model was cut off mid-sentence: a `max_tokens` stop means truncation rather than completion, so the turn continues instead of ending on half a sentence, capped at 8 attempts and 10 minutes. Every other stop reason is taken at face value. Disable with `"autoContinueIncompleteTurns": false`.
|
|
1249
1254
|
- **`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)).
|
|
1250
1255
|
- **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.
|
|
1251
|
-
- **
|
|
1256
|
+
- **Context usage, not turn totals.** The CLI's `result` adds up every API call in a turn, and opencode reads a message's usage as how full the context is, so a tool-heavy turn looked several times its real size and triggered auto-compaction far below the window. The plugin reports the last API call's input and cache counts plus the turn's output instead. See [Per-turn stats](#per-turn-stats) for what that does to opencode's cost figure.
|
|
1252
1257
|
- **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
|
|
1253
1258
|
- **Variants survive merge.** opencode recalculates variant lists after the plugin loads; the plugin re-injects defaults into runtime config so your variants don't disappear.
|
|
1254
1259
|
|
|
@@ -1573,7 +1578,7 @@ This plugin absorbs work from its forks directly, cherry-picked with the origina
|
|
|
1573
1578
|
| [@galvani](https://github.com/galvani) (Jan Kozak) | Per-session working directory for `opencode serve`, so one server spawns each project's `claude` in the right place. Also found the stale `toolCallMap` re-emission three months before it was fixed here. | `9e02ce4`, `2238ed0` |
|
|
1574
1579
|
| [@HeikoAtGitHub](https://github.com/HeikoAtGitHub) | Stopped sending `AGENTS.md` to the model twice (opencode already forwards it). Independently diagnosed the 5-minute proxy wall. | `25260a4`, `42f426d` |
|
|
1575
1580
|
| [@bernardofortes](https://github.com/bernardofortes) (Bernardo Fortes) | `idleProcessTimeoutMs`, idle eviction of retained `claude` workers. | `a5f723a` |
|
|
1576
|
-
| [@broskees](https://github.com/broskees) (Joseph Roberts) | Task proxy default-on (PR #18), the abort `interrupt` so Esc really stops the CLI, the skill bridge, `task_batch` for concurrent subagents (and the measurement that the CLI serialises MCP calls), the undici 300 s diagnosis of the proxy wall, the lifecycle release of proxied calls that made the `task` deadline unnecessary (PR #36),
|
|
1581
|
+
| [@broskees](https://github.com/broskees) (Joseph Roberts) | Task proxy default-on (PR #18), the abort `interrupt` so Esc really stops the CLI, the skill bridge, `task_batch` for concurrent subagents (and the measurement that the CLI serialises MCP calls), the undici 300 s diagnosis of the proxy wall, the lifecycle release of proxied calls that made the `task` deadline unnecessary (PR #36), Claude Opus 5.5 with its fast-mode entry (PR #43), and the fix for turn-summed usage that made opencode auto-compact far below the window (PR #63). | PR #18, `68ed142`, PR #36, PR #43, PR #63 |
|
|
1577
1582
|
| [@jknlsn](https://github.com/jknlsn) (Jake Nelson) | Per-tool proxy timeouts, subagent dispatch steering, the question proxy, the start watchdog respawn. | `84f3db9`, `94980a6`, `47501d0`, `ffefc24` |
|
|
1578
1583
|
| [@CollieIsCute](https://github.com/CollieIsCute) (Collie Tsai) | The plan-mode approval bridge. | `8c5b583` |
|
|
1579
1584
|
| [@flupkede](https://github.com/flupkede) | The compress proxy tool design and the AI-SDK v4 image-part fix. | `4ac319f`, `60a6e9a` |
|
|
@@ -1581,6 +1586,7 @@ This plugin absorbs work from its forks directly, cherry-picked with the origina
|
|
|
1581
1586
|
| [@willmcginnis](https://github.com/willmcginnis) | The proxy endpoint authentication (PR #28, GHSA-3mxm-w7gf-3c5x). | PR #28 |
|
|
1582
1587
|
| [@nic-lan](https://github.com/nic-lan) | The issue #29 diagnosis of subagent output lost across the CLI resume boundary, and the fix for unattended output replaying as one text block per delta (PR #35). | #29, PR #35 |
|
|
1583
1588
|
| [@acastro2](https://github.com/acastro2) (Alexandre Castro) | Found and fixed CLI tool results being emitted under a different name than their call, which made opencode 2 abort every turn that used a Claude-side MCP server (PR #46). | PR #46 |
|
|
1589
|
+
| [@bangnh1](https://github.com/bangnh1) | Independently found and diagnosed the turn-summed usage that tripped auto-compaction after a single prompt, measured on opencode 2 (PR #62; the fix landed as PR #63). | PR #62 |
|
|
1584
1590
|
| [@JWebCoder](https://github.com/JWebCoder) (joao moura) | Diagnosed that auto-continue never fires on current CLIs (PR #15). | PR #15 |
|
|
1585
1591
|
|
|
1586
1592
|
Commit hashes are on the contributors' forks where the work was cherry-picked; `git log --author` on this repo shows the preserved authorship.
|
package/dist/index.d.ts
CHANGED
|
@@ -937,6 +937,8 @@ interface ClaudeStreamMessage {
|
|
|
937
937
|
/** On a `tool_result` block: the CLI-executed tool failed. */
|
|
938
938
|
is_error?: boolean;
|
|
939
939
|
}>;
|
|
940
|
+
/** On an `assistant` frame: that one API call's usage, not the turn's. */
|
|
941
|
+
usage?: ClaudeStreamMessage["usage"];
|
|
940
942
|
};
|
|
941
943
|
apiKeySource?: string;
|
|
942
944
|
permissionMode?: string;
|
package/dist/index.js
CHANGED
|
@@ -415,6 +415,7 @@ var DEFAULT_PROXY_TOOL_NAMES = [
|
|
|
415
415
|
"Task"
|
|
416
416
|
];
|
|
417
417
|
var PROXY_MCP_SERVER_NAME = "opencode_proxy";
|
|
418
|
+
var SKILL_PLUGIN_NAME = "opencode-skills";
|
|
418
419
|
|
|
419
420
|
// src/cli-events.ts
|
|
420
421
|
function isRecord(value) {
|
|
@@ -595,9 +596,44 @@ function parseSystemInit(msg) {
|
|
|
595
596
|
cliVersion: str(raw.claude_code_version),
|
|
596
597
|
toolCount: Array.isArray(raw.tools) ? raw.tools.length : 0,
|
|
597
598
|
mcpServers: servers,
|
|
598
|
-
mcpServerErrors: parseMcpServerErrors(msg)
|
|
599
|
+
mcpServerErrors: parseMcpServerErrors(msg),
|
|
600
|
+
pluginErrors: parsePluginDiagnostics(raw.plugin_errors),
|
|
601
|
+
pluginWarnings: parsePluginDiagnostics(raw.plugin_warnings),
|
|
602
|
+
loadedPlugins: parseLoadedPlugins(raw.plugins)
|
|
599
603
|
};
|
|
600
604
|
}
|
|
605
|
+
function parsePluginDiagnostics(raw) {
|
|
606
|
+
if (!Array.isArray(raw)) return [];
|
|
607
|
+
const diagnostics = [];
|
|
608
|
+
for (const entry of raw) {
|
|
609
|
+
if (!isRecord(entry)) continue;
|
|
610
|
+
diagnostics.push({
|
|
611
|
+
plugin: str(entry.plugin) ?? "unknown",
|
|
612
|
+
type: str(entry.type) ?? "unknown",
|
|
613
|
+
message: str(entry.message) ?? ""
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
return diagnostics;
|
|
617
|
+
}
|
|
618
|
+
function parseLoadedPlugins(raw) {
|
|
619
|
+
if (!Array.isArray(raw)) return [];
|
|
620
|
+
const loaded = [];
|
|
621
|
+
for (const entry of raw) {
|
|
622
|
+
if (!isRecord(entry)) continue;
|
|
623
|
+
for (const id of [str(entry.source), str(entry.name)]) {
|
|
624
|
+
if (id && !loaded.includes(id)) loaded.push(id);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return loaded;
|
|
628
|
+
}
|
|
629
|
+
function describePluginLoadFailure(diagnostic) {
|
|
630
|
+
const detail = diagnostic.message ? ` Claude Code said: ${diagnostic.message}` : "";
|
|
631
|
+
const name = diagnostic.plugin.split("@", 1)[0];
|
|
632
|
+
if (name === SKILL_PLUGIN_NAME) {
|
|
633
|
+
return `Claude Code did not load the plugin's own skill bridge "${diagnostic.plugin}" (${diagnostic.type}), so the opencode skills it bridges are missing this session. This is a plugin bug or a corrupted scratch directory rather than your config: report it with this line.${detail}`;
|
|
634
|
+
}
|
|
635
|
+
return `Claude Code did not load plugin "${diagnostic.plugin}" (${diagnostic.type}), so its skills, commands and MCP servers are missing this session.${detail}`;
|
|
636
|
+
}
|
|
601
637
|
function apiKeySourceWarning(apiKeySource, ignoreAnthropicApiKey) {
|
|
602
638
|
if (!apiKeySource || !API_KEY_SOURCES.has(apiKeySource)) return null;
|
|
603
639
|
const base = `Claude Code authenticated with an API key (apiKeySource: ${apiKeySource}), so these turns bill as pay-as-you-go API usage instead of your subscription.`;
|
|
@@ -607,9 +643,32 @@ var warnedApiKeySources = /* @__PURE__ */ new Set();
|
|
|
607
643
|
var warnedMcpFailures = /* @__PURE__ */ new Set();
|
|
608
644
|
var warnedMcpSkips = /* @__PURE__ */ new Set();
|
|
609
645
|
var lastMcpServerErrors = /* @__PURE__ */ new Map();
|
|
646
|
+
var warnedPluginDiagnostics = /* @__PURE__ */ new Set();
|
|
647
|
+
var lastPluginLoadFailures = /* @__PURE__ */ new Map();
|
|
610
648
|
function snapshotMcpServerErrors() {
|
|
611
649
|
return [...lastMcpServerErrors.values()];
|
|
612
650
|
}
|
|
651
|
+
function snapshotPluginLoadFailures() {
|
|
652
|
+
return [...lastPluginLoadFailures.values()];
|
|
653
|
+
}
|
|
654
|
+
function reportPluginDiagnostics(info) {
|
|
655
|
+
const report = (kind, diagnostic) => {
|
|
656
|
+
const loaded = kind === "warning" && info.loadedPlugins.includes(diagnostic.plugin);
|
|
657
|
+
const message = loaded ? `Claude Code has advisory feedback for plugin "${diagnostic.plugin}" (${diagnostic.type}): ${diagnostic.message}` : describePluginLoadFailure(diagnostic);
|
|
658
|
+
if (!loaded) lastPluginLoadFailures.set(diagnostic.plugin, { kind, ...diagnostic });
|
|
659
|
+
const key = `${kind}:${diagnostic.plugin}:${diagnostic.type}`;
|
|
660
|
+
const data = { plugin: diagnostic.plugin, type: diagnostic.type, kind };
|
|
661
|
+
if (warnedPluginDiagnostics.has(key)) {
|
|
662
|
+
log.debug(message, data);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
warnedPluginDiagnostics.add(key);
|
|
666
|
+
if (loaded) log.info(message, data);
|
|
667
|
+
else log.warn(message, data);
|
|
668
|
+
};
|
|
669
|
+
for (const diagnostic of info.pluginErrors) report("error", diagnostic);
|
|
670
|
+
for (const diagnostic of info.pluginWarnings) report("warning", diagnostic);
|
|
671
|
+
}
|
|
613
672
|
function reportSystemInit(msg, options = {}) {
|
|
614
673
|
const info = parseSystemInit(msg);
|
|
615
674
|
if (!info) return;
|
|
@@ -620,8 +679,11 @@ function reportSystemInit(msg, options = {}) {
|
|
|
620
679
|
cliVersion: info.cliVersion ?? null,
|
|
621
680
|
tools: info.toolCount,
|
|
622
681
|
mcpServers: info.mcpServers,
|
|
623
|
-
mcpServerErrors: info.mcpServerErrors
|
|
682
|
+
mcpServerErrors: info.mcpServerErrors,
|
|
683
|
+
pluginErrors: info.pluginErrors,
|
|
684
|
+
pluginWarnings: info.pluginWarnings
|
|
624
685
|
});
|
|
686
|
+
reportPluginDiagnostics(info);
|
|
625
687
|
for (const error of info.mcpServerErrors) {
|
|
626
688
|
lastMcpServerErrors.set(error.name, error);
|
|
627
689
|
const key2 = `${error.name}:${error.type}`;
|
|
@@ -736,6 +798,38 @@ function formatSilentTurnNote(hadReasoning) {
|
|
|
736
798
|
${SILENT_TURN_MARKER} ${what}, so there is no reply above. Nothing failed and nothing is pending: send the message again, or rephrase it.
|
|
737
799
|
`;
|
|
738
800
|
}
|
|
801
|
+
var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
|
|
802
|
+
var MODEL_CLI_FLOORS = {
|
|
803
|
+
// "Added Claude Sonnet 5.5 (`claude-sonnet-5-5`)" (CHANGELOG, 2.1.284).
|
|
804
|
+
"claude-sonnet-5-5": "2.1.284"
|
|
805
|
+
};
|
|
806
|
+
function parseUnrecognizedModel(stderr) {
|
|
807
|
+
const at = stderr.indexOf(UNRECOGNIZED_MODEL_STDERR_MARKER);
|
|
808
|
+
if (at === -1) return null;
|
|
809
|
+
const rest = stderr.slice(at + UNRECOGNIZED_MODEL_STDERR_MARKER.length);
|
|
810
|
+
const line = rest.split("\n", 1)[0].trim();
|
|
811
|
+
try {
|
|
812
|
+
const payload = JSON.parse(line);
|
|
813
|
+
return { model: isRecord(payload) ? str(payload.model) : void 0 };
|
|
814
|
+
} catch {
|
|
815
|
+
return { model: void 0 };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
var warnedUnrecognizedModels = /* @__PURE__ */ new Set();
|
|
819
|
+
function reportUnrecognizedModel(stderr) {
|
|
820
|
+
const parsed = parseUnrecognizedModel(stderr);
|
|
821
|
+
if (!parsed) return;
|
|
822
|
+
const model = parsed.model ?? "unknown";
|
|
823
|
+
const floor = parsed.model ? MODEL_CLI_FLOORS[parsed.model] : void 0;
|
|
824
|
+
const fix = floor ? `It needs Claude Code ${floor} or newer: run \`claude update\`.` : "Update Claude Code (`claude update`) to a release that knows it.";
|
|
825
|
+
const message = `Claude Code does not recognise the model "${model}". It still runs it, but on fallback limits (measured on 2.1.280: a 200k context window instead of 1M, and an estimated cost), so its own compaction may start early and turnStats' cost is approximate. ${fix}`;
|
|
826
|
+
if (warnedUnrecognizedModels.has(model)) {
|
|
827
|
+
log.debug(message, { model });
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
warnedUnrecognizedModels.add(model);
|
|
831
|
+
log.warn(message, { model, ...floor ? { cliFloor: floor } : {} });
|
|
832
|
+
}
|
|
739
833
|
|
|
740
834
|
// src/plan-mode-question.ts
|
|
741
835
|
var QUESTION_TOOL_NAME = "question";
|
|
@@ -3507,6 +3601,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
3507
3601
|
const stderr = data.toString();
|
|
3508
3602
|
log.debug("stderr", { data: stderr.slice(0, 200) });
|
|
3509
3603
|
retainStderr(ap, stderr);
|
|
3604
|
+
reportUnrecognizedModel(stderr);
|
|
3510
3605
|
if (stderr.includes("No conversation found") || stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
|
|
3511
3606
|
if (activeProcesses.get(sessionKey2) === ap) {
|
|
3512
3607
|
log.warn("claude session ID error, clearing session", {
|
|
@@ -4836,6 +4931,18 @@ function formatDoctorReport(report) {
|
|
|
4836
4931
|
lines.push("");
|
|
4837
4932
|
lines.push("A skipped server is missing from the model's tools with no other sign of it.");
|
|
4838
4933
|
}
|
|
4934
|
+
if (report.pluginLoadFailures.length > 0) {
|
|
4935
|
+
lines.push("");
|
|
4936
|
+
lines.push("**Plugins Claude Code did not load**");
|
|
4937
|
+
lines.push("");
|
|
4938
|
+
lines.push("| plugin | kind | category | Claude Code said |");
|
|
4939
|
+
lines.push("|---|---|---|---|");
|
|
4940
|
+
for (const failure of report.pluginLoadFailures) {
|
|
4941
|
+
lines.push(
|
|
4942
|
+
`| ${failure.plugin} | ${failure.kind} | \`${failure.type}\` | ${failure.message || "no detail"} |`
|
|
4943
|
+
);
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4839
4946
|
lines.push("");
|
|
4840
4947
|
lines.push("**Plan usage**");
|
|
4841
4948
|
lines.push("");
|
|
@@ -4934,6 +5041,7 @@ async function gatherDoctorReport(options) {
|
|
|
4934
5041
|
pendingCalls: snapshotPendingProxyCalls(),
|
|
4935
5042
|
proxyServers,
|
|
4936
5043
|
mcpServerErrors: snapshotMcpServerErrors(),
|
|
5044
|
+
pluginLoadFailures: snapshotPluginLoadFailures(),
|
|
4937
5045
|
planUsage
|
|
4938
5046
|
};
|
|
4939
5047
|
}
|
|
@@ -4948,6 +5056,7 @@ async function buildDoctorReport(options) {
|
|
|
4948
5056
|
pendingCalls: report.pendingCalls.length,
|
|
4949
5057
|
proxyServers: report.proxyServers.map((server2) => server2.auth.status),
|
|
4950
5058
|
mcpServerErrors: report.mcpServerErrors.length,
|
|
5059
|
+
pluginLoadFailures: report.pluginLoadFailures.length,
|
|
4951
5060
|
planUsage: report.planUsage.status
|
|
4952
5061
|
});
|
|
4953
5062
|
return formatDoctorReport(report);
|
|
@@ -5022,6 +5131,7 @@ function passthroughModel(id) {
|
|
|
5022
5131
|
}
|
|
5023
5132
|
var haikuCost = { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 };
|
|
5024
5133
|
var sonnetCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
5134
|
+
var sonnet5Cost = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
5025
5135
|
var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
5026
5136
|
var fableCost = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
|
|
5027
5137
|
var fable51Cost = { input: 10, output: 50, cacheRead: 0.25, cacheWrite: 12.5 };
|
|
@@ -5101,10 +5211,26 @@ var defaultModels = {
|
|
|
5101
5211
|
reasoning: true,
|
|
5102
5212
|
context: 1e6,
|
|
5103
5213
|
output: 128e3,
|
|
5104
|
-
cost:
|
|
5105
|
-
multiplier:
|
|
5214
|
+
cost: sonnet5Cost,
|
|
5215
|
+
multiplier: 2,
|
|
5106
5216
|
releaseDate: "2026-06-30"
|
|
5107
5217
|
}),
|
|
5218
|
+
// Sonnet 5.5 runs on any recent Claude Code, but 2.1.284 is the first
|
|
5219
|
+
// release that knows it: an older CLI serves it on fallback limits (200k
|
|
5220
|
+
// context, an estimated cost) and says so on stderr, which
|
|
5221
|
+
// `reportUnrecognizedModel` turns into a warning. No `-fast` entry: the CLI
|
|
5222
|
+
// gates fast mode on Opus names.
|
|
5223
|
+
"claude-sonnet-5-5": defineModel({
|
|
5224
|
+
id: "claude-sonnet-5-5",
|
|
5225
|
+
name: "Claude Sonnet 5.5",
|
|
5226
|
+
family: "sonnet",
|
|
5227
|
+
reasoning: true,
|
|
5228
|
+
context: 1e6,
|
|
5229
|
+
output: 128e3,
|
|
5230
|
+
cost: sonnet5Cost,
|
|
5231
|
+
multiplier: 2,
|
|
5232
|
+
releaseDate: "2026-09-28"
|
|
5233
|
+
}),
|
|
5108
5234
|
"claude-opus-4-5": defineModel({
|
|
5109
5235
|
id: "claude-opus-4-5",
|
|
5110
5236
|
name: "Claude Opus 4.5",
|
|
@@ -6028,7 +6154,6 @@ import * as fs5 from "fs";
|
|
|
6028
6154
|
import * as os3 from "os";
|
|
6029
6155
|
import * as path7 from "path";
|
|
6030
6156
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6031
|
-
var SKILL_PLUGIN_NAME = "opencode-skills";
|
|
6032
6157
|
function bundledSkillsDir() {
|
|
6033
6158
|
try {
|
|
6034
6159
|
const here = fileURLToPath2(import.meta.url);
|
|
@@ -7764,6 +7889,17 @@ function toUsage(rawUsage) {
|
|
|
7764
7889
|
raw: rawUsage
|
|
7765
7890
|
};
|
|
7766
7891
|
}
|
|
7892
|
+
function lastCallContextUsage(lastCallUsage, turnTotalUsage) {
|
|
7893
|
+
if (!lastCallUsage || !turnTotalUsage) return turnTotalUsage;
|
|
7894
|
+
const iterations = lastCallUsage.iterations;
|
|
7895
|
+
const context = iterations?.length ? iterations[iterations.length - 1] : lastCallUsage;
|
|
7896
|
+
return {
|
|
7897
|
+
input_tokens: context.input_tokens,
|
|
7898
|
+
cache_read_input_tokens: context.cache_read_input_tokens,
|
|
7899
|
+
cache_creation_input_tokens: context.cache_creation_input_tokens,
|
|
7900
|
+
output_tokens: turnTotalUsage.output_tokens
|
|
7901
|
+
};
|
|
7902
|
+
}
|
|
7767
7903
|
function toFinishReason(reason = "stop") {
|
|
7768
7904
|
return {
|
|
7769
7905
|
unified: reason,
|
|
@@ -7855,6 +7991,7 @@ function createTurnState(init) {
|
|
|
7855
7991
|
skipResultForIds: /* @__PURE__ */ new Set(),
|
|
7856
7992
|
toolCallsById: /* @__PURE__ */ new Map(),
|
|
7857
7993
|
resultMeta: {},
|
|
7994
|
+
lastCallUsage: void 0,
|
|
7858
7995
|
resultFailure: void 0,
|
|
7859
7996
|
accountLimitHit: null,
|
|
7860
7997
|
accountBlock: null,
|
|
@@ -8264,7 +8401,8 @@ function finishWithToolCalls(state, calls) {
|
|
|
8264
8401
|
state.controller.enqueue({
|
|
8265
8402
|
type: "finish",
|
|
8266
8403
|
finishReason: state.toFinishReason("tool-calls"),
|
|
8267
|
-
|
|
8404
|
+
// No result yet (the usual mid-turn boundary) still reports nothing.
|
|
8405
|
+
usage: state.toUsage(lastCallContextUsage(state.lastCallUsage, state.resultMeta.usage)),
|
|
8268
8406
|
providerMetadata: {
|
|
8269
8407
|
"claude-code": state.resultMeta
|
|
8270
8408
|
}
|
|
@@ -8295,7 +8433,7 @@ function finishWithQuestionCall(state, call) {
|
|
|
8295
8433
|
state.controller.enqueue({
|
|
8296
8434
|
type: "finish",
|
|
8297
8435
|
finishReason: state.toFinishReason("tool-calls"),
|
|
8298
|
-
usage: state.toUsage(state.resultMeta.usage),
|
|
8436
|
+
usage: state.toUsage(lastCallContextUsage(state.lastCallUsage, state.resultMeta.usage)),
|
|
8299
8437
|
providerMetadata: {
|
|
8300
8438
|
"claude-code": state.resultMeta
|
|
8301
8439
|
}
|
|
@@ -8743,6 +8881,10 @@ ${plan}
|
|
|
8743
8881
|
if (msg.type === "assistant" && msg.message && typeof msg.message.stop_reason === "string") {
|
|
8744
8882
|
state.lastStopReason = msg.message.stop_reason;
|
|
8745
8883
|
}
|
|
8884
|
+
const callUsage = msg.type === "assistant" ? msg.message?.usage : void 0;
|
|
8885
|
+
if (callUsage && (callUsage.input_tokens ?? 0) + (callUsage.cache_read_input_tokens ?? 0) + (callUsage.cache_creation_input_tokens ?? 0) > 0) {
|
|
8886
|
+
state.lastCallUsage = callUsage;
|
|
8887
|
+
}
|
|
8746
8888
|
if (msg.type === "assistant" && msg.message?.content && state.gotPartialEvents) {
|
|
8747
8889
|
const thinkingBlocks = msg.message.content.filter(
|
|
8748
8890
|
(b) => b.type === "thinking"
|
|
@@ -10226,7 +10368,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
10226
10368
|
controller.enqueue({
|
|
10227
10369
|
type: "finish",
|
|
10228
10370
|
finishReason: { unified: "error", raw: refusal.kind },
|
|
10229
|
-
usage: toUsage2(msg.usage),
|
|
10371
|
+
usage: toUsage2(lastCallContextUsage(state.lastCallUsage, msg.usage)),
|
|
10230
10372
|
providerMetadata: {
|
|
10231
10373
|
"claude-code": { ...state.resultMeta, path: "model-fallback" }
|
|
10232
10374
|
}
|
|
@@ -10274,19 +10416,23 @@ var ClaudeCodeLanguageModel = class {
|
|
|
10274
10416
|
});
|
|
10275
10417
|
state.endTextBlock();
|
|
10276
10418
|
}
|
|
10419
|
+
const usage = toUsage2(lastCallContextUsage(state.lastCallUsage, msg.usage));
|
|
10277
10420
|
controller.enqueue({
|
|
10278
10421
|
type: "finish",
|
|
10279
10422
|
finishReason: state.resultFailure ? { unified: "error", raw: state.resultFailure } : toFinishReason2("stop"),
|
|
10280
|
-
usage
|
|
10423
|
+
usage,
|
|
10281
10424
|
providerMetadata: {
|
|
10282
10425
|
"claude-code": {
|
|
10283
10426
|
...state.resultMeta,
|
|
10284
10427
|
...state.resultFailure ? { resultSubtype: state.resultFailure } : {},
|
|
10285
10428
|
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
10286
10429
|
},
|
|
10430
|
+
// opencode falls back to this when the usage has no cache
|
|
10431
|
+
// write (0 is sent as none), so it must match the usage, never
|
|
10432
|
+
// the turn total.
|
|
10287
10433
|
...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
|
|
10288
10434
|
anthropic: {
|
|
10289
|
-
cacheCreationInputTokens:
|
|
10435
|
+
cacheCreationInputTokens: usage.inputTokens.cacheWrite ?? 0
|
|
10290
10436
|
}
|
|
10291
10437
|
} : {}
|
|
10292
10438
|
}
|