@khalilgharbaoui/opencode-claude-code-plugin 0.28.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,12 +6,39 @@ Use Claude models inside [opencode](https://opencode.ai) by driving the official
6
6
 
7
7
  - **Your CLI's auth, untouched.** Because `claude` does the authenticating, there is no subscription token here to lift and replay against the Anthropic API. That replay is what proxy-style opencode plugins do, it is a practice Anthropic has disallowed for third-party tools in 2026, and it is structurally not something this plugin can do.
8
8
  - **opencode stays in charge of your machine.** Bash, Edit, Write, WebFetch and subagent dispatch are executed by opencode, behind its permission prompts and audit log, rather than by Claude Code. See [Selective tool proxy](#selective-tool-proxy).
9
- - **Headless by default, which has a billing consequence.** `claude --print` usage on a subscription plan draws from the separate Agent SDK / extra-usage allowance rather than from normal plan usage; API-key authentication is unaffected. See [Billing](#billing).
9
+ - **Headless by default, on your plan's ordinary usage limits.** `claude --print` usage on a subscription plan draws from the same usage limits as interactive Claude Code; the separate Agent SDK credit Anthropic announced for June 2026 was paused before it took effect. API-key authentication bills pay-as-you-go instead. See [Billing](#billing).
10
10
 
11
11
  > Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin). Published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
12
12
 
13
13
  ---
14
14
 
15
+ ## How this compares
16
+
17
+ Three ways to reach Claude from opencode. They differ in who authenticates, who gets billed, whether Anthropic sanctions it, and how much of your machine opencode still governs.
18
+
19
+ | | opencode's native `anthropic` provider | This plugin | Proxy / token-reuse plugins |
20
+ |---|---|---|---|
21
+ | **Authentication** | An Anthropic Platform API key, held in opencode's own auth store. | Whatever the official `claude` CLI already holds: a subscription login, an API key, Bedrock, or Vertex. The plugin never reads, stores, or replays a token of its own, and there is no subscription token here to lift. | The Claude OAuth session, used outside the official client. Meridian runs a local proxy that maps Anthropic-style HTTP onto the Claude Agent SDK and your Claude session; `opencode-claude-auth` reads the OAuth tokens out of the macOS Keychain or `~/.claude/.credentials.json` and refreshes them against Anthropic's OAuth endpoint itself. |
22
+ | **What is billed, and to whom** | Pay as you go on the Platform account that owns the key. | Whatever the CLI's own authentication bills. Headless `--print` is the Agent SDK path; an API key found anywhere the CLI looks switches the same turn onto Console pay-as-you-go instead. `apiKeySource` on the CLI's `system` init event is the field that says which, and the plugin warns once per process when a key is in effect. On a subscription, headless and interactive turns both draw from the plan's ordinary usage limits. See [which login bills what](#which-login-bills-what). | The subscription the reused session belongs to. Meridian's own FAQ: "Usage limits follow your Max subscription, not Anthropic API billing tiers." |
23
+ | **Terms-of-service status** | The ordinary API route. Nothing unusual about it. | Sanctioned: the official client does the authenticating, and driving `claude` is what `claude` is for. | Disallowed. Anthropic disallowed reusing subscription authentication for third-party Claude use in February 2026, and each project says so in its own words: Meridian's wrapper "makes no claims regarding compliance with Anthropic's Terms of Service"; `opencode-claude-auth` calls itself "a community workaround" and notes that the terms say subscription tokens "should only be used with official Anthropic clients"; `opencode-claude-plan` quotes Consumer Terms 3.7 and asks you to accept that your account "could be suspended or terminated". |
24
+ | **Model list and fast mode** | Whatever opencode's own provider registers. | 17 ids auto-registered, Haiku 4.5 through Opus 5.5 plus Fable and Mythos, each carrying a `(N×)` list-price suffix, and any other id `claude --model` accepts passes straight through. Three `-fast` Opus ids are this plugin's own markers and opt a headless session into fast mode through `--settings` (CLI 2.1.220+). See [Models](#models). | `opencode-claude-auth`'s README lists 14 model ids. Meridian's lists none, because model metadata comes from opencode's own `anthropic` provider. Neither README mentions fast mode. |
25
+ | **Which tools run where, under whose permissions** | All of them are opencode's, behind opencode's permission prompts and audit log. | Your choice, per tool. `Bash`, `Edit`, `Write`, `WebFetch` and `Task` are proxied by default: Claude calls an in-process MCP tool and **opencode** executes it, under its own permissions and audit log. Anything neither proxied nor named in `extraDisallowedTools` runs inside Claude Code under `--dangerously-skip-permissions`. See [Selective tool proxy](#selective-tool-proxy) and [Read-only mode](#read-only-mode). | All of them are opencode's, because the model call is an ordinary provider call. This is the one row where the third column matches the native provider and this plugin has to work for the same result. |
26
+ | **Reasoning and effort** | opencode's own reasoning controls. | Five picker variants per model, `low` through `max`, handed to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn. Effort is fixed for the life of a `claude` process, so it is part of the session key, and an agent's own `reasoningEffort` beats the effort a call arrived with. Thinking is Anthropic's summarized digest, not raw chain-of-thought. See [Extended thinking](#extended-thinking). | Meridian's SDK-features file exposes a `thinking` key. Neither README documents per-model effort variants. |
27
+ | **Context window** | Whatever the model exposes. | The registered limits: 200k context / 64k output on the 4.5 generation, 1M / 128k on 4.6 and later, all at standard pricing with no above-200K tier. Claude Code may also compact or clear its own context mid-conversation, which the plugin can announce but not prevent. | Not stated in either README. |
28
+ | **Subagents** | opencode's own task tool and child sessions. | `Task` is proxied by default, so dispatch is an opencode child session under the caller's `permission.task` rule, and `task_batch` runs two or more concurrently because the CLI otherwise serialises MCP calls. Drop `Task` from `proxyTools` and Claude orchestrates internally with no opencode child-session visibility. See [OpenCode-native subagents](#opencode-native-subagents). | opencode's own, unchanged. |
29
+ | **What you lose versus the native provider** | Baseline. | A `claude` child process per conversation (an idle `--print` holds around 250 MB) under an LRU cap, so many open chats cost memory. Claude Code can compact or clear its own context behind opencode's back. Session titles are a local keyword stub, not a model-written title. Two watchdogs exist only because a child that is alive but wedged emits no event to listen for. `/compact` runs as its own short-lived spawn, on Haiku by default. On opencode 2 there is no todo panel, because 2.x has no `todowrite` tool, and `/btw` is answered after the running turn rather than inside it. opencode hooks that overlap the plugin's hand-rolled features (`tool.definition`, the two compaction hooks, `chat.headers`, `permission.ask`) are deliberately not adopted, and opencode's own reasoning features are bypassed by design, because the whole point is to route through the CLI. Windows spawns through `cmd.exe` unquoted and is [not hardened](#scratch-files-on-disk). | Everything in the row to the left is avoided, because opencode's runtime is doing the work. What replaces it is the account risk in the terms row, plus one more moving part between opencode and Anthropic: a local HTTP proxy, or a reader of your credential store. |
30
+
31
+ Where the third column names a project, the claim is that project's own README:
32
+
33
+ - [ianjwhite99/opencode-with-claude](https://github.com/ianjwhite99/opencode-with-claude) starts [Meridian](https://github.com/rynfar/meridian) inside opencode's own lifecycle and points opencode's `anthropic` provider at it. Its disclaimer calls it an "unofficial wrapper", says the authors "make no claims regarding compliance with Anthropic's Terms of Service", and notes that no API keys are intercepted: the proxy uses the Agent SDK over your own OAuth session.
34
+ - [griffinmartin/opencode-claude-auth](https://github.com/griffinmartin/opencode-claude-auth) registers its own auth provider, reads Claude Code's OAuth credentials from the Keychain or `~/.claude/.credentials.json`, caches and refreshes them, and syncs them into opencode's `auth.json`. Its disclaimer is quoted in the table.
35
+ - [jcubic/opencode-claude-plan](https://github.com/jcubic/opencode-claude-plan) ships no plugin at all: it is a documented plan an agent can build one from, written after opencode removed its bundled Anthropic OAuth plugin on a legal request. Its legal note is the bluntest of the three.
36
+ - [unixfox/opencode-claude-code-plugin](https://github.com/unixfox/opencode-claude-code-plugin) belongs in the middle column rather than the third, and deserves the credit: it is this plugin's archived ancestor and it already drove the `claude` CLI as a subprocess, so it inherited the CLI's authentication the same sanctioned way. What it does not have is the opencode-side mediation. Its README states that the CLI executes every tool, that permissions go through Claude Code's own allow/deny lists with "no opencode permission UI integration", that MCP servers are Claude's rather than opencode's, and that its session key is `(cwd, model)`, so two opencode instances in one directory on one model share a process and interfere. It is archived and links here.
37
+
38
+ Policy sources: Anthropic's [Agent SDK on a Claude plan](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) page, which is authoritative and does change (read the dated note under [Billing](#billing) before quoting it), and the February 2026 report that [Anthropic banned subscription authentication for third-party Claude use](https://alternativeto.net/news/2026/2/anthropic-officially-bans-using-subscription-authentication-for-third-party-claude-use).
39
+
40
+ ---
41
+
15
42
  ## Quickstart
16
43
 
17
44
  ### 1. Install and log in the Claude Code CLI
@@ -44,22 +71,7 @@ Quit opencode fully and relaunch it: plugins are loaded once, at process start,
44
71
 
45
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 (3×)` 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.
46
73
 
47
- If the provider does not appear, turn on the plugin's log file and look for its one startup line:
48
-
49
- ```bash
50
- OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
51
- grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
52
- ```
53
-
54
- That single `NOTICE: claude-code plugin ready` entry reports the plugin version, the `claude` binary and version it found, the directory it will spawn in, and which providers registered. [Startup diagnostics](#startup-diagnostics) explains every field.
55
-
56
- ### Not seeing a version you just upgraded to?
57
-
58
- opencode resolves the `@latest` plugin spec once and freezes the concrete version into its own package cache, so restarting never re-resolves the tag. Delete the cache entry and relaunch:
59
-
60
- ```bash
61
- rm -rf ~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest
62
- ```
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.
63
75
 
64
76
  ### Local development
65
77
 
@@ -168,11 +180,13 @@ Variants set the underlying reasoning effort. They're regular opencode model var
168
180
 
169
181
  ## Billing
170
182
 
171
- By default this plugin drives Claude Code headlessly (the Agent SDK path, `claude --print`). Since June 2026, headless usage on a Claude subscription plan draws from a separate Agent SDK credit / extra usage rather than from normal plan usage. Authenticating the CLI with an API key is unaffected by that policy and bills as ordinary API usage.
183
+ By default this plugin drives Claude Code headlessly (the Agent SDK path, `claude --print`). On a Claude subscription plan that usage draws from your plan's ordinary usage limits, the same pool as interactive Claude Code. Authenticating the CLI with an API key instead bills as ordinary pay-as-you-go API usage.
184
+
185
+ Anthropic's own page is the authoritative source and it changes: <https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan>
172
186
 
173
- Anthropic's own page is the authoritative and current source, including the amounts, which change: <https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan>
187
+ > **The history, so older text does not mislead you.** Anthropic announced a separate monthly Agent SDK credit for headless and third-party usage, to start on June 15, 2026, and paused it the same day. The page, fetched on 2026-09-27, opens with that June 15 update: nothing has changed, Agent SDK usage, `claude -p` and third-party apps still draw from subscription usage limits, and the credit is not available. Earlier versions of this README, and some third-party write-ups, describe the credit as if it were in effect. Re-read the page rather than this section when it matters. The mechanism the plugin exposes is the same either way: `apiKeySource` is what tells you whether a turn is on the subscription or on pay-as-you-go.
174
188
 
175
- Two things in this plugin interact with the above. [`ignoreAnthropicApiKey`](#options-reference) stops a stray `ANTHROPIC_API_KEY` in your environment from silently redirecting the CLI onto pay-as-you-go API billing. The experimental [interactive transport](#interactive-transport-experimental) drives the real `claude` TUI instead of `--print`, which bills as normal plan usage.
189
+ One thing in this plugin interacts with the above: [`ignoreAnthropicApiKey`](#options-reference) stops a stray `ANTHROPIC_API_KEY` in your environment from silently redirecting the CLI onto pay-as-you-go API billing. The experimental [interactive transport](#interactive-transport-experimental) drives the real `claude` TUI instead of `--print`; it does not change what a turn draws from.
176
190
 
177
191
  ---
178
192
 
@@ -316,6 +330,81 @@ To force an **account** rather than a model, pin the full string. This only appl
316
330
  model: claude-code-work/claude-opus-5@work
317
331
  ```
318
332
 
333
+ ### Fallback model chain
334
+
335
+ `forceModel` and the model picker each name exactly one model, so a model this
336
+ account cannot run today is a dead turn. The two ordinary ways to get there are
337
+ a **retired id** (Anthropic retires model names on a published schedule, and an
338
+ agent file written six months ago outlives them) and a **per-model usage cap**.
339
+ An ordered chain degrades instead of failing:
340
+
341
+ Per agent, in the agent's own file, either YAML spelling:
342
+
343
+ ```yaml
344
+ forceModel: claude-opus-5
345
+ fallbackModels: [claude-sonnet-5, claude-haiku-4-5]
346
+ ```
347
+
348
+ Or once, as the default for every agent that declares none:
349
+
350
+ ```json
351
+ { "provider": { "claude-code": { "options": { "fallbackModels": ["claude-sonnet-5"] } } } }
352
+ ```
353
+
354
+ A per-agent list **replaces** the provider one rather than extending it, because
355
+ a merge would append the provider's expensive tail to an agent that deliberately
356
+ named two cheap models.
357
+
358
+ **Exactly two things arm it, and "an error" is not one of them:**
359
+
360
+ 1. **The CLI refuses the model.** Measured on Claude Code 2.1.280: a retired,
361
+ sunset or made-up id produces an assistant frame tagged
362
+ `"error": "model_not_found"` and a result with `is_error: true`,
363
+ `api_error_status: 404` and the text *"There's an issue with the selected
364
+ model (…). It may not exist or you may not have access to it."* Note that the
365
+ result's `subtype` is `success`, which is why a refused model used to finish
366
+ as an ordinary reply with the CLI's error standing in for Claude's answer.
367
+ 2. **A usage limit with nowhere else to go.** Only when
368
+ [account failover](#account-failover) has no other account to offer, meaning
369
+ a single configured account or every other one already limited. **When
370
+ another account exists the switch form wins and the chain does not fire**:
371
+ moving your billing is your decision, moving to a cheaper model is not, and a
372
+ per-model weekly cap is exactly the case a chain helps with.
373
+
374
+ An expired login, a billing hold, a network failure, a tool error and every
375
+ other CLI error kind are deliberately excluded: they fail identically on the
376
+ next model, so retrying would spend a spawn per entry to print the same message.
377
+
378
+ On a trigger the failed attempt is dropped whole (its process killed, its
379
+ session id discarded), a fresh process spawns on the next model with the **same
380
+ account, thinking budget and working directory**, the conversation replays into
381
+ it, and a note goes into the reply:
382
+
383
+ ```
384
+ ▌ **model fallback:** "claude-opus-5" was refused by the Claude CLI
385
+ (model_not_found: it is retired, misspelled, or this account cannot use it), so
386
+ this turn is being served by "claude-sonnet-5" instead. The account, the
387
+ thinking budget and the working directory are unchanged.
388
+ ```
389
+
390
+ The refused attempt's output never reaches you and never reaches a rebuilt
391
+ transcript, and neither does the note, which the plugin wrote rather than
392
+ Claude.
393
+
394
+ The rails: **entries are model names from this plugin's own list** (an unknown
395
+ one is refused with a warning and skipped, exactly as an unknown `forceModel`
396
+ is); **the chain never crosses accounts**, since the `@account` marker is taken
397
+ from the id the turn arrived with and an entry spelling its own is ignored;
398
+ **each model is tried at most once per turn**, and **an exhausted chain surfaces
399
+ the original error unchanged**. Unset (the default) means no chain, so upgrading
400
+ never moves a turn onto a model nobody picked.
401
+
402
+ Not applied to compaction turns (a second model would rewrite the summary
403
+ opencode stores), to title stubs, or to the
404
+ [interactive transport](#interactive-transport-experimental). `doGenerate`
405
+ (titles and no-tools calls) does not fall back either: it bills the picked model
406
+ once and reports its own error.
407
+
319
408
  ### Options reference
320
409
 
321
410
  ```json
@@ -347,6 +436,7 @@ model: claude-code-work/claude-opus-5@work
347
436
  | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to headless `claude --permission-mode`. `"plan"` also suppresses `--dangerously-skip-permissions` (see the row above). Not version-gated, so check that your installed CLI accepts the value. The [interactive transport](#interactive-transport-experimental) does not forward it. |
348
437
  | `permissionPreset` | `"read-only"` | – | A named permission posture, so you set one option instead of combining five and getting one wrong. Opt-in: unset is exactly today's behaviour. `"read-only"` replaces `skipPermissions`, `permissionMode`, `controlRequestBehavior` and `controlRequestToolBehaviors`, and filters the write and command tools out of `proxyTools`. See [Read-only mode](#read-only-mode). |
349
438
  | `defaultSubagentModel` | string | – | Model that plugin-discovered `mode: subagent` agents run on when their own definition pins nothing. The caller's account is kept; only the model name changes. An agent's own `forceModel` wins over it, and an unknown id is refused rather than spawned. Unset means no implicit override at all. See [Subagents: your account, their model](#subagents-your-account-their-model). |
439
+ | `fallbackModels` | string[] | – | Ordered models to try when the one a turn would run on is refused. The default for agents that declare no `fallbackModels` of their own; a per-agent list **replaces** this one rather than extending it. Always the same account, never a different one. Only two things arm it: the CLI refusing the model (`model_not_found`) and a usage limit on an account with no other account to offer. Each model is tried at most once per turn and an exhausted chain surfaces the original error. Unset means no chain at all. See [Fallback model chain](#fallback-model-chain). |
350
440
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
351
441
  | `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
352
442
  | `proxyToolTimeoutMs` | `Record<string, number>` | – | Optional wall-clock backstop per proxy tool, in ms, keyed by proxy tool name (`bash`, `task`, …). A call normally ends on an event the plugin listens for (result, abort, next message, process exit, chat deletion), not on a timer; see [How a proxied call ends](#how-a-proxied-call-ends). Defaults: 10 min flat, `task` / `task_batch` → none, `question` → 30 min. `0` disables a tool's deadline; negative or non-numeric values are ignored. For `bash`, the call's own `input.timeout` is honoured on top (`max(resolved, input.timeout)`). See [Per-tool proxy timeouts](#per-tool-proxy-timeouts). |
@@ -430,7 +520,7 @@ Anything you supply is merged on top of the defaults; you don't need to redeclar
430
520
 
431
521
  ## Interactive transport (experimental)
432
522
 
433
- By default the plugin spawns `claude --print` (headless). From **June 15, 2026** that usage bills against the separate [Agent SDK credit](#billing) on subscription plans. The interactive transport instead drives the real interactive `claude` TUI — which bills as **normal plan usage** — under a native PTY inside opencode's Bun runtime, types your prompt into it, and streams the session transcript (`~/.claude/projects/<cwd>/<session-id>.jsonl`) back through the same pipeline the headless transport uses.
523
+ By default the plugin spawns `claude --print` (headless). The interactive transport instead drives the real interactive `claude` TUI under a native PTY inside opencode's Bun runtime, types your prompt into it, and streams the session transcript (`~/.claude/projects/<cwd>/<session-id>.jsonl`) back through the same pipeline the headless transport uses. It was built as insurance for the day headless usage is billed differently from interactive usage; today both draw from the same plan usage limits (see [Billing](#billing)), so it is not a way to change what a turn costs.
434
524
 
435
525
  ```json
436
526
  "options": { "interactive": true }
@@ -768,12 +858,14 @@ Fully restart opencode after upgrading to load the command and runtime changes.
768
858
 
769
859
  Prints, in the chat, what the plugin currently thinks is happening. The plugin answers it itself: no model is called, nothing is billed, and the reply reports 0 tokens. It is the thing to paste into a bug report.
770
860
 
771
- It carries the startup-diagnostics fields (plugin version, opencode version, `claude` path and version, the working directory and which resolution tier picked it, providers, accounts, `proxyTools`, the on-disk MCP servers, transport, whether an `ANTHROPIC_API_KEY` is present) plus the live runtime state the startup block cannot know:
861
+ It carries the startup-diagnostics fields (plugin version, opencode version, `claude` path and version, the working directory and which resolution tier picked it, providers, accounts, `proxyTools`, the on-disk MCP servers, the `permissionPreset` in force per provider, transport, whether an `ANTHROPIC_API_KEY` is present) plus the live runtime state the startup block cannot know:
772
862
 
773
863
  - every live `claude` child, by opencode session id and model, with its pid, whether a turn is in flight, how long it has been up, and the effort it was spawned at,
774
864
  - every pending proxy call, with the tool, the call id, how long it has waited, and its deadline,
775
865
  - each proxy server's URL with one unauthenticated `initialize` posted to it: `401, good` is the patched behaviour, and anything else is flagged unsafe with the fix (restart every opencode window, since a window opened before 0.13.2 keeps serving an open port). See [Proxy endpoint security](#proxy-endpoint-security).
776
866
 
867
+ The `permissionPreset` row reads `provider: preset` for every registered provider, `none` where none is set, so two accounts configured with different postures are not collapsed into one answer. When a preset is in force, a **Permission preset overrides** block under the table lists the options it replaced, in the same words the log uses. A name the plugin does not recognise is reported as `readonly (unknown, nothing applied)` rather than shown as if it took effect: a typo'd safety option runs at full permissions, and the report is where you find that out. See [Read-only mode](#read-only-mode).
868
+
777
869
  Nothing secret goes in it: not the proxy bearer token, not the value of `ANTHROPIC_API_KEY`, not the system prompt, not a pending call's arguments. A `claude-code-doctor` command you defined yourself is never overwritten. The name has no space in it because opencode reads everything after the first space as the command's arguments. The whole exchange is kept out of any transcript replayed to the CLI, like a `/btw` pair.
778
870
 
779
871
  ## Per-turn stats
@@ -797,7 +889,7 @@ The same numbers are logged at INFO whatever this option is set to, and `total_c
797
889
 
798
890
  Four Claude Code stream events used to reach nothing but a debug log:
799
891
 
800
- - **A rate-limit rejection.** When the CLI reports `status: "rejected"` (or a rejected extra-usage state), the turn now carries a `▌ **rate limit:**` line naming the window, the reason extra usage is unavailable, when it resets, and the four things that can be done about it. Warned once per identity per process. See [Billing](#billing-change-june-15-2026-agent-sdk-credit).
892
+ - **A rate-limit rejection.** When the CLI reports `status: "rejected"` (or a rejected extra-usage state), the turn now carries a `▌ **rate limit:**` line naming the window, the reason extra usage is unavailable, when it resets, and the four things that can be done about it. Warned once per identity per process. See [Billing](#billing) and [which login bills what](#which-login-bills-what).
801
893
  - **A context compaction Claude Code did on its own.** A `▌ **context compacted:**` note says so, with the before and after token counts, so an answer that suddenly forgets the start of the conversation has a visible cause.
802
894
  - **A conversation Claude Code cleared.** Sending `/clear` as a message, or a plan-mode exit that clears context, makes Claude Code start a fresh conversation while opencode still shows the old messages. A `▌ **claude code reset:**` note says so. The plugin deliberately does not replay the earlier messages, since that would undo the clear. Start a new opencode session if you want the two to match.
803
895
  - **A `result` whose subtype is not `success`** (`error_max_turns`, `error_during_execution`, …). The subtype is named in the transcript and the turn finishes as an error instead of an ordinary reply.
@@ -1031,11 +1123,7 @@ opencode ships a built-in `question` tool (`packages/opencode/src/tool/question.
1031
1123
 
1032
1124
  > **Correction, September 6, 2026: this is no longer blocked, and earlier releases of this README were wrong about why.** The missing form was attributed to an upstream TUI regression. The real cause was local: a notification plugin awaited macOS `alerter` dismissal inside `tool.execute.before`, so the question tool never started. Native providers load that same global plugin, which is why their identical failure did not isolate the TUI. With the hook made non-blocking, the form renders, and the full path through this plugin is verified: on plugin 0.18.0 / Claude Code 2.1.258 / opencode 1.18.29, Claude called `mcp__opencode_proxy__question`, the request appeared in `GET /question`, the reply completed the tool, and Claude's answer contained a token it could only have read from the tool result. Confirmed in a real terminal too: with `"Question"` enabled and opencode relaunched, the proxied call rendered as a TUI form and the clicked answers came back into the turn.
1033
1125
  >
1034
- > `"Question"` is still opt-in, because turning it on disables Claude's own `AskUserQuestion` (see the fallback below) and that trade should be deliberate. If your form does not render, see [question troubleshooting](#question-troubleshooting) before assuming an upstream bug.
1035
-
1036
- #### Question troubleshooting
1037
-
1038
- For a stalled call, inspect `GET /question` on the same opencode server and workspace. If no request exists, check awaited `tool.execute.before` hooks and custom tools replacing `question`, especially notification plugins: a hook opencode waits on runs *before* the tool, so the request cannot exist yet. If a request exists but no form appears, check session ownership, pending permissions, and event delivery. The separate detach/reattach issue [anomalyco/opencode#36604](https://github.com/anomalyco/opencode/issues/36604) remains open; [PR #36603](https://github.com/anomalyco/opencode/pull/36603) is closed without merging. Do not infer a universal platform or version failure from either symptom.
1126
+ > `"Question"` is still opt-in, because turning it on disables Claude's own `AskUserQuestion` (see the fallback below) and that trade should be deliberate. If your form does not render, see [a question form never renders](#a-question-form-never-renders-and-the-turn-hangs) before assuming an upstream bug.
1039
1127
 
1040
1128
  Add `"Question"` to `proxyTools`. Claude's built-in `AskUserQuestion` is disabled via `--disallowedTools`, and the plugin exposes `mcp__opencode_proxy__question` in its place. A primary agent needs no permission entry (verified on opencode 1.18.29 with no `permission` block at all); if a subagent's form is refused, grant it `permission.question: "allow"` on that agent, the same way [subagent todos](#subagent-todos) need `todowrite`. 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.
1041
1129
 
@@ -1220,6 +1308,15 @@ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
1220
1308
  "accounts": ["default", "work"],
1221
1309
  "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task"],
1222
1310
  "mcpServers": ["github", "slack"],
1311
+ "permissionPresets": [
1312
+ { "provider": "claude-code-default", "preset": "none", "applied": false, "overrides": [] },
1313
+ {
1314
+ "provider": "claude-code-work",
1315
+ "preset": "read-only",
1316
+ "applied": true,
1317
+ "overrides": ["skipPermissions: forced to false; ..."]
1318
+ }
1319
+ ],
1223
1320
  "interactiveTransport": false,
1224
1321
  "anthropicApiKeyInEnv": false,
1225
1322
  "claudeCli": { "path": "claude", "version": "2.1.211 (Claude Code)" }
@@ -1239,6 +1336,12 @@ Reading it:
1239
1336
  flags like `--thinking-display`.
1240
1337
  - **`mcpServers`** is the on-disk merge, before opencode's runtime toggles
1241
1338
  are applied (those aren't settled yet at startup).
1339
+ - **`permissionPresets`** is one row per provider rather than a single value,
1340
+ because a preset is a safety posture and two accounts can be configured with
1341
+ different ones. `preset` is the configured name or `none`; `applied` is false
1342
+ for `none` and for a name the plugin does not recognise, which applies
1343
+ nothing at all; `overrides` are the operator settings the preset replaced,
1344
+ the same lines logged at NOTICE when it was applied.
1242
1345
  - **`opencode`** is read from the running opencode binary (`--version`), since
1243
1346
  opencode still does not hand its version to plugins. It reads `unknown` when
1244
1347
  opencode is run from source rather than as the packaged binary.
@@ -1280,6 +1383,94 @@ The two compress different windows, so pick deliberately rather than enabling bo
1280
1383
 
1281
1384
  ---
1282
1385
 
1386
+ ## Troubleshooting
1387
+
1388
+ Four checks answer almost everything. Run them in this order, and stop as soon as one of them explains what you are seeing.
1389
+
1390
+ | Check | What it tells you |
1391
+ |---|---|
1392
+ | `/claude-code-doctor` in the session | The plugin version actually loaded, the `claude` path and version, which providers and accounts registered, `proxyTools`, the `permissionPreset` per provider and what it replaced, the working directory and which rule picked it, every live `claude` child, and every pending proxy call. No model is called and nothing is billed. Start here. |
1393
+ | `OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode`, then grep `~/.local/share/opencode-claude-code/plugin.log` | Whether the plugin loaded at all, and every warning it emitted. The log file is off by default, so turning it on needs a relaunch. |
1394
+ | `claude --version` | Whether a version-gated feature can work at all. Version floors: 2.1.142 thinking summaries, 2.1.220 fast mode, 2.1.258 `/btw` and `--restricted`, 2.1.263 `--permission-prompts none`, 2.1.280 `claude-opus-5-5`. |
1395
+ | `claude auth status`, or `CLAUDE_CONFIG_DIR=~/.claude-<name> claude auth status` | Which account is signed in, and whether its login is still valid. |
1396
+
1397
+ ### Start from the symptom
1398
+
1399
+ | What you see first | The one check | The fix |
1400
+ |---|---|---|
1401
+ | No `claude-code` provider or model in the picker at all | Is there a `plugin ready` line in the log? | None means the plugin never loaded, an older version means the package cache. See [Nothing in the picker](#nothing-in-the-picker-or-a-version-you-just-upgraded-to-is-missing). |
1402
+ | `Model unavailable` for a model id you typed | The `providers` field of the ready block, or the same line in `/claude-code-doctor` | Use the provider id that line actually lists. With no `accounts` configured on opencode 2 the id is `claude-code`, so `claude-code-default/<model>` fails while the plugin is perfectly healthy (measured on opencode 2.0.16, 2026-09-27). Declaring [`accounts`](#multiple-claude-code-accounts) is what creates `claude-code-default`. |
1403
+ | 400 `Third-party apps now draw from your extra usage…` | `/claude-code-doctor` for the account the conversation is on, then `claude auth status` for its plan | An account-level usage gate, not a plugin fault: extra usage is off, or the window is exhausted. Wait for the reset, or move to another configured account. This is one of the two error texts that open the [account failover](#account-failover) form, so with several accounts you get the form instead of the error. Enabling paid usage or changing authentication is a billing decision and nothing here makes it for you. |
1404
+ | `Tool result name changed`, and the turn aborts, on opencode 2 | The `plugin` version in `/claude-code-doctor` | Fixed in 0.28.1: a CLI-executed tool's result used to reach opencode under a different name than its call, and opencode 2.0.16 aborts the turn on that mismatch, which broke every Claude-side MCP server call. Upgrade, then **fully quit and relaunch every opencode window**: plugin code is read once at process start, so a new package in a running window changes nothing. |
1405
+ | `plugin ready` is missing from the log | That the log file is actually on, since it is off by default | If it is on and the line is still absent, the plugin never loaded. Check the package is in `plugin` (1.x) or `plugins` (2.x), that a local checkout points at `dist/` on 2.x, and that you relaunched rather than opened a new session. `/claude-code-doctor` answers the same questions without enabling logging. |
1406
+ | `Failed to authenticate: OAuth session expired`, one account, every turn failing in milliseconds | `CLAUDE_CONFIG_DIR=~/.claude-<name> claude auth status` for that account | Log it in again. The plugin writes a `▌ **claude account:**` note naming the account and the exact command, for example `CLAUDE_CONFIG_DIR=~/.claude-work claude auth login`, and offers the switch form when another account exists. Restart opencode afterwards: a switch made from that form lasts until opencode restarts. |
1407
+ | A tool call reported as rejected although it really ran | The `plugin` version in `/claude-code-doctor` | Upgrade to 0.26.2 or newer. Two separate causes, both fixed: opencode 1.18.32 aborts the provider signal of every step that ends in tool calls and the plugin read that as you pressing stop (0.26.1), and a call waiting on an unanswered permission prompt was rejected at the flat 10-minute deadline, after which your late approval cancelled Claude's next call (0.26.2). A deadline now waits while opencode reports the session busy, so an unanswered prompt is never a reason to raise `proxyToolTimeoutMs`. |
1408
+ | `proxy call still waiting` in the log, or a `task` that looks stuck | `/claude-code-doctor`, which lists every pending call with its tool, age and deadline | Usually nothing is wrong. See [A proxy call that will not finish](#a-proxy-call-that-will-not-finish). |
1409
+ | `⚙ invalid` or `⚙ unknown` tool rows | Which tool name the row carries | `⚙ invalid todowrite` inside a subagent means that agent has no `permission.todowrite: "allow"`; see [Subagent todos](#subagent-todos). Any other name is a Claude tool this plugin version does not map for your CLI version: record the plugin version, the CLI version and the tool name, and report it. A permanently pending `⚙ unknown` row is the same problem in its older shape, an input delta for a call opencode never saw start. |
1410
+ | A `-fast` model clearly ran at ordinary speed | Grep `plugin.log` for `fast mode` | Fast mode fails soft, so the plugin warns once per reason and names it; the CLI reports `fast_mode_state: "off"`. The usual cause is that usage credits are off (`/usage-credits` in an interactive `claude`). Also: a CLI below 2.1.220, a cooldown after a fast-mode rate limit, free tier or an organization that disabled it, `CLAUDE_CODE_DISABLE_FAST_MODE=1`, or a non-first-party route, since Bedrock, Vertex and Foundry are excluded. Until it is fixed, switch to the non-fast id so the picker's price matches your bill. |
1411
+ | An MCP server's tools are simply absent | The WARN the plugin logs once per process at session start for each server Claude Code could not connect | Authenticate or repair that server where it is configured. `mcpServers` in the ready block is on-disk discovery, so a server can be listed there and still be unreachable. |
1412
+ | A freshly published version does not appear | The `plugin` version in `/claude-code-doctor` against the version you expect | Remove the frozen cache entry and relaunch: see [Nothing in the picker](#nothing-in-the-picker-or-a-version-you-just-upgraded-to-is-missing). If npm itself does not list the version, a local security scanner with a minimum-package-age policy can be filtering it out of the reply, so read that tool's event log before blaming the registry. |
1413
+ | `permissionPreset` is set but nothing about the session looks restricted | The `permissionPreset` row in `/claude-code-doctor`, for the provider the conversation is actually on | `none` there means the option never reached this provider: it belongs under `provider.<id>.options`, and with `accounts` configured each account is its own provider id. `readonly (unknown, nothing applied)` means the name is not one the plugin knows, so nothing was applied at all; the only name today is `read-only`. When it did apply, the **Permission preset overrides** block names every option it replaced. |
1414
+ | `permissionPreset: "read-only"` is set, but reads are not confined or something still prompts | `claude --version` | The preset holds on any CLI, but two of its four layers are version-gated: `--restricted` needs 2.1.258 and `--permission-prompts none` needs 2.1.263. Below those it falls back to `--disallowedTools` plus the plugin's own denial of every permission request, and warns naming what is missing. Below 2.1.258 you lose the working-directory confinement on reads; below 2.1.263 the denial happens in the plugin instead of in the CLI, one layer instead of two. See [Read-only mode](#read-only-mode). |
1415
+ | A reply that is only *"There's an issue with the selected model (…). It may not exist or you may not have access to it."* | Whether that model id is in the picker, and `claude -p --model <id> "hi"` | The CLI refused the model: it is retired, misspelled, or this account cannot use it. The result's `subtype` is `success`, so without a chain the turn finishes as an ordinary reply with that sentence as the answer. Fix the id in the agent's `forceModel` or in your picker, or declare a [fallback model chain](#fallback-model-chain) so the turn degrades to the next model instead of dying. |
1416
+ | `▌ **model fallback:**` on a turn you expected to run on a specific model | The note itself, which names the model that failed and why | Working as configured: your [`fallbackModels`](#fallback-model-chain) chain moved the turn. `model_not_found` means fix the first id. `out of usage on this account` means that account's cap, and the reason you got a chain rather than the [account failover](#account-failover) form is that no other account was available to offer. The chain never changes account, only model. |
1417
+ | A chain is declared but a refused model still kills the turn | Grep `plugin.log` for `fallback model refused: unknown model` | Every entry has to be a model id this plugin registers; an unknown one is skipped with that warning, and a chain whose entries are all unknown is an empty chain. The other empty-chain case is a list containing only the model the turn already runs on, which is dropped from its own chain. Compaction turns, title stubs and the interactive transport never fall back at all. |
1418
+ | A config change did nothing | `/claude-code-doctor`, which reports the options in force | Provider options are read once at opencode startup. Quit every opencode window, `serve` and GUI processes included, and relaunch. A `/new` session is not enough. |
1419
+ | A question form never renders and the turn hangs | `GET /question` on the same opencode server and workspace | See [A question form never renders](#a-question-form-never-renders-and-the-turn-hangs). |
1420
+
1421
+ ### Longer cases
1422
+
1423
+ #### Nothing in the picker, or a version you just upgraded to is missing
1424
+
1425
+ The plugin's own startup line separates "never loaded" from "loaded and misconfigured":
1426
+
1427
+ ```bash
1428
+ OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
1429
+ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
1430
+ ```
1431
+
1432
+ One `NOTICE: claude-code plugin ready` entry per process reports the plugin version, the `claude` binary and version it found, the directory it will spawn in, and which providers registered. [Startup diagnostics](#startup-diagnostics) explains every field, and `/claude-code-doctor` prints the same fields plus live process state without enabling the log at all.
1433
+
1434
+ **No line.** The plugin did not load. Confirm the package spec is in `plugin` (opencode 1.x) or `plugins` (2.x), that a local checkout points at the repository root on 1.x and at `dist/` on 2.x, and that you fully relaunched: plugins are loaded once, at process start.
1435
+
1436
+ **A line naming an older version.** That is opencode's package cache. It resolves the `@latest` spec once and freezes the concrete version, so restarting never re-resolves the tag. Delete the entry and relaunch:
1437
+
1438
+ ```bash
1439
+ rm -rf ~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest
1440
+ ```
1441
+
1442
+ A `file://` install is different: it runs the checkout's `dist/`, so rebuild with `npm run build` and restart rather than deleting anything.
1443
+
1444
+ **`claudeCli.version` reading `not detected`.** The binary at that path did not answer `--version`, which also silently disables every version-gated flag, including `--thinking-display summarized`, `--plugin-dir` and the fast-mode opt-in.
1445
+
1446
+ #### A question form never renders and the turn hangs
1447
+
1448
+ For a stalled call, inspect `GET /question` on the same opencode server and workspace. If no request exists, check awaited `tool.execute.before` hooks and custom tools replacing `question`, especially notification plugins: a hook opencode waits on runs *before* the tool, so the request cannot exist yet. If a request exists but no form appears, check session ownership, pending permissions, and event delivery. The separate detach/reattach issue [anomalyco/opencode#36604](https://github.com/anomalyco/opencode/issues/36604) remains open; [PR #36603](https://github.com/anomalyco/opencode/pull/36603) is closed without merging. Do not infer a universal platform or version failure from either symptom.
1449
+
1450
+ #### A proxy call that will not finish
1451
+
1452
+ A proxied call ends on an event rather than a clock ([how a proxied call ends](#how-a-proxied-call-ends)), so three log lines exist to keep the waiting visible. **None of them is a failure, and none of them ends a call:**
1453
+
1454
+ - `proxy call still waiting, no deadline`, at WARN, five minutes in and every five minutes after, naming the tool, the call id, how long it has waited and what will end it. `task` and `task_batch` have no deadline by default, so this is exactly what a healthy long-running subagent looks like.
1455
+ - `proxy call still waiting, deadline approaching`, once, at 60% of a deadline that does exist, carrying the time remaining and naming the option that would extend it. Deadlines under a minute are not announced at all.
1456
+ - `proxy call past its deadline, but opencode is still serving it; waiting`, when the deadline passed while opencode reported the session busy: most often a permission prompt nobody has answered yet. It is rechecked every minute.
1457
+
1458
+ `/claude-code-doctor` lists the same calls on demand, with ages and deadlines. Use it to tell a working subagent from a wedged one *before* changing any timeout, and read [per-tool proxy timeouts](#per-tool-proxy-timeouts) before setting one.
1459
+
1460
+ ### Which login bills what
1461
+
1462
+ The `claude` CLI decides this, not the plugin, and the plugin only reports it.
1463
+
1464
+ - **An OAuth subscription login** (`claude auth login`) is the normal case: turns run on your Claude plan. The default transport here is headless `--print`, which is the Agent SDK path, and what that draws from is Anthropic's policy to set: read the dated note under [Billing](#billing) rather than assuming. The [interactive transport](#interactive-transport-experimental) drives the real TUI instead and bills as normal plan usage.
1465
+ - **An API key in the environment.** `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` in the environment that launched opencode reaches the CLI, which prefers it over your subscription login and bills the Platform account pay as you go. This is the one route [`ignoreAnthropicApiKey`](#options-reference) can strip, and the plugin warns at startup whenever it sees one, whatever that option is set to.
1466
+ - **An API key the CLI found by itself**, from its own `user`, `project` or `org` settings scopes or from an `apiKeyHelper`. That is the CLI's configuration rather than opencode's, so no plugin option removes it.
1467
+ - **`apiKeySource` is the field that tells the truth.** The CLI reports it on the `system` init event of every session, and anything other than `oauth` (the subscription) or `none` means a key is in effect. The plugin warns once per process when that happens. An absent `ANTHROPIC_API_KEY` does not prove pay-as-you-go is off, because of the route above; `apiKeySource` does.
1468
+ - **Bedrock and Vertex** are the other two things the CLI's authentication can be, and if it is one of them then neither a Claude subscription nor an Anthropic key is in play for that turn. Fast mode is first-party only, so it is excluded on Bedrock, on Vertex and on Foundry.
1469
+
1470
+ With more than one account configured, an account that runs out mid-task ends the turn on a form instead of an error, and the pick is sticky for the limited account until its reset time: see [Account failover](#account-failover). The one cost worth knowing before you pick is that the conversation is replayed into a fresh session on the target account, because Claude transcripts live under each account's own `CLAUDE_CONFIG_DIR` and `--resume` cannot cross accounts.
1471
+
1472
+ ---
1473
+
1283
1474
  ## Known limitations
1284
1475
 
1285
1476
  - Tool inputs stream as they are constructed (Anthropic's `input_json_delta` is forwarded as `tool-input-delta`), but only for tool calls opencode actually sees. Calls the plugin deliberately does not forward, meaning proxy tools, CLI-internal `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, the todo-ledger `Task*` family and Claude's other internal tools, have their deltas suppressed, because a delta for a tool opencode never saw start renders as a permanently pending `⚙ unknown` row.
package/dist/index.d.ts CHANGED
@@ -498,6 +498,20 @@ interface ClaudeCodeProviderSettings {
498
498
  * caller's model exactly as opencode intends. See `src/agent-models.ts`.
499
499
  */
500
500
  defaultSubagentModel?: string;
501
+ /**
502
+ * Models to try, in order, when the model a turn would have run on is
503
+ * refused. The default for every agent that declares no `fallbackModels` of
504
+ * its own; a per-agent list replaces this one rather than extending it.
505
+ *
506
+ * Unset means no chain at all, so an upgrade never moves a turn onto a
507
+ * model nobody picked. Entries are model NAMES from this plugin's own list
508
+ * (an unknown one is refused with a WARN and skipped) and always run on the
509
+ * account the turn arrived on: this never crosses accounts, which is what
510
+ * `accountFailover` is for. Only two things arm it, the CLI refusing the
511
+ * model outright and a usage limit with no other account to offer. See
512
+ * README "Fallback model chain" and `src/model-fallback.ts`.
513
+ */
514
+ fallbackModels?: string[];
501
515
  skipPermissions?: boolean;
502
516
  permissionMode?: PermissionMode;
503
517
  /**
@@ -851,6 +865,21 @@ type PermissionPreset = "read-only";
851
865
  declare const READ_ONLY_PERMISSION_MODE = "read-only";
852
866
  type EffectivePermissionMode = PermissionMode | typeof READ_ONLY_PERMISSION_MODE;
853
867
  type ControlRequestBehavior = "allow" | "deny";
868
+ /**
869
+ * `Question` is deliberately absent: enabling it disables Claude Code's
870
+ * built-in AskUserQuestion (via --disallowedTools) and replaces the
871
+ * stop-and-wait deny/markdown path with an in-turn blocking form. That is a
872
+ * behavior trade against the issue-#8 guarantee, so it stays opt-in until it
873
+ * has the same live mileage Task had before v0.10.0 flipped it on. Users opt
874
+ * in by listing it in `proxyTools`; see README "Question proxy tool".
875
+ *
876
+ * It lives here rather than in `index.ts` (which still re-exports it, so the
877
+ * public name is unchanged) because `permission-presets.ts` needs it to say
878
+ * which proxy tools a preset dropped, and `startup-diagnostics.ts` and
879
+ * `doctor.ts` read that answer. Importing `index.ts` from any of the three
880
+ * would be a cycle.
881
+ */
882
+ declare const DEFAULT_PROXY_TOOL_NAMES: string[];
854
883
  /**
855
884
  * Claude CLI stream-json message types.
856
885
  */
@@ -1134,6 +1163,25 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
1134
1163
  * returned untouched.
1135
1164
  */
1136
1165
  doStream(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doStream"]>>>;
1166
+ /**
1167
+ * Run the turn, moving to the next model in the fallback chain if the one
1168
+ * it started on is refused. The single wiring point for `src/model-fallback.ts`.
1169
+ *
1170
+ * With no chain declared (the default) this is `doStreamForHost` and one
1171
+ * `await`, so nothing about an existing install changes. With a chain, one
1172
+ * rule carries the whole design: **an attempt's parts are withheld until it
1173
+ * proves the model is serving**, and a refused attempt is then discarded
1174
+ * whole rather than edited. That is what keeps the CLI's "There's an issue
1175
+ * with the selected model" text out of the operator's transcript and out of
1176
+ * any replay, and it costs nothing on a served turn, because the very first
1177
+ * content block commits the attempt and every later part passes straight
1178
+ * through.
1179
+ *
1180
+ * The bound is `tried`: one turn spawns each model at most once, in order,
1181
+ * and an exhausted chain leaves the last attempt un-armed so its error
1182
+ * surfaces exactly as it does today.
1183
+ */
1184
+ private runModelChain;
1137
1185
  private doStreamForHost;
1138
1186
  }
1139
1187
 
@@ -1187,6 +1235,11 @@ type AgentRecord = {
1187
1235
  forceModel?: string;
1188
1236
  /** Thinking budget this agent wants, whatever the caller's picker says. */
1189
1237
  reasoningEffort?: string;
1238
+ /**
1239
+ * Models to try, in order, when the one this agent would have run is
1240
+ * refused. Same account throughout; see `src/model-fallback.ts`.
1241
+ */
1242
+ fallbackModels?: string[];
1190
1243
  };
1191
1244
  declare function getAgentRegistry(): Record<string, AgentRecord>;
1192
1245
  declare function getDefaultSubagentModel(): string | undefined;
@@ -1218,7 +1271,7 @@ interface ClaudeCodeProvider {
1218
1271
  (modelId: string): LanguageModelV3;
1219
1272
  languageModel(modelId: string): LanguageModelV3;
1220
1273
  }
1221
- declare const DEFAULT_PROXY_TOOL_NAMES: string[];
1274
+
1222
1275
  /**
1223
1276
  * Registers `/btw` unless the user defined their own. Returns whether the
1224
1277
  * registration is ours: the command hook only intercepts `btw` in that case,