@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/build/model.d.ts CHANGED
@@ -2,6 +2,31 @@ import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3Genera
2
2
  import type Anthropic from "@anthropic-ai/sdk";
3
3
  type DoGenerateResult = LanguageModelV3GenerateResult;
4
4
  type DoStreamResult = LanguageModelV3StreamResult;
5
+ /**
6
+ * Build a clear error for a Fable 5 / Mythos-class safety refusal.
7
+ *
8
+ * Fable 5's safety classifiers can decline a request — the Messages API
9
+ * returns this as a *successful* HTTP 200 with `stop_reason: "refusal"`,
10
+ * empty `content`, and a `stop_details` object naming the policy area.
11
+ * Categories: cyber, bio, frontier_llm, reasoning_extraction (may be null).
12
+ *
13
+ * For now we surface this as an error so the user gets a clear message
14
+ * instead of an empty/confusing response. A future enhancement is to
15
+ * automatically retry on a fallback model (Claude Opus 4.8), per Anthropic's
16
+ * recommended pattern. See RESEARCH.md.
17
+ */
18
+ export declare function isRefusal(stopReason: string | null | undefined): boolean;
19
+ export declare function refusalError(stopDetails: any, fallbackModel?: string): Error;
20
+ /**
21
+ * Internal marker header: set on requests that need the server-side-fallback
22
+ * betas (request carries a `fallbacks` chain, or the conversation history
23
+ * echoes a `fallback` block from a previous fallback response). The fetch
24
+ * wrapper in index.ts translates it into the real `anthropic-beta` flags and
25
+ * strips it before the request leaves the process. This keeps the decision
26
+ * here — where the request is built — instead of sniffing the serialized
27
+ * body in the fetch wrapper (cf. RESEARCH.md Discovery #24/#25).
28
+ */
29
+ export declare const FALLBACK_BETAS_HEADER = "x-anthropic-sdk-fallback-betas";
5
30
  export declare class AnthropicSDKModel implements LanguageModelV3 {
6
31
  private client;
7
32
  private providerName;
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAG1B,6BAA6B,EAE7B,2BAA2B,EAE5B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAQ9C,KAAK,gBAAgB,GAAG,6BAA6B,CAAA;AACrD,KAAK,cAAc,GAAG,2BAA2B,CAAA;AA8HjD,qBAAa,iBAAkB,YAAW,eAAe;IAarD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IAdjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;IAErD,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGnC,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IASlC,OAAO,CAAC,WAAW;IA6KnB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAMrB,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsF1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA4E7E"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAG1B,6BAA6B,EAE7B,2BAA2B,EAE5B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAc9C,KAAK,gBAAgB,GAAG,6BAA6B,CAAA;AACrD,KAAK,cAAc,GAAG,2BAA2B,CAAA;AAkDjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExE;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAW5E;AAmBD;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,mCAAmC,CAAA;AA6DrE,qBAAa,iBAAkB,YAAW,eAAe;IAarD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IAdjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;IAErD,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGnC,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IASlC,OAAO,CAAC,WAAW;IAuMnB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAUrB,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqL1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA2F7E"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Auto-continuation for `stop_reason: "pause_turn"`.
3
+ *
4
+ * The Messages API pauses long-running turns and expects the client to
5
+ * resend the conversation — with the paused turn's partial assistant
6
+ * content appended — to let the model continue the same turn. Observed in
7
+ * the wild with Claude Fable 5 when the safety-refusal fallback hop runs
8
+ * long: the server returns `pause_turn` after ~1 output token and no
9
+ * content. Without auto-continuation that surfaces as a silent empty
10
+ * response (finish reason "unknown").
11
+ *
12
+ * The wrapper is transport-agnostic: it consumes streaming events,
13
+ * reconstructs the raw wire content blocks, and re-issues the request via
14
+ * the provided `request` callback whenever a segment ends with
15
+ * `pause_turn`. The paused segment's `message_delta` and `message_stop`
16
+ * events are suppressed so downstream conversion sees one continuous turn.
17
+ */
18
+ import type { MessageStreamEvent } from "@anthropic-ai/sdk/resources/messages";
19
+ export declare const MAX_PAUSE_TURN_CONTINUATIONS = 5;
20
+ type RawBlock = Record<string, any>;
21
+ export declare function isPauseTurn(stopReason: string | null | undefined): boolean;
22
+ /**
23
+ * Continuation request params: the original conversation with the paused
24
+ * turn's partial content appended as an assistant message. With no partial
25
+ * content (the observed Fable 5 case) the request is resent unchanged —
26
+ * an empty assistant content array would be rejected by the API.
27
+ */
28
+ export declare function continuationParams(params: Record<string, any>, blocks: RawBlock[]): Record<string, any>;
29
+ /**
30
+ * Wrap a stream of Anthropic events with pause_turn auto-continuation.
31
+ * After `maxContinuations` pauses the pause_turn `message_delta` is passed
32
+ * through so stream conversion can surface a clear error.
33
+ */
34
+ export declare function withPauseTurnContinuation(initial: AsyncIterable<MessageStreamEvent>, params: Record<string, any>, request: (params: Record<string, any>) => Promise<AsyncIterable<MessageStreamEvent>>, maxContinuations?: number): AsyncGenerator<MessageStreamEvent>;
35
+ export {};
36
+ //# sourceMappingURL=pause-turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pause-turn.d.ts","sourceRoot":"","sources":["../src/pause-turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAA;AAE9E,eAAO,MAAM,4BAA4B,IAAI,CAAA;AAE7C,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEnC,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE1E;AAoDD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,QAAQ,EAAE,GACjB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAMrB;AAED;;;;GAIG;AACH,wBAAuB,yBAAyB,CAC9C,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,EACpF,gBAAgB,SAA+B,GAC9C,cAAc,CAAC,kBAAkB,CAAC,CAuBpC"}
package/build/server.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) => {