@xtruder/opencode-claude-max-plugin 0.3.1 → 0.4.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 CHANGED
@@ -9,8 +9,9 @@ An [OpenCode](https://opencode.ai/) plugin that enables Claude Pro/Max subscript
9
9
  - **Use your Claude subscription** — Automatically reads OAuth credentials from Claude Code, no separate API key needed
10
10
  - **Matches Claude Code 2.1.154** — Same request format and behavior as the official CLI
11
11
  - **Prompt caching** — Multi-turn conversations cache properly, keeping costs and latency low
12
- - **All Claude models** — Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Haiku 4.5
12
+ - **All Claude models** — Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Haiku 4.5
13
13
  - **Extended / adaptive thinking** — Full reasoning support across models, including Opus 4.7+'s adaptive thinking
14
+ - **Safety-refusal fallback** — Fable 5 refusals are transparently answered by Opus 4.8 in the same request, with TUI notification
14
15
  - **Usage tracking** — Sidebar widget with live progress bars + `/usage` command
15
16
  - **Self-registering** — Models are registered automatically, no manual provider config needed
16
17
 
@@ -25,9 +26,9 @@ Add the plugin to your `opencode.json` (project-level or `~/.config/opencode/ope
25
26
  }
26
27
  ```
27
28
 
28
- That's it. The plugin self-registers the `anthropic-sdk` provider and its models (Haiku 4.5, Sonnet 4.6, Opus 4.6, Opus 4.7, Opus 4.8) at startup via the OpenCode config hook. No separate `provider` block is needed.
29
+ That's it. The plugin self-registers the `anthropic-sdk` provider and its models (Haiku 4.5, Sonnet 4.6, Opus 4.6, Opus 4.7, Opus 4.8, Fable 5) at startup via the OpenCode config hook. No separate `provider` block is needed.
29
30
 
30
- Then open OpenCode and run `/connect` → Other → `anthropic-sdk`. If Claude Code is installed and you're logged in, credentials are read automatically from `~/.claude/.credentials.json` — no API key needed.
31
+ Then open OpenCode and models will automatically be available under `anthropic-sdk` provider.
31
32
 
32
33
  ### TUI Plugin (sidebar + /usage command)
33
34
 
@@ -54,6 +55,7 @@ The TUI plugin provides:
54
55
  - **Sidebar widget** — Compact progress bars for 5-hour session and 7-day weekly usage
55
56
  - **`/usage` command** — Opens a dialog with full usage breakdown (per-model, extra usage)
56
57
  - **Auto-refresh** — Polls the usage API every 60s and after each inference call
58
+ - **Fallback indicator** — Toast when a Fable 5 refusal falls back to Opus 4.8, plus a sidebar line showing which model served the latest turn
57
59
 
58
60
  #### TUI Configuration
59
61
 
@@ -108,6 +110,36 @@ If you want to override model settings (e.g. thinking budgets, variants), you ca
108
110
 
109
111
  Config-level settings are merged with plugin defaults — you only need to specify what you want to override.
110
112
 
113
+ ### Claude Fable 5 and safety-refusal fallback
114
+
115
+ Claude Fable 5 (`claude-fable-5`) ships with stricter safety classifiers that can refuse a request at the API level (`stop_reason: "refusal"`) — even for benign follow-ups if the conversation contains a flagged topic. To keep sessions usable, the plugin enables Anthropic's server-side fallback by default: when Fable 5 refuses, **Opus 4.8 answers the same request in the same round trip**. Tool loops keep running, thinking chains stay verified, and prompt caching is unaffected.
116
+
117
+ When a fallback happens:
118
+
119
+ - The TUI shows a toast (`fable-5 refused — answered by opus-4-8`) on the first fallback turn
120
+ - The sidebar shows a `Model Fallback` line while the latest turn was served by the fallback model
121
+ - The served model is recorded in part metadata (`anthropic.servedBy`) — the model's own self-report will still say `claude-fable-5`, since identity comes from the prompt, not the serving model
122
+
123
+ Configure via the `refusalFallback` model option:
124
+
125
+ ```json
126
+ {
127
+ "provider": {
128
+ "anthropic-sdk": {
129
+ "models": {
130
+ "claude-fable-5": {
131
+ "options": {
132
+ "refusalFallback": false
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ Set it to another model ID to change the fallback target, or `false` to disable (refusals then surface as errors with the refusal category).
142
+
111
143
  ## Authentication
112
144
 
113
145
  Credentials are resolved in order:
@@ -124,6 +156,7 @@ For Claude Code credentials, log in via `claude` CLI first (`claude auth login`)
124
156
  - MCP tool name remapping (`server_tool` → `mcp__server__tool`)
125
157
  - Extended thinking (Sonnet/Opus 4.6) and adaptive thinking (Opus 4.7+) with effort levels and multi-turn signature passthrough
126
158
  - Prompt caching that holds across long, tool-heavy conversations
159
+ - Server-side safety-refusal fallback for Fable 5 (configurable, on by default)
127
160
  - Subscription rate limit detection — fails fast with a clear message instead of hanging
128
161
  - Long-context auto-detection for large prompts
129
162
  - TUI sidebar with live usage bars + `/usage` slash command
package/build/index.d.ts CHANGED
@@ -1,14 +1,22 @@
1
1
  import type { LanguageModelV3 } from "@ai-sdk/provider";
2
2
  import type { Plugin } from "@opencode-ai/plugin";
3
3
  export declare const CLAUDE_CODE_SYSTEM_PROMPT: string;
4
- export declare const CLAUDE_CODE_OPUS48_SYSTEM_PROMPT: string;
4
+ export declare const CLAUDE_CODE_NEW_SYSTEM_PROMPT: string;
5
5
  /**
6
6
  * Select the Claude Code system prompt that matches the given model.
7
7
  *
8
- * Claude Code 2.1.154 ships per-model prompts: Opus 4.8 gets a condensed
9
- * ~6KB "Harness" prompt that trusts the model's defaults; everything else
10
- * (Opus 4.7, Sonnet 4.6, Haiku 4.5, Opus 4.6) gets the long-form ~11KB
11
- * prompt with explicit behavior rules.
8
+ * Claude Code ships per-model prompts: the newer flagships (Opus 4.8,
9
+ * Fable 5) get a condensed "Harness" prompt that trusts the model's
10
+ * defaults; everything else (Opus 4.7, Sonnet 4.6, Haiku 4.5, Opus 4.6)
11
+ * gets the long-form prompt with explicit behavior rules.
12
+ *
13
+ * Opus 4.8 and Fable 5 share one condensed prompt here. Claude Code 2.1.173
14
+ * sends Fable 5 a longer variant (security steering, a verbose
15
+ * "Communicating" section, a Fable 5 identity blurb, and CLI-only session
16
+ * items), but the safety classifiers are server-side rather than
17
+ * prompt-driven and the CLI-specific guidance does not apply to OpenCode —
18
+ * so we distill it down to the shared base. The verbatim capture lives in
19
+ * src/fixtures/claudecode-system-fable5-original.txt.
12
20
  */
13
21
  export declare function selectClaudePromptForModel(modelId: string): string;
14
22
  export interface AnthropicSDKProviderOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AASjD,eAAO,MAAM,yBAAyB,QAA4B,CAAA;AAClE,eAAO,MAAM,gCAAgC,QAA8B,CAAA;AAE3E;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGlE;AAqND,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAqJtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAoDhC,CAAA;AAID,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AASjD,eAAO,MAAM,yBAAyB,QAA4B,CAAA;AAClE,eAAO,MAAM,6BAA6B,QAA2B,CAAA;AAErE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlE;AAwPD,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA6KtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAwEhC,CAAA;AAID,eAAe,kBAAkB,CAAA"}
package/build/index.js CHANGED
@@ -48,8 +48,8 @@ function hasCchPlaceholder(body) {
48
48
  return block?.text?.includes("cch=00000") ?? false;
49
49
  }
50
50
 
51
- // src/claudecode-system-opus48.txt
52
- var claudecode_system_opus48_default = `
51
+ // src/claudecode-system-new.txt
52
+ var claudecode_system_new_default = `
53
53
  You are an interactive agent that helps users with software engineering tasks.
54
54
 
55
55
  # Harness
@@ -222,6 +222,84 @@ function getCachedCredentials(path) {
222
222
  import { APIError, RateLimitError } from "@anthropic-ai/sdk";
223
223
  import { createHash, randomBytes } from "node:crypto";
224
224
 
225
+ // src/pause-turn.ts
226
+ var MAX_PAUSE_TURN_CONTINUATIONS = 5;
227
+ function isPauseTurn(stopReason) {
228
+ return stopReason === "pause_turn";
229
+ }
230
+
231
+ class BlockAccumulator {
232
+ blocks = [];
233
+ open = new Map;
234
+ push(event) {
235
+ switch (event.type) {
236
+ case "content_block_start": {
237
+ this.open.set(event.index, {
238
+ block: JSON.parse(JSON.stringify(event.content_block))
239
+ });
240
+ break;
241
+ }
242
+ case "content_block_delta": {
243
+ const entry = this.open.get(event.index);
244
+ if (!entry)
245
+ break;
246
+ const delta = event.delta;
247
+ if (delta.type === "text_delta") {
248
+ entry.block.text = (entry.block.text ?? "") + delta.text;
249
+ } else if (delta.type === "thinking_delta") {
250
+ entry.block.thinking = (entry.block.thinking ?? "") + delta.thinking;
251
+ } else if (delta.type === "signature_delta") {
252
+ entry.block.signature = (entry.block.signature ?? "") + delta.signature;
253
+ } else if (delta.type === "input_json_delta") {
254
+ entry.argsText = (entry.argsText ?? "") + delta.partial_json;
255
+ }
256
+ break;
257
+ }
258
+ case "content_block_stop": {
259
+ const entry = this.open.get(event.index);
260
+ if (!entry)
261
+ break;
262
+ this.open.delete(event.index);
263
+ if (entry.argsText !== undefined) {
264
+ try {
265
+ entry.block.input = JSON.parse(entry.argsText || "{}");
266
+ } catch {
267
+ entry.block.input = {};
268
+ }
269
+ }
270
+ this.blocks.push(entry.block);
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ }
276
+ function continuationParams(params, blocks) {
277
+ if (blocks.length === 0)
278
+ return params;
279
+ return {
280
+ ...params,
281
+ messages: [...params.messages ?? [], { role: "assistant", content: blocks }]
282
+ };
283
+ }
284
+ async function* withPauseTurnContinuation(initial, params, request, maxContinuations = MAX_PAUSE_TURN_CONTINUATIONS) {
285
+ const acc = new BlockAccumulator;
286
+ let stream = initial;
287
+ let continuations = 0;
288
+ outer:
289
+ while (true) {
290
+ for await (const event of stream) {
291
+ if (event.type === "message_delta" && isPauseTurn(event.delta?.stop_reason) && continuations < maxContinuations) {
292
+ continuations++;
293
+ stream = await request(continuationParams(params, acc.blocks));
294
+ continue outer;
295
+ }
296
+ acc.push(event);
297
+ yield event;
298
+ }
299
+ return;
300
+ }
301
+ }
302
+
225
303
  // src/tool-names.ts
226
304
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
227
305
  import { homedir as homedir2 } from "node:os";
@@ -382,6 +460,14 @@ function convertUserMessage(message) {
382
460
  function convertAssistantMessage(message) {
383
461
  const content = [];
384
462
  for (const part of message.content) {
463
+ const fallback = part.providerMetadata?.anthropic?.fallback ?? part.providerOptions?.anthropic?.fallback;
464
+ if (fallback?.from?.model && fallback?.to?.model) {
465
+ content.push({
466
+ type: "fallback",
467
+ from: { model: fallback.from.model },
468
+ to: { model: fallback.to.model }
469
+ });
470
+ }
385
471
  switch (part.type) {
386
472
  case "text":
387
473
  if (part.text.length > 0) {
@@ -460,6 +546,14 @@ var idCounter = 0;
460
546
  function generateId() {
461
547
  return `block-${Date.now()}-${idCounter++}`;
462
548
  }
549
+ function buildRefusalError(stopDetails, fallbackModel) {
550
+ const category = stopDetails?.category ?? null;
551
+ const explanation = stopDetails?.explanation ?? null;
552
+ const categoryNote = category ? ` (category: ${category})` : "";
553
+ const detail = explanation ? explanation : "Claude Fable 5's safety classifiers declined this request.";
554
+ const hint = fallbackModel ? `The configured fallback model (${fallbackModel}) also refused.` : `Retry with a fallback model such as anthropic-sdk/claude-opus-4-8.`;
555
+ return new Error(`Claude Fable 5 refused this request${categoryNote}. ${detail} ${hint}`);
556
+ }
463
557
  function mapFinishReason(stopReason) {
464
558
  const unified = (() => {
465
559
  switch (stopReason) {
@@ -476,8 +570,13 @@ function mapFinishReason(stopReason) {
476
570
  })();
477
571
  return { unified, raw: stopReason ?? undefined };
478
572
  }
479
- function convertStream(anthropicStream, modelId) {
573
+ function convertStream(anthropicStream, modelId, context) {
480
574
  const blockStates = new Map;
575
+ const ctx = {
576
+ apiModelId: context?.apiModelId ?? modelId,
577
+ fallbacksEnabled: context?.fallbacksEnabled ?? false,
578
+ fallbackModel: context?.fallbackModel
579
+ };
481
580
  let inputTokens;
482
581
  let outputTokens;
483
582
  let cachedInputTokens;
@@ -486,7 +585,7 @@ function convertStream(anthropicStream, modelId) {
486
585
  async start(controller) {
487
586
  try {
488
587
  for await (const event of anthropicStream) {
489
- const parts = processEvent(event, blockStates, modelId);
588
+ const parts = processEvent(event, blockStates, modelId, ctx);
490
589
  if (event.type === "message_start" && event.message.usage) {
491
590
  const u = event.message.usage;
492
591
  inputTokens = u.input_tokens;
@@ -496,6 +595,18 @@ function convertStream(anthropicStream, modelId) {
496
595
  if (event.type === "message_delta") {
497
596
  const delta = event;
498
597
  outputTokens = delta.usage?.output_tokens;
598
+ if (delta.delta?.stop_reason === "refusal") {
599
+ controller.enqueue({
600
+ type: "error",
601
+ error: buildRefusalError(delta.delta?.stop_details, ctx.fallbacksEnabled ? ctx.fallbackModel : undefined)
602
+ });
603
+ }
604
+ if (delta.delta?.stop_reason === "pause_turn") {
605
+ controller.enqueue({
606
+ type: "error",
607
+ error: new Error(`Anthropic kept pausing this turn (stop_reason "pause_turn") ` + `after the provider's auto-continuation attempts. Retry the request.`)
608
+ });
609
+ }
499
610
  const finishReason = mapFinishReason(delta.delta?.stop_reason);
500
611
  controller.enqueue({
501
612
  type: "finish",
@@ -532,10 +643,16 @@ function convertStream(anthropicStream, modelId) {
532
643
  }
533
644
  });
534
645
  }
535
- function processEvent(event, blockStates, modelId) {
646
+ function processEvent(event, blockStates, modelId, ctx) {
536
647
  const parts = [];
537
648
  switch (event.type) {
538
649
  case "message_start": {
650
+ const servedModel = event.message.model;
651
+ if (ctx.fallbacksEnabled && servedModel && servedModel !== ctx.apiModelId) {
652
+ ctx.pendingMeta = {
653
+ servedBy: { from: ctx.apiModelId, to: servedModel, kind: "sticky" }
654
+ };
655
+ }
539
656
  parts.push({
540
657
  type: "response-metadata",
541
658
  id: event.message.id,
@@ -547,14 +664,38 @@ function processEvent(event, blockStates, modelId) {
547
664
  case "content_block_start": {
548
665
  const index = event.index;
549
666
  const block = event.content_block;
550
- if (block.type === "text") {
667
+ const takeMeta = () => {
668
+ const meta = ctx.pendingMeta;
669
+ ctx.pendingMeta = undefined;
670
+ return meta;
671
+ };
672
+ if (block.type === "fallback") {
673
+ ctx.pendingMeta = {
674
+ fallback: { from: block.from, to: block.to },
675
+ servedBy: {
676
+ from: block.from?.model ?? ctx.apiModelId,
677
+ to: block.to?.model ?? "unknown",
678
+ kind: "fallback"
679
+ }
680
+ };
681
+ } else if (block.type === "text") {
551
682
  const id = generateId();
552
- blockStates.set(index, { type: "text", id });
553
- parts.push({ type: "text-start", id });
683
+ const meta = takeMeta();
684
+ blockStates.set(index, { type: "text", id, fallbackMeta: meta });
685
+ parts.push({
686
+ type: "text-start",
687
+ id,
688
+ ...meta ? { providerMetadata: { anthropic: meta } } : {}
689
+ });
554
690
  } else if (block.type === "thinking") {
555
691
  const id = generateId();
556
- blockStates.set(index, { type: "thinking", id });
557
- parts.push({ type: "reasoning-start", id });
692
+ const meta = takeMeta();
693
+ blockStates.set(index, { type: "thinking", id, fallbackMeta: meta });
694
+ parts.push({
695
+ type: "reasoning-start",
696
+ id,
697
+ ...meta ? { providerMetadata: { anthropic: meta } } : {}
698
+ });
558
699
  } else if (block.type === "tool_use") {
559
700
  const toolId = block.id;
560
701
  const toolName = toOpencodeToolName(block.name);
@@ -563,7 +704,8 @@ function processEvent(event, blockStates, modelId) {
563
704
  id: toolId,
564
705
  toolUseId: toolId,
565
706
  toolName,
566
- argsText: ""
707
+ argsText: "",
708
+ fallbackMeta: takeMeta()
567
709
  });
568
710
  parts.push({
569
711
  type: "tool-input-start",
@@ -611,12 +753,14 @@ function processEvent(event, blockStates, modelId) {
611
753
  if (state.type === "text") {
612
754
  parts.push({ type: "text-end", id: state.id });
613
755
  } else if (state.type === "thinking") {
756
+ const meta = {
757
+ ...state.signature ? { signature: state.signature } : {},
758
+ ...state.fallbackMeta
759
+ };
614
760
  parts.push({
615
761
  type: "reasoning-end",
616
762
  id: state.id,
617
- ...state.signature ? {
618
- providerMetadata: { anthropic: { signature: state.signature } }
619
- } : {}
763
+ ...Object.keys(meta).length > 0 ? { providerMetadata: { anthropic: meta } } : {}
620
764
  });
621
765
  } else if (state.type === "tool_use") {
622
766
  parts.push({ type: "tool-input-end", id: state.id });
@@ -624,7 +768,8 @@ function processEvent(event, blockStates, modelId) {
624
768
  type: "tool-call",
625
769
  toolCallId: state.toolUseId,
626
770
  toolName: state.toolName,
627
- input: state.argsText || "{}"
771
+ input: state.argsText || "{}",
772
+ ...state.fallbackMeta ? { providerMetadata: { anthropic: state.fallbackMeta } } : {}
628
773
  });
629
774
  }
630
775
  blockStates.delete(index);
@@ -695,6 +840,17 @@ function handleApiError(error) {
695
840
  }
696
841
  throw error;
697
842
  }
843
+ function isRefusal(stopReason) {
844
+ return stopReason === "refusal";
845
+ }
846
+ function refusalError(stopDetails, fallbackModel) {
847
+ const category = stopDetails?.category ?? null;
848
+ const explanation = stopDetails?.explanation ?? null;
849
+ const categoryNote = category ? ` (category: ${category})` : "";
850
+ const detail = explanation ? explanation : "Claude Fable 5's safety classifiers declined this request.";
851
+ const hint = fallbackModel ? `The configured fallback model (${fallbackModel}) also refused.` : `Retry with a fallback model such as anthropic-sdk/claude-opus-4-8.`;
852
+ return new Error(`Claude Fable 5 refused this request${categoryNote}. ${detail} ${hint}`);
853
+ }
698
854
  function mapFinishReason2(stopReason) {
699
855
  const unified = (() => {
700
856
  switch (stopReason) {
@@ -711,9 +867,10 @@ function mapFinishReason2(stopReason) {
711
867
  })();
712
868
  return { unified, raw: stopReason ?? undefined };
713
869
  }
870
+ var FALLBACK_BETAS_HEADER = "x-anthropic-sdk-fallback-betas";
714
871
  var BILLING_SYSTEM_BLOCK = {
715
872
  type: "text",
716
- text: "x-anthropic-billing-header: cc_version=2.1.154.cea; cc_entrypoint=sdk-cli; cch=00000;"
873
+ text: "x-anthropic-billing-header: cc_version=2.1.173.d11; cc_entrypoint=sdk-cli; cch=00000;"
717
874
  };
718
875
  var IDENTITY_SYSTEM_BLOCK = {
719
876
  type: "text",
@@ -847,6 +1004,9 @@ class AnthropicSDKModel {
847
1004
  }
848
1005
  if (anthropicOptions.metadata)
849
1006
  params.metadata = anthropicOptions.metadata;
1007
+ if (typeof anthropicOptions.refusalFallback === "string" && anthropicOptions.refusalFallback.length > 0) {
1008
+ params.fallbacks = [{ model: anthropicOptions.refusalFallback }];
1009
+ }
850
1010
  }
851
1011
  if (this.isOAuth && supportsContextManagement(this.apiModelId)) {
852
1012
  const edits = [];
@@ -861,16 +1021,19 @@ class AnthropicSDKModel {
861
1021
  params.context_management = { edits };
862
1022
  }
863
1023
  }
864
- return { params, warnings };
1024
+ const needsFallbackBetas = params.fallbacks != null || messages.some((msg) => Array.isArray(msg.content) && msg.content.some((block) => block?.type === "fallback"));
1025
+ return { params, warnings, needsFallbackBetas };
865
1026
  }
866
- buildRequestOptions(signal) {
1027
+ buildRequestOptions(signal, needsFallbackBetas) {
867
1028
  const opts = {};
868
1029
  if (signal)
869
1030
  opts.signal = signal;
1031
+ if (needsFallbackBetas)
1032
+ opts.headers = { [FALLBACK_BETAS_HEADER]: "1" };
870
1033
  return opts;
871
1034
  }
872
1035
  async doGenerate(options) {
873
- const { params, warnings } = this.buildParams(options);
1036
+ const { params, warnings, needsFallbackBetas } = this.buildParams(options);
874
1037
  if (!options.maxOutputTokens && params.max_tokens > 4096) {
875
1038
  params.max_tokens = 4096;
876
1039
  }
@@ -879,31 +1042,92 @@ class AnthropicSDKModel {
879
1042
  response = await this.client.messages.create({
880
1043
  ...params,
881
1044
  stream: false
882
- }, this.buildRequestOptions(options.abortSignal));
1045
+ }, this.buildRequestOptions(options.abortSignal, needsFallbackBetas));
883
1046
  } catch (error) {
884
1047
  handleApiError(error);
885
1048
  }
1049
+ const pausedBlocks = [];
1050
+ let continuations = 0;
1051
+ while (isPauseTurn(response.stop_reason) && continuations < MAX_PAUSE_TURN_CONTINUATIONS) {
1052
+ continuations++;
1053
+ pausedBlocks.push(...response.content);
1054
+ try {
1055
+ response = await this.client.messages.create({
1056
+ ...continuationParams(params, pausedBlocks),
1057
+ stream: false
1058
+ }, this.buildRequestOptions(options.abortSignal, needsFallbackBetas));
1059
+ } catch (error) {
1060
+ handleApiError(error);
1061
+ }
1062
+ }
1063
+ if (isPauseTurn(response.stop_reason)) {
1064
+ throw new Error(`Anthropic paused this turn (stop_reason "pause_turn") more than ` + `${MAX_PAUSE_TURN_CONTINUATIONS} times. Retry the request.`);
1065
+ }
1066
+ const responseContent = [...pausedBlocks, ...response.content];
1067
+ const fallbacksSent = Array.isArray(params.fallbacks) && params.fallbacks.length > 0;
1068
+ if (isRefusal(response.stop_reason)) {
1069
+ throw refusalError(response.stop_details, fallbacksSent ? params.fallbacks[0].model : undefined);
1070
+ }
1071
+ const fallbackBlock = responseContent.find((b) => b.type === "fallback");
1072
+ const sticky = !fallbackBlock && fallbacksSent && response.model && response.model !== this.apiModelId;
1073
+ let pendingMeta;
1074
+ if (fallbackBlock) {
1075
+ pendingMeta = {
1076
+ fallback: { from: fallbackBlock.from, to: fallbackBlock.to },
1077
+ servedBy: {
1078
+ from: fallbackBlock.from?.model ?? this.apiModelId,
1079
+ to: fallbackBlock.to?.model ?? "unknown",
1080
+ kind: "fallback"
1081
+ }
1082
+ };
1083
+ } else if (sticky) {
1084
+ pendingMeta = {
1085
+ servedBy: { from: this.apiModelId, to: response.model, kind: "sticky" }
1086
+ };
1087
+ }
886
1088
  const content = [];
887
- for (const block of response.content) {
1089
+ const takeMeta = () => {
1090
+ const meta = pendingMeta;
1091
+ pendingMeta = undefined;
1092
+ return meta;
1093
+ };
1094
+ let seenFallbackBlock = false;
1095
+ for (const block of responseContent) {
888
1096
  switch (block.type) {
889
- case "text":
890
- content.push({ type: "text", text: block.text });
1097
+ case "text": {
1098
+ const meta = seenFallbackBlock || sticky ? takeMeta() : undefined;
1099
+ content.push({
1100
+ type: "text",
1101
+ text: block.text,
1102
+ ...meta ? { providerMetadata: { anthropic: meta } } : {}
1103
+ });
891
1104
  break;
892
- case "tool_use":
1105
+ }
1106
+ case "tool_use": {
1107
+ const meta = seenFallbackBlock || sticky ? takeMeta() : undefined;
893
1108
  content.push({
894
1109
  type: "tool-call",
895
1110
  toolCallId: block.id,
896
1111
  toolName: toOpencodeToolName(block.name),
897
- input: JSON.stringify(block.input)
1112
+ input: JSON.stringify(block.input),
1113
+ ...meta ? { providerMetadata: { anthropic: meta } } : {}
898
1114
  });
899
1115
  break;
1116
+ }
900
1117
  default: {
901
1118
  const anyBlock = block;
902
- if (anyBlock.type === "thinking" && anyBlock.thinking) {
1119
+ if (anyBlock.type === "fallback") {
1120
+ seenFallbackBlock = true;
1121
+ } else if (anyBlock.type === "thinking" && anyBlock.thinking) {
1122
+ const extra = seenFallbackBlock || sticky ? takeMeta() : undefined;
1123
+ const meta = {
1124
+ ...anyBlock.signature ? { signature: anyBlock.signature } : {},
1125
+ ...extra
1126
+ };
903
1127
  content.push({
904
1128
  type: "reasoning",
905
1129
  text: anyBlock.thinking,
906
- providerMetadata: anyBlock.signature ? { anthropic: { signature: anyBlock.signature } } : undefined
1130
+ providerMetadata: Object.keys(meta).length > 0 ? { anthropic: meta } : undefined
907
1131
  });
908
1132
  }
909
1133
  break;
@@ -942,13 +1166,13 @@ class AnthropicSDKModel {
942
1166
  };
943
1167
  }
944
1168
  async doStream(options) {
945
- const { params, warnings } = this.buildParams(options);
1169
+ const { params, warnings, needsFallbackBetas } = this.buildParams(options);
946
1170
  let anthropicStream;
947
1171
  try {
948
1172
  anthropicStream = await this.client.messages.create({
949
1173
  ...params,
950
1174
  stream: true
951
- }, this.buildRequestOptions(options.abortSignal));
1175
+ }, this.buildRequestOptions(options.abortSignal, needsFallbackBetas));
952
1176
  } catch (error) {
953
1177
  if (error instanceof APIError && error.status === 400 && error.message?.includes("must end with a user message")) {
954
1178
  return {
@@ -976,7 +1200,14 @@ class AnthropicSDKModel {
976
1200
  }
977
1201
  handleApiError(error);
978
1202
  }
979
- const stream = convertStream(anthropicStream, this.modelId);
1203
+ const makeRequest = (p) => this.client.messages.create({ ...p, stream: true }, this.buildRequestOptions(options.abortSignal, needsFallbackBetas));
1204
+ const continued = withPauseTurnContinuation(anthropicStream, params, makeRequest);
1205
+ const fallbacksSent = Array.isArray(params.fallbacks) && params.fallbacks.length > 0;
1206
+ const stream = convertStream(continued, this.modelId, {
1207
+ apiModelId: this.apiModelId,
1208
+ fallbacksEnabled: fallbacksSent,
1209
+ fallbackModel: fallbacksSent ? params.fallbacks[0].model : undefined
1210
+ });
980
1211
  const wrappedStream = new ReadableStream({
981
1212
  async start(controller) {
982
1213
  controller.enqueue({
@@ -1099,13 +1330,14 @@ var API_TTL_MS = 5 * 60000;
1099
1330
 
1100
1331
  // src/index.ts
1101
1332
  var CLAUDE_CODE_SYSTEM_PROMPT = claudecode_system_default;
1102
- var CLAUDE_CODE_OPUS48_SYSTEM_PROMPT = claudecode_system_opus48_default;
1333
+ var CLAUDE_CODE_NEW_SYSTEM_PROMPT = claudecode_system_new_default;
1103
1334
  function selectClaudePromptForModel(modelId) {
1104
- if (modelId.includes("opus-4-8"))
1105
- return claudecode_system_opus48_default;
1335
+ if (modelId.includes("opus-4-8") || modelId.includes("fable-5")) {
1336
+ return claudecode_system_new_default;
1337
+ }
1106
1338
  return claudecode_system_default;
1107
1339
  }
1108
- var CLAUDE_CODE_VERSION = "2.1.154";
1340
+ var CLAUDE_CODE_VERSION = "2.1.173";
1109
1341
  var OAUTH_BETAS = [
1110
1342
  "claude-code-20250219",
1111
1343
  "oauth-2025-04-20",
@@ -1122,7 +1354,8 @@ var PROVIDER_ID = "anthropic-sdk";
1122
1354
  var API_KEY_COSTS = {
1123
1355
  haiku: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
1124
1356
  sonnet: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
1125
- opus: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }
1357
+ opus: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
1358
+ fable: { input: 10, output: 50, cache_read: 1, cache_write: 12.5 }
1126
1359
  };
1127
1360
  var ZERO_COST = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
1128
1361
  function buildPluginModels(isOAuth) {
@@ -1236,6 +1469,21 @@ function buildPluginModels(isOAuth) {
1236
1469
  },
1237
1470
  options: { effort: "medium" },
1238
1471
  ...opus47Variants
1472
+ },
1473
+ "claude-fable-5": {
1474
+ name: "Claude Fable 5",
1475
+ reasoning: true,
1476
+ tool_call: true,
1477
+ attachment: true,
1478
+ temperature: true,
1479
+ limit: { context: 1e6, output: 128000 },
1480
+ cost: cost("fable"),
1481
+ modalities: {
1482
+ input: ["text", "image", "pdf"],
1483
+ output: ["text"]
1484
+ },
1485
+ options: { effort: "medium", refusalFallback: "claude-opus-4-8" },
1486
+ ...opus47Variants
1239
1487
  }
1240
1488
  };
1241
1489
  }
@@ -1300,7 +1548,7 @@ function createAnthropicSDK(options = {}) {
1300
1548
  if (!betas.includes("effort-2025-11-24"))
1301
1549
  betas.push("effort-2025-11-24");
1302
1550
  }
1303
- if (model.includes("opus-4-8")) {
1551
+ if (model.includes("opus-4-8") || model.includes("fable-5")) {
1304
1552
  if (!betas.includes("mid-conversation-system-2026-04-07")) {
1305
1553
  betas.push("mid-conversation-system-2026-04-07");
1306
1554
  }
@@ -1308,6 +1556,19 @@ function createAnthropicSDK(options = {}) {
1308
1556
  reqHeaders.set("anthropic-beta", betas.join(","));
1309
1557
  init = { ...init, headers: reqHeaders };
1310
1558
  }
1559
+ {
1560
+ const reqHeaders = new Headers(init?.headers);
1561
+ if (reqHeaders.has(FALLBACK_BETAS_HEADER)) {
1562
+ reqHeaders.delete(FALLBACK_BETAS_HEADER);
1563
+ const betas = (reqHeaders.get("anthropic-beta") ?? "").split(",").map((beta) => beta.trim()).filter(Boolean);
1564
+ for (const beta of ["server-side-fallback-2026-06-01", "fallback-credit-2026-06-01"]) {
1565
+ if (!betas.includes(beta))
1566
+ betas.push(beta);
1567
+ }
1568
+ reqHeaders.set("anthropic-beta", betas.join(","));
1569
+ init = { ...init, headers: reqHeaders };
1570
+ }
1571
+ }
1311
1572
  const resp = await baseFetch(url, init);
1312
1573
  const h5 = resp.headers.get("anthropic-ratelimit-unified-5h-utilization");
1313
1574
  if (h5 != null) {
@@ -1356,10 +1617,22 @@ var anthropicSDKPlugin = async () => {
1356
1617
  if (!cfg.provider) {
1357
1618
  cfg.provider = {};
1358
1619
  }
1620
+ const userProvider = cfg.provider[PROVIDER_ID] ?? {};
1621
+ const defaultModels = buildPluginModels(isOAuth);
1622
+ const models = { ...defaultModels };
1623
+ for (const [modelID, userModel] of Object.entries(userProvider.models ?? {})) {
1624
+ const base = defaultModels[modelID];
1625
+ models[modelID] = base ? {
1626
+ ...base,
1627
+ ...userModel,
1628
+ options: { ...base.options, ...userModel?.options },
1629
+ variants: { ...base.variants, ...userModel?.variants }
1630
+ } : userModel;
1631
+ }
1359
1632
  cfg.provider[PROVIDER_ID] = {
1360
1633
  npm: PACKAGE_NAME,
1361
- models: buildPluginModels(isOAuth),
1362
- ...cfg.provider[PROVIDER_ID]
1634
+ ...userProvider,
1635
+ models
1363
1636
  };
1364
1637
  },
1365
1638
  "experimental.chat.system.transform": async (input, output) => {
@@ -1391,5 +1664,5 @@ export {
1391
1664
  createAnthropicSDK,
1392
1665
  anthropicSDKPlugin,
1393
1666
  CLAUDE_CODE_SYSTEM_PROMPT,
1394
- CLAUDE_CODE_OPUS48_SYSTEM_PROMPT
1667
+ CLAUDE_CODE_NEW_SYSTEM_PROMPT
1395
1668
  };