@sammorrowdrums/mcpi-ai 0.85.1 → 0.85.3

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.
Files changed (62) hide show
  1. package/dist/api/anthropic-messages.d.ts +3 -1
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +270 -59
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  6. package/dist/api/azure-openai-responses.js +2 -0
  7. package/dist/api/azure-openai-responses.js.map +1 -1
  8. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  9. package/dist/api/bedrock-converse-stream.js +2 -0
  10. package/dist/api/bedrock-converse-stream.js.map +1 -1
  11. package/dist/api/google-generative-ai.d.ts.map +1 -1
  12. package/dist/api/google-generative-ai.js +2 -0
  13. package/dist/api/google-generative-ai.js.map +1 -1
  14. package/dist/api/google-vertex.d.ts.map +1 -1
  15. package/dist/api/google-vertex.js +2 -0
  16. package/dist/api/google-vertex.js.map +1 -1
  17. package/dist/api/mistral-conversations.d.ts.map +1 -1
  18. package/dist/api/mistral-conversations.js +2 -0
  19. package/dist/api/mistral-conversations.js.map +1 -1
  20. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  21. package/dist/api/openai-codex-responses.js +12 -2
  22. package/dist/api/openai-codex-responses.js.map +1 -1
  23. package/dist/api/openai-completions.d.ts +3 -1
  24. package/dist/api/openai-completions.d.ts.map +1 -1
  25. package/dist/api/openai-completions.js +23 -16
  26. package/dist/api/openai-completions.js.map +1 -1
  27. package/dist/api/openai-responses.d.ts.map +1 -1
  28. package/dist/api/openai-responses.js +12 -2
  29. package/dist/api/openai-responses.js.map +1 -1
  30. package/dist/api/provider-replay.d.ts +17 -0
  31. package/dist/api/provider-replay.d.ts.map +1 -0
  32. package/dist/api/provider-replay.js +2 -0
  33. package/dist/api/provider-replay.js.map +1 -0
  34. package/dist/api/transform-messages.d.ts.map +1 -1
  35. package/dist/api/transform-messages.js +5 -1
  36. package/dist/api/transform-messages.js.map +1 -1
  37. package/dist/image-models.generated.d.ts +30 -0
  38. package/dist/image-models.generated.d.ts.map +1 -1
  39. package/dist/image-models.generated.js +30 -0
  40. package/dist/image-models.generated.js.map +1 -1
  41. package/dist/providers/data/.manifest.json +1 -1
  42. package/dist/providers/data/amazon-bedrock.json +1 -1
  43. package/dist/providers/data/baseten.json +1 -1
  44. package/dist/providers/data/fireworks.json +1 -1
  45. package/dist/providers/data/github-copilot.json +1 -1
  46. package/dist/providers/data/huggingface.json +1 -1
  47. package/dist/providers/data/kimi-coding.json +1 -1
  48. package/dist/providers/data/nvidia.json +1 -1
  49. package/dist/providers/data/opencode-go.json +1 -1
  50. package/dist/providers/data/openrouter.json +1 -1
  51. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  52. package/dist/providers/data/qwen-token-plan.json +1 -1
  53. package/dist/providers/data/together.json +1 -1
  54. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  55. package/dist/types.d.ts +25 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/dist/utils/deferred-tools.d.ts +63 -4
  59. package/dist/utils/deferred-tools.d.ts.map +1 -1
  60. package/dist/utils/deferred-tools.js +111 -12
  61. package/dist/utils/deferred-tools.js.map +1 -1
  62. package/package.json +2 -2
@@ -1,7 +1,9 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- import type { SimpleStreamOptions, StreamFunction, StreamOptions } from "../types.ts";
2
+ import type { Model, SimpleStreamOptions, StreamFunction, StreamOptions } from "../types.ts";
3
3
  export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
4
4
  export type AnthropicThinkingDisplay = "summarized" | "omitted";
5
+ /** Record a hard endpoint rejection so this process stops sending deferred tools. */
6
+ export declare function disableToolReferencesForEndpoint(model: Model<"anthropic-messages">): void;
5
7
  export interface AnthropicOptions extends StreamOptions {
6
8
  /**
7
9
  * Enable extended thinking.
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAU1C,OAAO,KAAK,EAWX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAwIrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAkChE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AA+OD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA+RzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAwClF,CAAC","sourcesContent":["import Anthropic from \"@anthropic-ai/sdk\";\nimport type {\n\tCacheControlEphemeral,\n\tContentBlockParam,\n\tMessageCreateParamsStreaming,\n\tMessageParam,\n\tRawMessageStreamEvent,\n\tRefusalStopDetails,\n} from \"@anthropic-ai/sdk/resources/messages.js\";\nimport { calculateCost } from \"../models.ts\";\nimport type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tCacheRetention,\n\tContext,\n\tImageContent,\n\tMessage,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStopReason,\n\tStreamFunction,\n\tStreamOptions,\n\tTextContent,\n\tThinkingContent,\n\tTool,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\nimport { splitDeferredTools } from \"../utils/deferred-tools.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { parseJsonWithRepair, parseStreamingJson } from \"../utils/json-parse.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\nimport { getProviderEnvValue } from \"../utils/provider-env.ts\";\nimport { retryProviderRequest } from \"../utils/provider-retry.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\n\nimport { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from \"./constrained-sampling.ts\";\nimport { buildCopilotDynamicHeaders, hasCopilotVisionInput } from \"./github-copilot-headers.ts\";\nimport { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from \"./simple-options.ts\";\nimport { transformMessages } from \"./transform-messages.ts\";\n\n/**\n * Resolve cache retention preference.\n * Defaults to \"short\" and uses MCPI_CACHE_RETENTION when configured.\n */\nfunction resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {\n\tif (cacheRetention) {\n\t\treturn cacheRetention;\n\t}\n\tif (getProviderEnvValue(\"MCPI_CACHE_RETENTION\", env) === \"long\") {\n\t\treturn \"long\";\n\t}\n\treturn \"short\";\n}\n\nfunction getCacheControl(\n\tmodel: Model<\"anthropic-messages\">,\n\tcacheRetention?: CacheRetention,\n\tenv?: ProviderEnv,\n): { retention: CacheRetention; cacheControl?: CacheControlEphemeral } {\n\tconst retention = resolveCacheRetention(cacheRetention, env);\n\tif (retention === \"none\") {\n\t\treturn { retention };\n\t}\n\tconst ttl = retention === \"long\" && getAnthropicCompat(model).supportsLongCacheRetention ? \"1h\" : undefined;\n\treturn {\n\t\tretention,\n\t\tcacheControl: { type: \"ephemeral\", ...(ttl && { ttl }) },\n\t};\n}\n\n// Stealth mode: Mimic Claude Code's tool naming exactly\nconst claudeCodeVersion = \"2.1.75\";\n\n// Claude Code 2.x tool names (canonical casing)\n// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md\n// To update: https://github.com/badlogic/cchistory\nconst claudeCodeTools = [\n\t\"Read\",\n\t\"Write\",\n\t\"Edit\",\n\t\"Bash\",\n\t\"Grep\",\n\t\"Glob\",\n\t\"AskUserQuestion\",\n\t\"EnterPlanMode\",\n\t\"ExitPlanMode\",\n\t\"KillShell\",\n\t\"NotebookEdit\",\n\t\"Skill\",\n\t\"Task\",\n\t\"TaskOutput\",\n\t\"TodoWrite\",\n\t\"WebFetch\",\n\t\"WebSearch\",\n];\n\nconst ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));\n\n// Convert tool name to CC canonical casing if it matches (case-insensitive)\nconst toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;\nconst fromClaudeCodeName = (name: string, tools?: Tool[]) => {\n\tif (tools && tools.length > 0) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tconst matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);\n\t\tif (matchedTool) return matchedTool.name;\n\t}\n\treturn name;\n};\n\n/**\n * Convert content blocks to Anthropic API format\n */\nfunction convertContentBlocks(content: (TextContent | ImageContent)[]):\n\t| string\n\t| Array<\n\t\t\t| { type: \"text\"; text: string }\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\tsource: {\n\t\t\t\t\t\ttype: \"base64\";\n\t\t\t\t\t\tmedia_type: \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\t\t\t\t\t\tdata: string;\n\t\t\t\t\t};\n\t\t\t }\n\t > {\n\t// If only text blocks, return as concatenated string for simplicity\n\tconst hasImages = content.some((c) => c.type === \"image\");\n\tif (!hasImages) {\n\t\treturn sanitizeSurrogates(content.map((c) => (c as TextContent).text).join(\"\\n\"));\n\t}\n\n\t// If we have images, convert to content block array\n\tconst blocks = content.map((block) => {\n\t\tif (block.type === \"text\") {\n\t\t\treturn {\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\ttype: \"image\" as const,\n\t\t\tsource: {\n\t\t\t\ttype: \"base64\" as const,\n\t\t\t\tmedia_type: block.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\tdata: block.data,\n\t\t\t},\n\t\t};\n\t});\n\n\t// If only images (no text), add placeholder text block\n\tconst hasText = blocks.some((b) => b.type === \"text\");\n\tif (!hasText) {\n\t\tblocks.unshift({\n\t\t\ttype: \"text\" as const,\n\t\t\ttext: \"(see attached image)\",\n\t\t});\n\t}\n\n\treturn blocks;\n}\n\nexport type AnthropicEffort = \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\nexport type AnthropicThinkingDisplay = \"summarized\" | \"omitted\";\n\nconst FINE_GRAINED_TOOL_STREAMING_BETA = \"fine-grained-tool-streaming-2025-05-14\";\nconst INTERLEAVED_THINKING_BETA = \"interleaved-thinking-2025-05-14\";\n\nfunction getAnthropicCompat(\n\tmodel: Model<\"anthropic-messages\">,\n): Required<Omit<AnthropicMessagesCompat, \"forceAdaptiveThinking\">> {\n\treturn {\n\t\tsupportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,\n\t\tsupportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,\n\t\tsendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? false,\n\t\tsupportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,\n\t\tsupportsTemperature: model.compat?.supportsTemperature ?? true,\n\t\tallowEmptySignature: model.compat?.allowEmptySignature ?? false,\n\t\tsupportsStrictTools: model.compat?.supportsStrictTools ?? false,\n\t\tsupportsToolReferences: model.compat?.supportsToolReferences ?? defaultSupportsToolReferences(model),\n\t};\n}\n\n/**\n * Default for `supportsToolReferences`: first-party Anthropic models except\n * Haiku (rejects client-side tool_reference blocks) and models that predate\n * tool search (Claude 3.x, Opus/Sonnet 4.0, Opus 4.1).\n */\nfunction defaultSupportsToolReferences(model: Model<\"anthropic-messages\">): boolean {\n\tif (model.provider !== \"anthropic\" || model.id.includes(\"haiku\")) return false;\n\tconst version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\\d+)(?:-(\\d+))?(?:-|$)/);\n\tif (!version) return false;\n\tconst major = Number(version[1]);\n\tconst minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;\n\treturn major > 4 || (major === 4 && minor >= 5);\n}\n\nexport interface AnthropicOptions extends StreamOptions {\n\t/**\n\t * Enable extended thinking.\n\t * For adaptive thinking models: the model decides when/how much to think.\n\t * For older models: uses budget-based thinking with thinkingBudgetTokens.\n\t * Default: undefined (thinking is omitted unless `streamSimple()` maps\n\t * a simple reasoning level to this option, or callers set it explicitly).\n\t */\n\tthinkingEnabled?: boolean;\n\t/**\n\t * Token budget for extended thinking (older models only).\n\t * Ignored for adaptive thinking models.\n\t * Default: 1024 when `thinkingEnabled` is true and no budget is provided.\n\t */\n\tthinkingBudgetTokens?: number;\n\t/**\n\t * Effort level for adaptive thinking models.\n\t * Controls how much thinking Claude allocates:\n\t * - \"max\": Always thinks with no constraints (Opus 4.6 and newer, Sonnet 4.6 and newer, Fable 5)\n\t * - \"xhigh\": Highest reasoning level (Opus 4.7 and newer including Opus 5, Sonnet 5, Fable 5)\n\t * - \"high\": Always thinks, deep reasoning\n\t * - \"medium\": Moderate thinking, may skip for simple queries\n\t * - \"low\": Minimal thinking, skips for simple tasks\n\t * Ignored for older models.\n\t * Default: omitted unless `streamSimple()` maps a simple reasoning\n\t * level to this option.\n\t */\n\teffort?: AnthropicEffort;\n\t/**\n\t * Controls how thinking content is returned in API responses.\n\t * - \"summarized\": Thinking blocks contain summarized thinking text.\n\t * - \"omitted\": Thinking blocks return an empty thinking field; the encrypted\n\t * signature still travels back for multi-turn continuity. Use for faster\n\t * time-to-first-text-token when your UI does not surface thinking.\n\t *\n\t * Note: Anthropic's API default for Claude Opus 4.7 and Claude Mythos Preview\n\t * is \"omitted\". We default to \"summarized\" here to keep behavior consistent\n\t * with older Claude 4 models. Set this explicitly to \"omitted\" to opt in.\n\t * Default: \"summarized\" when thinking is enabled.\n\t */\n\tthinkingDisplay?: AnthropicThinkingDisplay;\n\t/**\n\t * Whether to request the interleaved thinking beta header for non-adaptive\n\t * thinking models. Adaptive thinking models have interleaved thinking built in,\n\t * so the header is skipped for them regardless of this setting.\n\t * Default: true.\n\t */\n\tinterleavedThinking?: boolean;\n\t/**\n\t * Anthropic tool choice behavior. String values map to Anthropic's built-in\n\t * choices; `{ type: \"tool\", name }` forces a specific tool.\n\t * Default: omitted (Anthropic default behavior, currently equivalent to auto).\n\t */\n\ttoolChoice?: \"auto\" | \"any\" | \"none\" | { type: \"tool\"; name: string };\n\t/**\n\t * Pre-built Anthropic client instance. When provided, skips internal client\n\t * construction entirely. Use this to inject alternative SDK clients such as\n\t * `AnthropicVertex` that shares the same messaging API.\n\t */\n\tclient?: Anthropic;\n}\n\nfunction mergeHeaders(...headerSources: (ProviderHeaders | undefined)[]): ProviderHeaders {\n\tconst merged: ProviderHeaders = {};\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\treturn merged;\n}\n\nfunction mergeClientHeaders(\n\tmodel: Model<\"anthropic-messages\">,\n\t...headerSources: (ProviderHeaders | undefined)[]\n): ProviderHeaders {\n\tconst merged = mergeHeaders(...headerSources);\n\tif (model.provider === \"kimi-coding\") {\n\t\tfor (const name of Object.keys(merged)) {\n\t\t\tif (name.toLowerCase() === \"user-agent\") delete merged[name];\n\t\t}\n\t\tmerged[\"User-Agent\"] = getPiUserAgent();\n\t}\n\treturn merged;\n}\n\nfunction hasHeader(headers: ProviderHeaders | undefined, name: string): boolean {\n\tif (!headers) return false;\n\tconst expected = name.toLowerCase();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (key.toLowerCase() === expected && value !== null && value.trim().length > 0) return true;\n\t}\n\treturn false;\n}\n\nfunction assertRequestAuth(provider: string, apiKey: string | undefined, headers: ProviderHeaders | undefined): void {\n\tif (apiKey) return;\n\tif (\n\t\thasHeader(headers, \"authorization\") ||\n\t\thasHeader(headers, \"x-api-key\") ||\n\t\thasHeader(headers, \"cf-aig-authorization\")\n\t) {\n\t\treturn;\n\t}\n\tthrow new Error(`No API key for provider: ${provider}`);\n}\n\ninterface ServerSentEvent {\n\tevent: string | null;\n\tdata: string;\n\traw: string[];\n}\n\ninterface SseDecoderState {\n\tevent: string | null;\n\tdata: string[];\n\traw: string[];\n}\n\nconst ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([\n\t\"message_start\",\n\t\"message_delta\",\n\t\"message_stop\",\n\t\"content_block_start\",\n\t\"content_block_delta\",\n\t\"content_block_stop\",\n]);\n\nfunction flushSseEvent(state: SseDecoderState): ServerSentEvent | null {\n\tif (!state.event && state.data.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst event: ServerSentEvent = {\n\t\tevent: state.event,\n\t\tdata: state.data.join(\"\\n\"),\n\t\traw: [...state.raw],\n\t};\n\tstate.event = null;\n\tstate.data = [];\n\tstate.raw = [];\n\treturn event;\n}\n\nfunction decodeSseLine(line: string, state: SseDecoderState): ServerSentEvent | null {\n\tif (line === \"\") {\n\t\treturn flushSseEvent(state);\n\t}\n\n\tstate.raw.push(line);\n\tif (line.startsWith(\":\")) {\n\t\treturn null;\n\t}\n\n\tconst delimiterIndex = line.indexOf(\":\");\n\tconst fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);\n\tlet value = delimiterIndex === -1 ? \"\" : line.slice(delimiterIndex + 1);\n\tif (value.startsWith(\" \")) {\n\t\tvalue = value.slice(1);\n\t}\n\n\tif (fieldName === \"event\") {\n\t\tstate.event = value;\n\t} else if (fieldName === \"data\") {\n\t\tstate.data.push(value);\n\t}\n\n\treturn null;\n}\n\nfunction nextLineBreakIndex(text: string): number {\n\tconst carriageReturnIndex = text.indexOf(\"\\r\");\n\tconst newlineIndex = text.indexOf(\"\\n\");\n\tif (carriageReturnIndex === -1) {\n\t\treturn newlineIndex;\n\t}\n\tif (newlineIndex === -1) {\n\t\treturn carriageReturnIndex;\n\t}\n\treturn Math.min(carriageReturnIndex, newlineIndex);\n}\n\nfunction consumeLine(text: string): { line: string; rest: string } | null {\n\tconst lineBreakIndex = nextLineBreakIndex(text);\n\tif (lineBreakIndex === -1) {\n\t\treturn null;\n\t}\n\n\tlet nextIndex = lineBreakIndex + 1;\n\tif (text[lineBreakIndex] === \"\\r\" && text[nextIndex] === \"\\n\") {\n\t\tnextIndex += 1;\n\t}\n\n\treturn {\n\t\tline: text.slice(0, lineBreakIndex),\n\t\trest: text.slice(nextIndex),\n\t};\n}\n\nasync function* iterateSseMessages(\n\tbody: ReadableStream<Uint8Array>,\n\tsignal?: AbortSignal,\n): AsyncGenerator<ServerSentEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst state: SseDecoderState = { event: null, data: [], raw: [] };\n\tlet buffer = \"\";\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\tlet consumed = consumeLine(buffer);\n\t\t\twhile (consumed) {\n\t\t\t\tbuffer = consumed.rest;\n\t\t\t\tconst event = decodeSseLine(consumed.line, state);\n\t\t\t\tif (event) {\n\t\t\t\t\tyield event;\n\t\t\t\t}\n\t\t\t\tconsumed = consumeLine(buffer);\n\t\t\t}\n\t\t}\n\n\t\tbuffer += decoder.decode();\n\t\tlet consumed = consumeLine(buffer);\n\t\twhile (consumed) {\n\t\t\tbuffer = consumed.rest;\n\t\t\tconst event = decodeSseLine(consumed.line, state);\n\t\t\tif (event) {\n\t\t\t\tyield event;\n\t\t\t}\n\t\t\tconsumed = consumeLine(buffer);\n\t\t}\n\n\t\tif (buffer.length > 0) {\n\t\t\tconst event = decodeSseLine(buffer, state);\n\t\t\tif (event) {\n\t\t\t\tyield event;\n\t\t\t}\n\t\t}\n\n\t\tconst trailingEvent = flushSseEvent(state);\n\t\tif (trailingEvent) {\n\t\t\tyield trailingEvent;\n\t\t}\n\t} finally {\n\t\treader.releaseLock();\n\t}\n}\n\nasync function* iterateAnthropicEvents(\n\tresponse: Response,\n\tsignal?: AbortSignal,\n): AsyncGenerator<RawMessageStreamEvent> {\n\tif (!response.body) {\n\t\tthrow new Error(\"Attempted to iterate over an Anthropic response with no body\");\n\t}\n\n\tlet sawMessageStart = false;\n\tlet sawMessageEnd = false;\n\n\tfor await (const sse of iterateSseMessages(response.body, signal)) {\n\t\tif (sse.event === \"error\") {\n\t\t\tthrow new Error(sse.data);\n\t\t}\n\n\t\tif (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? \"\")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst event = parseJsonWithRepair<RawMessageStreamEvent>(sse.data);\n\t\t\tif (event.type === \"message_start\") {\n\t\t\t\tsawMessageStart = true;\n\t\t\t} else if (event.type === \"message_stop\") {\n\t\t\t\tsawMessageEnd = true;\n\t\t\t}\n\t\t\tyield event;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join(\"\\\\n\")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (sawMessageStart && !sawMessageEnd) {\n\t\tthrow new Error(\"Anthropic stream ended before message_stop\");\n\t}\n}\n\nexport const stream: StreamFunction<\"anthropic-messages\", AnthropicOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: AnthropicOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"pending\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tlet client: Anthropic;\n\t\t\tlet isOAuth: boolean;\n\n\t\t\tif (options?.client) {\n\t\t\t\tclient = options.client;\n\t\t\t\tisOAuth = false;\n\t\t\t} else {\n\t\t\t\tconst apiKey = options?.apiKey;\n\t\t\t\tassertRequestAuth(model.provider, apiKey, options?.headers);\n\n\t\t\t\tlet copilotDynamicHeaders: Record<string, string> | undefined;\n\t\t\t\tif (model.provider === \"github-copilot\") {\n\t\t\t\t\tconst hasImages = hasCopilotVisionInput(context.messages);\n\t\t\t\t\tcopilotDynamicHeaders = buildCopilotDynamicHeaders({\n\t\t\t\t\t\tmessages: context.messages,\n\t\t\t\t\t\thasImages,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);\n\t\t\t\tconst cacheSessionId = cacheRetention === \"none\" ? undefined : options?.sessionId;\n\n\t\t\t\tconst created = createClient(\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\toptions?.interleavedThinking ?? true,\n\t\t\t\t\tshouldUseFineGrainedToolStreamingBeta(model, context),\n\t\t\t\t\toptions?.headers,\n\t\t\t\t\toptions?.fetch,\n\t\t\t\t\tcopilotDynamicHeaders,\n\t\t\t\t\tcacheSessionId,\n\t\t\t\t);\n\t\t\t\tclient = created.client;\n\t\t\t\tisOAuth = created.isOAuthToken;\n\t\t\t}\n\t\t\tlet params = buildParams(model, context, isOAuth, options);\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as MessageCreateParamsStreaming;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\tmaxRetries: 0,\n\t\t\t};\n\t\t\tconst response = await retryProviderRequest(\n\t\t\t\t() => client.messages.create({ ...params, stream: true }, requestOptions).asResponse(),\n\t\t\t\t{\n\t\t\t\t\tmaxRetries: options?.maxRetries,\n\t\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs,\n\t\t\t\t\tsignal: options?.signal,\n\t\t\t\t},\n\t\t\t);\n\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\ttype Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number };\n\t\t\tconst blocks = output.content as Block[];\n\n\t\t\tfor await (const event of iterateAnthropicEvents(response, options?.signal)) {\n\t\t\t\tif (event.type === \"message_start\") {\n\t\t\t\t\toutput.responseId = event.message.id;\n\t\t\t\t\t// Capture initial token usage from message_start event\n\t\t\t\t\t// This ensures we have input token counts even if the stream is aborted early\n\t\t\t\t\toutput.usage.input = event.message.usage.input_tokens || 0;\n\t\t\t\t\toutput.usage.output = event.message.usage.output_tokens || 0;\n\t\t\t\t\toutput.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;\n\t\t\t\t\toutput.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;\n\t\t\t\t\toutput.usage.cacheWrite1h = event.message.usage.cache_creation?.ephemeral_1h_input_tokens || 0;\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t} else if (event.type === \"content_block_start\") {\n\t\t\t\t\tif (event.content_block.type === \"text\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: event.content_block.text ?? \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: event.content_block.thinking ?? \"\",\n\t\t\t\t\t\t\tthinkingSignature: event.content_block.signature ?? \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"redacted_thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: \"[Reasoning redacted]\",\n\t\t\t\t\t\t\tthinkingSignature: event.content_block.data,\n\t\t\t\t\t\t\tredacted: true,\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"tool_use\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\t\tid: event.content_block.id,\n\t\t\t\t\t\t\tname: isOAuth\n\t\t\t\t\t\t\t\t? fromClaudeCodeName(event.content_block.name, context.tools)\n\t\t\t\t\t\t\t\t: event.content_block.name,\n\t\t\t\t\t\t\targuments: (event.content_block.input as Record<string, any>) ?? {},\n\t\t\t\t\t\t\tpartialJson: \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_delta\") {\n\t\t\t\t\tif (event.delta.type === \"text_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"text\") {\n\t\t\t\t\t\t\tblock.text += event.delta.text;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"thinking_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinking += event.delta.thinking;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"input_json_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.partialJson += event.delta.partial_json;\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.partial_json,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"signature_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinkingSignature = block.thinkingSignature || \"\";\n\t\t\t\t\t\t\tblock.thinkingSignature += event.delta.signature;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_stop\") {\n\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\tif (block) {\n\t\t\t\t\t\tdelete (block as any).index;\n\t\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\t// Finalize in-place and strip the scratch buffer so replay only\n\t\t\t\t\t\t\t// carries parsed arguments.\n\t\t\t\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"message_delta\") {\n\t\t\t\t\tif (event.delta.stop_reason) {\n\t\t\t\t\t\toutput.rawStopReason = event.delta.stop_reason;\n\t\t\t\t\t\tconst stopReasonResult = mapStopReason(event.delta.stop_reason, event.delta.stop_details);\n\t\t\t\t\t\toutput.stopReason = stopReasonResult.stopReason;\n\t\t\t\t\t\tif (stopReasonResult.errorMessage) {\n\t\t\t\t\t\t\toutput.errorMessage = stopReasonResult.errorMessage;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Only update usage fields if present (not null).\n\t\t\t\t\t// Preserves input_tokens from message_start when proxies omit it in message_delta.\n\t\t\t\t\tif (event.usage) {\n\t\t\t\t\t\tif (event.usage.input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.input = event.usage.input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.output_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.output = event.usage.output_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.cache_read_input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.cacheRead = event.usage.cache_read_input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.cache_creation_input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.cacheWrite = event.usage.cache_creation_input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the\n\t\t\t\t\t\t// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from\n\t\t\t\t\t\t// its Usage type, so read it through a narrow cast. Verified against the live API.\n\t\t\t\t\t\tconst thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })\n\t\t\t\t\t\t\t.output_tokens_details?.thinking_tokens;\n\t\t\t\t\t\tif (thinkingTokens != null) {\n\t\t\t\t\t\t\toutput.usage.reasoning = thinkingTokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"pending\") {\n\t\t\t\tthrow new Error(\"Anthropic stream ended without a stop reason\");\n\t\t\t}\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(output.errorMessage || \"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\n/**\n * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.\n * Note: effort \"max\" is available on all adaptive-thinking Claude models, while native\n * \"xhigh\" is only available on Opus 4.7/4.8, Sonnet 5, and Fable 5.\n */\nfunction mapThinkingLevelToEffort(\n\tmodel: Model<\"anthropic-messages\">,\n\tlevel: SimpleStreamOptions[\"reasoning\"],\n): AnthropicEffort {\n\tconst mapped = level ? model.thinkingLevelMap?.[level] : undefined;\n\tif (typeof mapped === \"string\") return mapped as AnthropicEffort;\n\n\tswitch (level) {\n\t\tcase \"minimal\":\n\t\tcase \"low\":\n\t\t\treturn \"low\";\n\t\tcase \"medium\":\n\t\t\treturn \"medium\";\n\t\tcase \"high\":\n\t\t\treturn \"high\";\n\t\tdefault:\n\t\t\treturn \"high\";\n\t}\n}\n\nexport const streamSimple: StreamFunction<\"anthropic-messages\", SimpleStreamOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tassertRequestAuth(model.provider, options?.apiKey, options?.headers);\n\n\tconst base = buildBaseOptions(model, context, options, options?.apiKey);\n\tif (!options?.reasoning) {\n\t\treturn stream(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions);\n\t}\n\n\t// For models with adaptive thinking: use an effort level.\n\t// For older models: use budget-based thinking.\n\tif (model.compat?.forceAdaptiveThinking === true) {\n\t\tconst effort = mapThinkingLevelToEffort(model, options.reasoning);\n\t\treturn stream(model, context, {\n\t\t\t...base,\n\t\t\tthinkingEnabled: true,\n\t\t\teffort,\n\t\t} satisfies AnthropicOptions);\n\t}\n\n\t// Undefined means the caller did not request an output cap; let the helper use the model cap.\n\t// Do not coerce to 0 here, or the thinking budget would become the entire max_tokens value.\n\tconst adjusted = adjustMaxTokensForThinking(\n\t\tbase.maxTokens,\n\t\tmodel.maxTokens,\n\t\toptions.reasoning,\n\t\toptions.thinkingBudgets,\n\t);\n\n\tconst maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\tmaxTokens,\n\t\tthinkingEnabled: true,\n\t\tthinkingBudgetTokens: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),\n\t} satisfies AnthropicOptions);\n};\n\nfunction isOAuthToken(apiKey: string): boolean {\n\treturn apiKey.includes(\"sk-ant-oat\");\n}\n\nfunction createClient(\n\tmodel: Model<\"anthropic-messages\">,\n\tapiKey: string | undefined,\n\tinterleavedThinking: boolean,\n\tuseFineGrainedToolStreamingBeta: boolean,\n\toptionsHeaders?: ProviderHeaders,\n\tfetch?: typeof globalThis.fetch,\n\tdynamicHeaders?: Record<string, string>,\n\tsessionId?: string,\n): { client: Anthropic; isOAuthToken: boolean } {\n\t// Adaptive thinking models have interleaved thinking built in, so skip the beta header.\n\tconst needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;\n\tconst betaFeatures: string[] = [];\n\tif (useFineGrainedToolStreamingBeta) {\n\t\tbetaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA);\n\t}\n\tif (needsInterleavedBeta) {\n\t\tbetaFeatures.push(INTERLEAVED_THINKING_BETA);\n\t}\n\n\t// Copilot: Bearer auth, selective betas.\n\tif (model.provider === \"github-copilot\") {\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey ?? null,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tfetch,\n\t\t\tdefaultHeaders: mergeClientHeaders(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t...(betaFeatures.length > 0 ? { \"anthropic-beta\": betaFeatures.join(\",\") } : {}),\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\tdynamicHeaders,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: false };\n\t}\n\n\t// OAuth: Bearer auth, Claude Code identity headers\n\tif (apiKey && isOAuthToken(apiKey)) {\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tfetch,\n\t\t\tdefaultHeaders: mergeClientHeaders(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t\"anthropic-beta\": [\"claude-code-20250219\", \"oauth-2025-04-20\", ...betaFeatures].join(\",\"),\n\t\t\t\t\t\"user-agent\": `claude-cli/${claudeCodeVersion}`,\n\t\t\t\t\t\"x-app\": \"cli\",\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: true };\n\t}\n\n\t// API key or header-owned auth.\n\tconst sessionAffinityHeaders: ProviderHeaders =\n\t\tsessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { \"x-session-affinity\": sessionId } : {};\n\tconst defaultHeaders = mergeClientHeaders(\n\t\tmodel,\n\t\t{\n\t\t\taccept: \"application/json\",\n\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t...(betaFeatures.length > 0 ? { \"anthropic-beta\": betaFeatures.join(\",\") } : {}),\n\t\t},\n\t\tsessionAffinityHeaders,\n\t\tmodel.headers,\n\t\toptionsHeaders,\n\t);\n\tconst client = new Anthropic({\n\t\tapiKey: apiKey ?? null,\n\t\tauthToken: null,\n\t\tbaseURL: model.baseUrl,\n\t\tdangerouslyAllowBrowser: true,\n\t\tfetch,\n\t\tdefaultHeaders,\n\t});\n\n\treturn { client, isOAuthToken: false };\n}\n\nfunction buildParams(\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\tisOAuthToken: boolean,\n\toptions?: AnthropicOptions,\n): MessageCreateParamsStreaming {\n\tconst { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);\n\tconst compat = getAnthropicCompat(model);\n\tconst transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);\n\tconst normalizeToolName = isOAuthToken ? toClaudeCodeName : (name: string) => name;\n\tconst toolPlacement = splitDeferredTools(\n\t\t{ ...context, messages: transformedMessages },\n\t\tcompat.supportsToolReferences,\n\t\tnormalizeToolName,\n\t);\n\tlet immediateTools = toolPlacement.immediate;\n\tlet deferredTools = [...toolPlacement.deferred.values()];\n\tif (immediateTools.length === 0 && deferredTools.length > 0) {\n\t\timmediateTools = deferredTools;\n\t\tdeferredTools = [];\n\t}\n\tconst deferredToolNames = new Set(deferredTools.map((tool) => normalizeToolName(tool.name)));\n\tconst params: MessageCreateParamsStreaming = {\n\t\tmodel: model.id,\n\t\tmessages: convertMessages(\n\t\t\ttransformedMessages,\n\t\t\tisOAuthToken,\n\t\t\tcacheControl,\n\t\t\tcompat.allowEmptySignature,\n\t\t\tdeferredToolNames,\n\t\t\tnormalizeToolName,\n\t\t),\n\t\tmax_tokens: options?.maxTokens ?? model.maxTokens,\n\t\tstream: true,\n\t};\n\n\t// For OAuth tokens, we MUST include Claude Code identity\n\tif (isOAuthToken) {\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: \"You are Claude Code, Anthropic's official CLI for Claude.\",\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t\tif (context.systemPrompt) {\n\t\t\tparams.system.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t});\n\t\t}\n\t} else if (context.systemPrompt) {\n\t\t// Add cache control to system prompt for non-OAuth tokens\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t}\n\n\t// Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.\n\tif (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {\n\t\tparams.temperature = options.temperature;\n\t}\n\n\tif (immediateTools.length > 0 || deferredTools.length > 0) {\n\t\tparams.tools = [\n\t\t\t...convertTools(\n\t\t\t\timmediateTools,\n\t\t\t\tisOAuthToken,\n\t\t\t\tcompat.supportsEagerToolInputStreaming,\n\t\t\t\tcompat.supportsStrictTools,\n\t\t\t\tcompat.supportsCacheControlOnTools ? cacheControl : undefined,\n\t\t\t),\n\t\t\t...convertTools(\n\t\t\t\tdeferredTools,\n\t\t\t\tisOAuthToken,\n\t\t\t\tcompat.supportsEagerToolInputStreaming,\n\t\t\t\tcompat.supportsStrictTools,\n\t\t\t\tundefined,\n\t\t\t\ttrue,\n\t\t\t),\n\t\t];\n\t}\n\n\t// Configure thinking mode: adaptive, budget-based, or explicitly disabled.\n\tif (model.reasoning) {\n\t\tif (options?.thinkingEnabled) {\n\t\t\t// Default to \"summarized\" so Opus 4.7 and Mythos Preview behave like\n\t\t\t// older Claude 4 models (whose API default is also \"summarized\").\n\t\t\tconst display: AnthropicThinkingDisplay = options.thinkingDisplay ?? \"summarized\";\n\t\t\tif (model.compat?.forceAdaptiveThinking === true) {\n\t\t\t\t// Adaptive thinking: Claude decides when and how much to think.\n\t\t\t\tparams.thinking = { type: \"adaptive\", display };\n\t\t\t\tif (options.effort) {\n\t\t\t\t\t// The Anthropic SDK types can lag newly supported effort values such as \"xhigh\".\n\t\t\t\t\tparams.output_config =\n\t\t\t\t\t\toptions.effort === \"xhigh\"\n\t\t\t\t\t\t\t? ({ effort: options.effort } as unknown as NonNullable<\n\t\t\t\t\t\t\t\t\tMessageCreateParamsStreaming[\"output_config\"]\n\t\t\t\t\t\t\t\t>)\n\t\t\t\t\t\t\t: { effort: options.effort };\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Budget-based thinking for older models\n\t\t\t\tparams.thinking = {\n\t\t\t\t\ttype: \"enabled\",\n\t\t\t\t\tbudget_tokens: options.thinkingBudgetTokens || 1024,\n\t\t\t\t\tdisplay,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.thinking = { type: \"disabled\" };\n\t\t}\n\t}\n\n\tif (options?.metadata) {\n\t\tconst userId = options.metadata.user_id;\n\t\tif (typeof userId === \"string\") {\n\t\t\tparams.metadata = { user_id: userId };\n\t\t}\n\t}\n\n\tif (options?.toolChoice) {\n\t\tif (typeof options.toolChoice === \"string\") {\n\t\t\tparams.tool_choice = { type: options.toolChoice };\n\t\t} else {\n\t\t\tparams.tool_choice = options.toolChoice;\n\t\t}\n\t}\n\n\treturn params;\n}\n\n// Normalize tool call IDs to match Anthropic's required pattern and length\nfunction normalizeToolCallId(id: string): string {\n\treturn id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n}\n\nfunction convertToolResult(\n\tmsg: ToolResultMessage,\n\tisOAuthToken: boolean,\n\tdeferredToolNames: ReadonlySet<string>,\n\tloadedToolNames: Set<string>,\n\tnormalizeToolName: (name: string) => string,\n): { toolResult: ContentBlockParam; siblingContent: ContentBlockParam[] } {\n\tconst references: Array<{ type: \"tool_reference\"; tool_name: string }> = [];\n\tfor (const name of msg.addedToolNames ?? []) {\n\t\tconst normalizedName = normalizeToolName(name);\n\t\tif (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName)) continue;\n\t\tloadedToolNames.add(normalizedName);\n\t\treferences.push({\n\t\t\ttype: \"tool_reference\",\n\t\t\ttool_name: isOAuthToken ? toClaudeCodeName(name) : name,\n\t\t});\n\t}\n\tconst convertedContent = convertContentBlocks(msg.content);\n\t// Anthropic rejects tool references mixed with ordinary tool-result content.\n\treturn {\n\t\ttoolResult: {\n\t\t\ttype: \"tool_result\",\n\t\t\ttool_use_id: msg.toolCallId,\n\t\t\tcontent: references.length > 0 ? references : convertedContent,\n\t\t\tis_error: msg.isError,\n\t\t},\n\t\tsiblingContent:\n\t\t\treferences.length === 0\n\t\t\t\t? []\n\t\t\t\t: typeof convertedContent === \"string\"\n\t\t\t\t\t? [{ type: \"text\", text: convertedContent }]\n\t\t\t\t\t: convertedContent,\n\t};\n}\n\nfunction convertMessages(\n\ttransformedMessages: Message[],\n\tisOAuthToken: boolean,\n\tcacheControl?: CacheControlEphemeral,\n\tallowEmptySignature = false,\n\tdeferredToolNames: ReadonlySet<string> = new Set(),\n\tnormalizeToolName: (name: string) => string = (name) => name,\n): MessageParam[] {\n\tconst params: MessageParam[] = [];\n\tconst loadedToolNames = new Set<string>();\n\n\tfor (let i = 0; i < transformedMessages.length; i++) {\n\t\tconst msg = transformedMessages[i];\n\n\t\tif (msg.role === \"user\") {\n\t\t\tif (typeof msg.content === \"string\") {\n\t\t\t\tif (msg.content.trim().length > 0) {\n\t\t\t\t\tparams.push({\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: sanitizeSurrogates(msg.content),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst blocks: ContentBlockParam[] = msg.content.map((item) => {\n\t\t\t\t\tif (item.type === \"text\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(item.text),\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"image\",\n\t\t\t\t\t\t\tsource: {\n\t\t\t\t\t\t\t\ttype: \"base64\",\n\t\t\t\t\t\t\t\tmedia_type: item.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst filteredBlocks = blocks.filter((b) => {\n\t\t\t\t\tif (b.type === \"text\") {\n\t\t\t\t\t\treturn b.text.trim().length > 0;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t\t\tif (filteredBlocks.length === 0) continue;\n\t\t\t\tparams.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: filteredBlocks,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst blocks: ContentBlockParam[] = [];\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (block.text.trim().length === 0) continue;\n\t\t\t\t\tblocks.push({\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t\t\t});\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking: pass the opaque payload back as redacted_thinking\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"redacted_thinking\",\n\t\t\t\t\t\t\tdata: block.thinkingSignature!,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst thinkingSignature = block.thinkingSignature;\n\t\t\t\t\tconst hasThinkingSignature = !!thinkingSignature && thinkingSignature.trim().length > 0;\n\t\t\t\t\tif (block.thinking.trim().length === 0 && !hasThinkingSignature) continue;\n\t\t\t\t\t// If thinking signature is missing/empty (e.g., from aborted stream),\n\t\t\t\t\t// convert to plain text for Anthropic. Some compatible providers emit\n\t\t\t\t\t// and accept empty signatures, so let marked models preserve the block.\n\t\t\t\t\tif (!hasThinkingSignature) {\n\t\t\t\t\t\tblocks.push(\n\t\t\t\t\t\t\tallowEmptySignature\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\t\t\t\tthinking: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\t\t\t\tsignature: \"\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\tsignature: thinkingSignature,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tblocks.push({\n\t\t\t\t\t\ttype: \"tool_use\",\n\t\t\t\t\t\tid: block.id,\n\t\t\t\t\t\tname: isOAuthToken ? toClaudeCodeName(block.name) : block.name,\n\t\t\t\t\t\tinput: block.arguments ?? {},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (blocks.length === 0) continue;\n\t\t\tparams.push({\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: blocks,\n\t\t\t});\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.\n\t\t\tconst toolResults: ContentBlockParam[] = [];\n\t\t\tconst siblingContent: ContentBlockParam[] = [];\n\t\t\tlet j = i;\n\t\t\twhile (j < transformedMessages.length && transformedMessages[j].role === \"toolResult\") {\n\t\t\t\tconst converted = convertToolResult(\n\t\t\t\t\ttransformedMessages[j] as ToolResultMessage,\n\t\t\t\t\tisOAuthToken,\n\t\t\t\t\tdeferredToolNames,\n\t\t\t\t\tloadedToolNames,\n\t\t\t\t\tnormalizeToolName,\n\t\t\t\t);\n\t\t\t\ttoolResults.push(converted.toolResult);\n\t\t\t\tsiblingContent.push(...converted.siblingContent);\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\t// Skip the messages we've already processed.\n\t\t\ti = j - 1;\n\n\t\t\t// Displaced reference-bearing results must follow every tool_result block.\n\t\t\tparams.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [...toolResults, ...siblingContent],\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add cache_control to the last user message to cache conversation history\n\tif (cacheControl && params.length > 0) {\n\t\tconst lastMessage = params[params.length - 1];\n\t\tif (lastMessage.role === \"user\") {\n\t\t\tif (Array.isArray(lastMessage.content)) {\n\t\t\t\tconst lastBlock = lastMessage.content[lastMessage.content.length - 1];\n\t\t\t\tif (\n\t\t\t\t\tlastBlock &&\n\t\t\t\t\t(lastBlock.type === \"text\" || lastBlock.type === \"image\" || lastBlock.type === \"tool_result\")\n\t\t\t\t) {\n\t\t\t\t\t(lastBlock as any).cache_control = cacheControl;\n\t\t\t\t}\n\t\t\t} else if (typeof lastMessage.content === \"string\") {\n\t\t\t\tlastMessage.content = [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: lastMessage.content,\n\t\t\t\t\t\tcache_control: cacheControl,\n\t\t\t\t\t},\n\t\t\t\t] as any;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn params;\n}\n\nfunction shouldUseFineGrainedToolStreamingBeta(model: Model<\"anthropic-messages\">, context: Context): boolean {\n\treturn !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;\n}\n\nfunction convertTools(\n\ttools: Tool[],\n\tisOAuthToken: boolean,\n\tsupportsEagerToolInputStreaming: boolean,\n\tsupportsStrictTools: boolean,\n\tcacheControl?: CacheControlEphemeral,\n\tdeferLoading = false,\n): Anthropic.Messages.Tool[] {\n\tif (!tools) return [];\n\n\treturn tools.map((tool, index) => {\n\t\tconst strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools);\n\t\tconst parameters = getJsonSchemaToolParameters(tool, strict);\n\t\tconst schema = parameters as { properties?: unknown; required?: string[] };\n\t\tconst legacyInputSchema = {\n\t\t\ttype: \"object\" as const,\n\t\t\tproperties: schema.properties ?? {},\n\t\t\trequired: schema.required ?? [],\n\t\t};\n\t\tconst inputSchema =\n\t\t\tstrict === true\n\t\t\t\t? {\n\t\t\t\t\t\t...(parameters as Record<string, unknown>),\n\t\t\t\t\t\t...legacyInputSchema,\n\t\t\t\t\t}\n\t\t\t\t: legacyInputSchema;\n\n\t\treturn {\n\t\t\tname: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,\n\t\t\tdescription: tool.description,\n\t\t\t...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),\n\t\t\t...(strict === true ? { strict: true } : {}),\n\t\t\tinput_schema: inputSchema,\n\t\t\t...(deferLoading ? { defer_loading: true } : {}),\n\t\t\t...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),\n\t\t};\n\t});\n}\n\nfunction mapStopReason(\n\treason: Anthropic.Messages.StopReason | string,\n\tstopDetails?: RefusalStopDetails | null,\n): { stopReason: StopReason; errorMessage?: string } {\n\tswitch (reason) {\n\t\tcase \"end_turn\":\n\t\t\treturn { stopReason: \"stop\" };\n\t\tcase \"max_tokens\":\n\t\t\treturn { stopReason: \"length\" };\n\t\tcase \"tool_use\":\n\t\t\treturn { stopReason: \"toolUse\" };\n\t\tcase \"refusal\":\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: stopDetails?.explanation || `The model refused to complete the request`,\n\t\t\t};\n\t\tcase \"pause_turn\": // Stop is good enough -> resubmit\n\t\t\treturn { stopReason: \"stop\" };\n\t\tcase \"stop_sequence\":\n\t\t\treturn { stopReason: \"stop\" }; // We don't supply stop sequences, so this should never happen\n\t\tcase \"sensitive\": // Content flagged by safety filters (not yet in SDK types)\n\t\t\treturn { stopReason: \"error\", errorMessage: \"Provider stopped with: sensitive\" };\n\t\tdefault:\n\t\t\t// Handle unknown stop reasons gracefully (API may add new values)\n\t\t\tthrow new Error(`Unhandled stop reason: ${reason}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAW1C,OAAO,KAAK,EASX,KAAK,EAGL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA0IrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAuChE,qFAAqF;AACrF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAEzF;AA8CD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAkRD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA4WzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAwClF,CAAC","sourcesContent":["import Anthropic from \"@anthropic-ai/sdk\";\nimport type {\n\tCacheControlEphemeral,\n\tContentBlockParam,\n\tMessageCreateParamsStreaming,\n\tMessageParam,\n\tRawMessageStreamEvent,\n\tRefusalStopDetails,\n\tToolSearchToolBm25_20251119,\n} from \"@anthropic-ai/sdk/resources/messages.js\";\nimport { calculateCost } from \"../models.ts\";\nimport type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tCacheRetention,\n\tContext,\n\tImageContent,\n\tJsonValue,\n\tMessage,\n\tModel,\n\tProviderEnv,\n\tProviderHeaders,\n\tSimpleStreamOptions,\n\tStopReason,\n\tStreamFunction,\n\tStreamOptions,\n\tTextContent,\n\tThinkingContent,\n\tTool,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\nimport { appendDeferredToolsUnsupportedDiagnostic, splitDeferredTools } from \"../utils/deferred-tools.ts\";\nimport { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic } from \"../utils/diagnostics.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { parseJsonWithRepair, parseStreamingJson } from \"../utils/json-parse.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\nimport { getProviderEnvValue } from \"../utils/provider-env.ts\";\nimport { retryProviderRequest } from \"../utils/provider-retry.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\n\nimport { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from \"./constrained-sampling.ts\";\nimport { buildCopilotDynamicHeaders, hasCopilotVisionInput } from \"./github-copilot-headers.ts\";\nimport type { ProviderReplayAssistantMessage } from \"./provider-replay.ts\";\nimport { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from \"./simple-options.ts\";\nimport { transformMessages } from \"./transform-messages.ts\";\n\n/**\n * Resolve cache retention preference.\n * Defaults to \"short\" and uses MCPI_CACHE_RETENTION when configured.\n */\nfunction resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {\n\tif (cacheRetention) {\n\t\treturn cacheRetention;\n\t}\n\tif (getProviderEnvValue(\"MCPI_CACHE_RETENTION\", env) === \"long\") {\n\t\treturn \"long\";\n\t}\n\treturn \"short\";\n}\n\nfunction getCacheControl(\n\tmodel: Model<\"anthropic-messages\">,\n\tcacheRetention?: CacheRetention,\n\tenv?: ProviderEnv,\n): { retention: CacheRetention; cacheControl?: CacheControlEphemeral } {\n\tconst retention = resolveCacheRetention(cacheRetention, env);\n\tif (retention === \"none\") {\n\t\treturn { retention };\n\t}\n\tconst ttl = retention === \"long\" && getAnthropicCompat(model).supportsLongCacheRetention ? \"1h\" : undefined;\n\treturn {\n\t\tretention,\n\t\tcacheControl: { type: \"ephemeral\", ...(ttl && { ttl }) },\n\t};\n}\n\n// Stealth mode: Mimic Claude Code's tool naming exactly\nconst claudeCodeVersion = \"2.1.75\";\n\n// Claude Code 2.x tool names (canonical casing)\n// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md\n// To update: https://github.com/badlogic/cchistory\nconst claudeCodeTools = [\n\t\"Read\",\n\t\"Write\",\n\t\"Edit\",\n\t\"Bash\",\n\t\"Grep\",\n\t\"Glob\",\n\t\"AskUserQuestion\",\n\t\"EnterPlanMode\",\n\t\"ExitPlanMode\",\n\t\"KillShell\",\n\t\"NotebookEdit\",\n\t\"Skill\",\n\t\"Task\",\n\t\"TaskOutput\",\n\t\"TodoWrite\",\n\t\"WebFetch\",\n\t\"WebSearch\",\n];\n\nconst ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));\n\n// Convert tool name to CC canonical casing if it matches (case-insensitive)\nconst toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;\nconst fromClaudeCodeName = (name: string, tools?: Tool[]) => {\n\tif (tools && tools.length > 0) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tconst matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);\n\t\tif (matchedTool) return matchedTool.name;\n\t}\n\treturn name;\n};\n\n/**\n * Convert content blocks to Anthropic API format\n */\nfunction convertContentBlocks(content: (TextContent | ImageContent)[]):\n\t| string\n\t| Array<\n\t\t\t| { type: \"text\"; text: string }\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\tsource: {\n\t\t\t\t\t\ttype: \"base64\";\n\t\t\t\t\t\tmedia_type: \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\t\t\t\t\t\tdata: string;\n\t\t\t\t\t};\n\t\t\t }\n\t > {\n\t// If only text blocks, return as concatenated string for simplicity\n\tconst hasImages = content.some((c) => c.type === \"image\");\n\tif (!hasImages) {\n\t\treturn sanitizeSurrogates(content.map((c) => (c as TextContent).text).join(\"\\n\"));\n\t}\n\n\t// If we have images, convert to content block array\n\tconst blocks = content.map((block) => {\n\t\tif (block.type === \"text\") {\n\t\t\treturn {\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\ttype: \"image\" as const,\n\t\t\tsource: {\n\t\t\t\ttype: \"base64\" as const,\n\t\t\t\tmedia_type: block.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\tdata: block.data,\n\t\t\t},\n\t\t};\n\t});\n\n\t// If only images (no text), add placeholder text block\n\tconst hasText = blocks.some((b) => b.type === \"text\");\n\tif (!hasText) {\n\t\tblocks.unshift({\n\t\t\ttype: \"text\" as const,\n\t\t\ttext: \"(see attached image)\",\n\t\t});\n\t}\n\n\treturn blocks;\n}\n\nexport type AnthropicEffort = \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\nexport type AnthropicThinkingDisplay = \"summarized\" | \"omitted\";\n\nconst FINE_GRAINED_TOOL_STREAMING_BETA = \"fine-grained-tool-streaming-2025-05-14\";\nconst INTERLEAVED_THINKING_BETA = \"interleaved-thinking-2025-05-14\";\n\nfunction getAnthropicCompat(\n\tmodel: Model<\"anthropic-messages\">,\n): Required<Omit<AnthropicMessagesCompat, \"forceAdaptiveThinking\">> {\n\treturn {\n\t\tsupportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,\n\t\tsupportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,\n\t\tsendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? false,\n\t\tsupportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,\n\t\tsupportsTemperature: model.compat?.supportsTemperature ?? true,\n\t\tallowEmptySignature: model.compat?.allowEmptySignature ?? false,\n\t\tsupportsStrictTools: model.compat?.supportsStrictTools ?? false,\n\t\tsupportsToolReferences: resolveSupportsToolReferences(model),\n\t\t// On by default wherever deferral works: Anthropic defines `defer_loading` as \"only\n\t\t// loaded when returned via tool_reference from tool search\", so withholding a schema\n\t\t// without offering search leaves no way to hand it back. Verified against the Copilot\n\t\t// gateway on every id in `GITHUB_COPILOT_TOOL_REFERENCE_MODEL_IDS` routed through this\n\t\t// API, each answering with `server_tool_use` -> `tool_search_tool_result` -> `tool_use`.\n\t\t// Only consulted when `supportsToolReferences` is on, which gates deferral itself.\n\t\tsupportsToolSearch: model.compat?.supportsToolSearch ?? true,\n\t};\n}\n\n/**\n * Models whose deferred-tool support was withdrawn after the endpoint rejected\n * `defer_loading` or `tool_reference`. Process-lifetime only, and only reachable\n * for endpoints that claim the capability without implementing it, since built-in\n * catalog metadata is probe-verified.\n */\nconst toolReferencesRejectedByEndpoint = new Set<string>();\n\nfunction toolReferenceEndpointKey(model: Model<\"anthropic-messages\">): string {\n\treturn `${model.provider}\\u0000${model.id}\\u0000${model.baseUrl}`;\n}\n\n/** Record a hard endpoint rejection so this process stops sending deferred tools. */\nexport function disableToolReferencesForEndpoint(model: Model<\"anthropic-messages\">): void {\n\ttoolReferencesRejectedByEndpoint.add(toolReferenceEndpointKey(model));\n}\n\nfunction resolveSupportsToolReferences(model: Model<\"anthropic-messages\">): boolean {\n\tif (toolReferencesRejectedByEndpoint.has(toolReferenceEndpointKey(model))) return false;\n\treturn model.compat?.supportsToolReferences ?? defaultSupportsToolReferences(model);\n}\n\n/**\n * Anthropic's server-side tool search, offered whenever a request carries deferred tools.\n *\n * BM25 rather than the regex variant: a regex that matches nothing returns no references\n * and leaves the tool hidden, which is the failure this exists to prevent, while BM25\n * always ranks the catalog and returns the closest matches. It stays outside the deferred\n * set so the model can always see that searching is possible.\n */\nconst TOOL_SEARCH_TOOL: ToolSearchToolBm25_20251119 = {\n\tname: \"tool_search_tool_bm25\",\n\ttype: \"tool_search_tool_bm25_20251119\",\n};\n\n/**\n * Fallback for `anthropic-messages` models with no explicit capability metadata.\n *\n * First-party Anthropic ships dated snapshots of the same model\n * (`claude-opus-4-6-20260101`), so support is derived from the family version\n * rather than an exact-id allowlist: Sonnet, Opus, and Fable at 4.5 or newer,\n * excluding Haiku, which rejects client-side `tool_reference` blocks.\n *\n * Every other provider returns false and must opt in through\n * `compat.supportsToolReferences`. Gateways that reuse Claude ids do not\n * necessarily implement the protocol, and the generated catalog carries\n * probe-verified metadata for the ones that do (see\n * `GITHUB_COPILOT_TOOL_REFERENCE_MODEL_IDS` in `scripts/generate-models.ts`).\n */\nfunction defaultSupportsToolReferences(model: Model<\"anthropic-messages\">): boolean {\n\tif (model.provider !== \"anthropic\" || model.id.includes(\"haiku\")) return false;\n\t// Minor versions appear as `claude-opus-4-6` and `claude-opus-4.8` depending on\n\t// the catalog, so accept either separator. A trailing 8-digit group is a release\n\t// date (`claude-sonnet-4-20250514`), not a minor version.\n\tconst version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\\d+)(?:[-.](\\d+))?(?:[-.]|$)/);\n\tif (!version) return false;\n\tconst major = Number(version[1]);\n\tconst minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;\n\treturn major > 4 || (major === 4 && minor >= 5);\n}\n\nexport interface AnthropicOptions extends StreamOptions {\n\t/**\n\t * Enable extended thinking.\n\t * For adaptive thinking models: the model decides when/how much to think.\n\t * For older models: uses budget-based thinking with thinkingBudgetTokens.\n\t * Default: undefined (thinking is omitted unless `streamSimple()` maps\n\t * a simple reasoning level to this option, or callers set it explicitly).\n\t */\n\tthinkingEnabled?: boolean;\n\t/**\n\t * Token budget for extended thinking (older models only).\n\t * Ignored for adaptive thinking models.\n\t * Default: 1024 when `thinkingEnabled` is true and no budget is provided.\n\t */\n\tthinkingBudgetTokens?: number;\n\t/**\n\t * Effort level for adaptive thinking models.\n\t * Controls how much thinking Claude allocates:\n\t * - \"max\": Always thinks with no constraints (Opus 4.6 and newer, Sonnet 4.6 and newer, Fable 5)\n\t * - \"xhigh\": Highest reasoning level (Opus 4.7 and newer including Opus 5, Sonnet 5, Fable 5)\n\t * - \"high\": Always thinks, deep reasoning\n\t * - \"medium\": Moderate thinking, may skip for simple queries\n\t * - \"low\": Minimal thinking, skips for simple tasks\n\t * Ignored for older models.\n\t * Default: omitted unless `streamSimple()` maps a simple reasoning\n\t * level to this option.\n\t */\n\teffort?: AnthropicEffort;\n\t/**\n\t * Controls how thinking content is returned in API responses.\n\t * - \"summarized\": Thinking blocks contain summarized thinking text.\n\t * - \"omitted\": Thinking blocks return an empty thinking field; the encrypted\n\t * signature still travels back for multi-turn continuity. Use for faster\n\t * time-to-first-text-token when your UI does not surface thinking.\n\t *\n\t * Note: Anthropic's API default for Claude Opus 4.7 and Claude Mythos Preview\n\t * is \"omitted\". We default to \"summarized\" here to keep behavior consistent\n\t * with older Claude 4 models. Set this explicitly to \"omitted\" to opt in.\n\t * Default: \"summarized\" when thinking is enabled.\n\t */\n\tthinkingDisplay?: AnthropicThinkingDisplay;\n\t/**\n\t * Whether to request the interleaved thinking beta header for non-adaptive\n\t * thinking models. Adaptive thinking models have interleaved thinking built in,\n\t * so the header is skipped for them regardless of this setting.\n\t * Default: true.\n\t */\n\tinterleavedThinking?: boolean;\n\t/**\n\t * Anthropic tool choice behavior. String values map to Anthropic's built-in\n\t * choices; `{ type: \"tool\", name }` forces a specific tool.\n\t * Default: omitted (Anthropic default behavior, currently equivalent to auto).\n\t */\n\ttoolChoice?: \"auto\" | \"any\" | \"none\" | { type: \"tool\"; name: string };\n\t/**\n\t * Pre-built Anthropic client instance. When provided, skips internal client\n\t * construction entirely. Use this to inject alternative SDK clients such as\n\t * `AnthropicVertex` that shares the same messaging API.\n\t */\n\tclient?: Anthropic;\n}\n\nfunction mergeHeaders(...headerSources: (ProviderHeaders | undefined)[]): ProviderHeaders {\n\tconst merged: ProviderHeaders = {};\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\treturn merged;\n}\n\nfunction mergeClientHeaders(\n\tmodel: Model<\"anthropic-messages\">,\n\t...headerSources: (ProviderHeaders | undefined)[]\n): ProviderHeaders {\n\tconst merged = mergeHeaders(...headerSources);\n\tif (model.provider === \"kimi-coding\") {\n\t\tfor (const name of Object.keys(merged)) {\n\t\t\tif (name.toLowerCase() === \"user-agent\") delete merged[name];\n\t\t}\n\t\tmerged[\"User-Agent\"] = getPiUserAgent();\n\t}\n\treturn merged;\n}\n\nfunction hasHeader(headers: ProviderHeaders | undefined, name: string): boolean {\n\tif (!headers) return false;\n\tconst expected = name.toLowerCase();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (key.toLowerCase() === expected && value !== null && value.trim().length > 0) return true;\n\t}\n\treturn false;\n}\n\nfunction assertRequestAuth(provider: string, apiKey: string | undefined, headers: ProviderHeaders | undefined): void {\n\tif (apiKey) return;\n\tif (\n\t\thasHeader(headers, \"authorization\") ||\n\t\thasHeader(headers, \"x-api-key\") ||\n\t\thasHeader(headers, \"cf-aig-authorization\")\n\t) {\n\t\treturn;\n\t}\n\tthrow new Error(`No API key for provider: ${provider}`);\n}\n\ninterface ServerSentEvent {\n\tevent: string | null;\n\tdata: string;\n\traw: string[];\n}\n\ninterface SseDecoderState {\n\tevent: string | null;\n\tdata: string[];\n\traw: string[];\n}\n\nconst ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([\n\t\"message_start\",\n\t\"message_delta\",\n\t\"message_stop\",\n\t\"content_block_start\",\n\t\"content_block_delta\",\n\t\"content_block_stop\",\n]);\n\nfunction flushSseEvent(state: SseDecoderState): ServerSentEvent | null {\n\tif (!state.event && state.data.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst event: ServerSentEvent = {\n\t\tevent: state.event,\n\t\tdata: state.data.join(\"\\n\"),\n\t\traw: [...state.raw],\n\t};\n\tstate.event = null;\n\tstate.data = [];\n\tstate.raw = [];\n\treturn event;\n}\n\nfunction decodeSseLine(line: string, state: SseDecoderState): ServerSentEvent | null {\n\tif (line === \"\") {\n\t\treturn flushSseEvent(state);\n\t}\n\n\tstate.raw.push(line);\n\tif (line.startsWith(\":\")) {\n\t\treturn null;\n\t}\n\n\tconst delimiterIndex = line.indexOf(\":\");\n\tconst fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);\n\tlet value = delimiterIndex === -1 ? \"\" : line.slice(delimiterIndex + 1);\n\tif (value.startsWith(\" \")) {\n\t\tvalue = value.slice(1);\n\t}\n\n\tif (fieldName === \"event\") {\n\t\tstate.event = value;\n\t} else if (fieldName === \"data\") {\n\t\tstate.data.push(value);\n\t}\n\n\treturn null;\n}\n\nfunction nextLineBreakIndex(text: string): number {\n\tconst carriageReturnIndex = text.indexOf(\"\\r\");\n\tconst newlineIndex = text.indexOf(\"\\n\");\n\tif (carriageReturnIndex === -1) {\n\t\treturn newlineIndex;\n\t}\n\tif (newlineIndex === -1) {\n\t\treturn carriageReturnIndex;\n\t}\n\treturn Math.min(carriageReturnIndex, newlineIndex);\n}\n\nfunction consumeLine(text: string): { line: string; rest: string } | null {\n\tconst lineBreakIndex = nextLineBreakIndex(text);\n\tif (lineBreakIndex === -1) {\n\t\treturn null;\n\t}\n\n\tlet nextIndex = lineBreakIndex + 1;\n\tif (text[lineBreakIndex] === \"\\r\" && text[nextIndex] === \"\\n\") {\n\t\tnextIndex += 1;\n\t}\n\n\treturn {\n\t\tline: text.slice(0, lineBreakIndex),\n\t\trest: text.slice(nextIndex),\n\t};\n}\n\nasync function* iterateSseMessages(\n\tbody: ReadableStream<Uint8Array>,\n\tsignal?: AbortSignal,\n): AsyncGenerator<ServerSentEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst state: SseDecoderState = { event: null, data: [], raw: [] };\n\tlet buffer = \"\";\n\n\ttry {\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\tlet consumed = consumeLine(buffer);\n\t\t\twhile (consumed) {\n\t\t\t\tbuffer = consumed.rest;\n\t\t\t\tconst event = decodeSseLine(consumed.line, state);\n\t\t\t\tif (event) {\n\t\t\t\t\tyield event;\n\t\t\t\t}\n\t\t\t\tconsumed = consumeLine(buffer);\n\t\t\t}\n\t\t}\n\n\t\tbuffer += decoder.decode();\n\t\tlet consumed = consumeLine(buffer);\n\t\twhile (consumed) {\n\t\t\tbuffer = consumed.rest;\n\t\t\tconst event = decodeSseLine(consumed.line, state);\n\t\t\tif (event) {\n\t\t\t\tyield event;\n\t\t\t}\n\t\t\tconsumed = consumeLine(buffer);\n\t\t}\n\n\t\tif (buffer.length > 0) {\n\t\t\tconst event = decodeSseLine(buffer, state);\n\t\t\tif (event) {\n\t\t\t\tyield event;\n\t\t\t}\n\t\t}\n\n\t\tconst trailingEvent = flushSseEvent(state);\n\t\tif (trailingEvent) {\n\t\t\tyield trailingEvent;\n\t\t}\n\t} finally {\n\t\treader.releaseLock();\n\t}\n}\n\nasync function* iterateAnthropicEvents(\n\tresponse: Response,\n\tsignal?: AbortSignal,\n): AsyncGenerator<RawMessageStreamEvent> {\n\tif (!response.body) {\n\t\tthrow new Error(\"Attempted to iterate over an Anthropic response with no body\");\n\t}\n\n\tlet sawMessageStart = false;\n\tlet sawMessageEnd = false;\n\n\tfor await (const sse of iterateSseMessages(response.body, signal)) {\n\t\tif (sse.event === \"error\") {\n\t\t\tthrow new Error(sse.data);\n\t\t}\n\n\t\tif (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? \"\")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst event = parseJsonWithRepair<RawMessageStreamEvent>(sse.data);\n\t\t\tif (event.type === \"message_start\") {\n\t\t\t\tsawMessageStart = true;\n\t\t\t} else if (event.type === \"message_stop\") {\n\t\t\t\tsawMessageEnd = true;\n\t\t\t}\n\t\t\tyield event;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join(\"\\\\n\")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (sawMessageStart && !sawMessageEnd) {\n\t\tthrow new Error(\"Anthropic stream ended before message_stop\");\n\t}\n}\n\n/**\n * Signals that the endpoint does not implement the deferred-tool protocol at all:\n * the field is rejected as unknown, or the content block type is unrecognized.\n */\nconst DEFERRED_TOOL_CAPABILITY_REJECTION =\n\t/extra inputs are not permitted|not permitted|unsupported|not supported|unrecognized|unexpected (?:field|keyword|argument|property)|unknown (?:field|parameter|argument|property)|does not match any of the expected|invalid[^.]{0,32}\\btype\\b/i;\n\n/**\n * Signals that the endpoint *does* implement references and could not resolve one.\n * That is a client-side activation or naming bug, not a capability gap, so it must\n * surface rather than silently downgrade the request.\n */\nconst TOOL_REFERENCE_RESOLUTION_FAILURE = /not found|does not exist|no such|unresolved|unknown tool\\b/i;\n\n/**\n * True only for an unambiguous rejection of the deferred-tool protocol itself:\n * a 400 naming the `defer_loading` field, the `tool_reference` block type, or the\n * `tool_search_tool_*` server tool, *and* describing it as unknown, extra, or\n * unsupported.\n *\n * The Copilot gateway words these as `Extra inputs are not permitted:\n * tools.1.defer_loading` and as an unrecognized content block type.\n *\n * `Tool reference 'x' not found in available tools` is deliberately excluded. It\n * proves the endpoint implements references, so downgrading would mask a client\n * activation or tool-naming bug behind a silent capability loss.\n */\nfunction isDeferredToolRejection(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tif ((error as { status?: unknown }).status !== 400) return false;\n\tif (!/\\b(?:defer_loading|tool_reference|tool_search_tool\\w*)\\b/.test(error.message)) return false;\n\tif (TOOL_REFERENCE_RESOLUTION_FAILURE.test(error.message)) return false;\n\treturn DEFERRED_TOOL_CAPABILITY_REJECTION.test(error.message);\n}\n\nexport const stream: StreamFunction<\"anthropic-messages\", AnthropicOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: AnthropicOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"pending\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tlet client: Anthropic;\n\t\t\tlet isOAuth: boolean;\n\n\t\t\tif (options?.client) {\n\t\t\t\tclient = options.client;\n\t\t\t\tisOAuth = false;\n\t\t\t} else {\n\t\t\t\tconst apiKey = options?.apiKey;\n\t\t\t\tassertRequestAuth(model.provider, apiKey, options?.headers);\n\n\t\t\t\tlet copilotDynamicHeaders: Record<string, string> | undefined;\n\t\t\t\tif (model.provider === \"github-copilot\") {\n\t\t\t\t\tconst hasImages = hasCopilotVisionInput(context.messages);\n\t\t\t\t\tcopilotDynamicHeaders = buildCopilotDynamicHeaders({\n\t\t\t\t\t\tmessages: context.messages,\n\t\t\t\t\t\thasImages,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);\n\t\t\t\tconst cacheSessionId = cacheRetention === \"none\" ? undefined : options?.sessionId;\n\n\t\t\t\tconst created = createClient(\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\toptions?.interleavedThinking ?? true,\n\t\t\t\t\tshouldUseFineGrainedToolStreamingBeta(model, context),\n\t\t\t\t\toptions?.headers,\n\t\t\t\t\toptions?.fetch,\n\t\t\t\t\tcopilotDynamicHeaders,\n\t\t\t\t\tcacheSessionId,\n\t\t\t\t);\n\t\t\t\tclient = created.client;\n\t\t\t\tisOAuth = created.isOAuthToken;\n\t\t\t}\n\t\t\tlet built = buildParams(model, context, isOAuth, options);\n\t\t\tappendDeferredToolsUnsupportedDiagnostic(output, model, built.unsupportedDeferredToolNames);\n\t\t\tlet params = built.params;\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as MessageCreateParamsStreaming;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\tmaxRetries: 0,\n\t\t\t};\n\t\t\tconst sendRequest = (body: MessageCreateParamsStreaming) =>\n\t\t\t\tretryProviderRequest(() => client.messages.create({ ...body, stream: true }, requestOptions).asResponse(), {\n\t\t\t\t\tmaxRetries: options?.maxRetries,\n\t\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs,\n\t\t\t\t\tsignal: options?.signal,\n\t\t\t\t});\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await sendRequest(params);\n\t\t\t} catch (error) {\n\t\t\t\t// Only a 400 that names the deferred-tool fields is treated as a\n\t\t\t\t// capability mismatch. Every other failure, including other 400s,\n\t\t\t\t// propagates with the provider's exact message.\n\t\t\t\tif (built.deferredToolNames.length === 0 || !isDeferredToolRejection(error)) throw error;\n\t\t\t\tdisableToolReferencesForEndpoint(model);\n\t\t\t\tappendAssistantMessageDiagnostic(\n\t\t\t\t\toutput,\n\t\t\t\t\tcreateAssistantMessageDiagnostic(\"deferred_tools_rejected\", error, {\n\t\t\t\t\t\tprovider: model.provider,\n\t\t\t\t\t\tmodel: model.id,\n\t\t\t\t\t\tdeferredTools: built.deferredToolNames,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tbuilt = buildParams(model, context, isOAuth, options);\n\t\t\t\tappendDeferredToolsUnsupportedDiagnostic(output, model, built.unsupportedDeferredToolNames);\n\t\t\t\tparams = built.params;\n\t\t\t\tconst retryParams = await options?.onPayload?.(params, model);\n\t\t\t\tif (retryParams !== undefined) {\n\t\t\t\t\tparams = retryParams as MessageCreateParamsStreaming;\n\t\t\t\t}\n\t\t\t\tresponse = await sendRequest(params);\n\t\t\t}\n\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\ttype Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number };\n\t\t\tconst blocks = output.content as Block[];\n\t\t\tinterface ReplayBlock {\n\t\t\t\tindex: number;\n\t\t\t\tblock: Record<string, unknown>;\n\t\t\t\tpartialJson: string;\n\t\t\t}\n\t\t\tconst replayBlocks: ReplayBlock[] = [];\n\n\t\t\tfor await (const event of iterateAnthropicEvents(response, options?.signal)) {\n\t\t\t\tif (event.type === \"message_start\") {\n\t\t\t\t\toutput.responseId = event.message.id;\n\t\t\t\t\t// Capture initial token usage from message_start event\n\t\t\t\t\t// This ensures we have input token counts even if the stream is aborted early\n\t\t\t\t\toutput.usage.input = event.message.usage.input_tokens || 0;\n\t\t\t\t\toutput.usage.output = event.message.usage.output_tokens || 0;\n\t\t\t\t\toutput.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;\n\t\t\t\t\toutput.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;\n\t\t\t\t\toutput.usage.cacheWrite1h = event.message.usage.cache_creation?.ephemeral_1h_input_tokens || 0;\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t} else if (event.type === \"content_block_start\") {\n\t\t\t\t\treplayBlocks.push({\n\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\tblock: event.content_block as unknown as Record<string, unknown>,\n\t\t\t\t\t\tpartialJson: \"\",\n\t\t\t\t\t});\n\t\t\t\t\tif (event.content_block.type === \"text\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: event.content_block.text ?? \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: event.content_block.thinking ?? \"\",\n\t\t\t\t\t\t\tthinkingSignature: event.content_block.signature ?? \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"redacted_thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: \"[Reasoning redacted]\",\n\t\t\t\t\t\t\tthinkingSignature: event.content_block.data,\n\t\t\t\t\t\t\tredacted: true,\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"tool_use\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\t\tid: event.content_block.id,\n\t\t\t\t\t\t\tname: isOAuth\n\t\t\t\t\t\t\t\t? fromClaudeCodeName(event.content_block.name, context.tools)\n\t\t\t\t\t\t\t\t: event.content_block.name,\n\t\t\t\t\t\t\targuments: (event.content_block.input as Record<string, any>) ?? {},\n\t\t\t\t\t\t\tpartialJson: \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_delta\") {\n\t\t\t\t\tconst replayBlock = replayBlocks.find((block) => block.index === event.index);\n\t\t\t\t\tif (replayBlock) {\n\t\t\t\t\t\tif (event.delta.type === \"text_delta\") {\n\t\t\t\t\t\t\tconst text = replayBlock.block.text;\n\t\t\t\t\t\t\treplayBlock.block.text = `${typeof text === \"string\" ? text : \"\"}${event.delta.text}`;\n\t\t\t\t\t\t} else if (event.delta.type === \"thinking_delta\") {\n\t\t\t\t\t\t\tconst thinking = replayBlock.block.thinking;\n\t\t\t\t\t\t\treplayBlock.block.thinking = `${typeof thinking === \"string\" ? thinking : \"\"}${event.delta.thinking}`;\n\t\t\t\t\t\t} else if (event.delta.type === \"signature_delta\") {\n\t\t\t\t\t\t\tconst signature = replayBlock.block.signature;\n\t\t\t\t\t\t\treplayBlock.block.signature = `${typeof signature === \"string\" ? signature : \"\"}${event.delta.signature}`;\n\t\t\t\t\t\t} else if (event.delta.type === \"input_json_delta\") {\n\t\t\t\t\t\t\treplayBlock.partialJson += event.delta.partial_json;\n\t\t\t\t\t\t\treplayBlock.block.input = parseStreamingJson(replayBlock.partialJson);\n\t\t\t\t\t\t} else if (event.delta.type === \"citations_delta\") {\n\t\t\t\t\t\t\tconst citations = replayBlock.block.citations;\n\t\t\t\t\t\t\treplayBlock.block.citations = Array.isArray(citations)\n\t\t\t\t\t\t\t\t? [...citations, event.delta.citation]\n\t\t\t\t\t\t\t\t: [event.delta.citation];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (event.delta.type === \"text_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"text\") {\n\t\t\t\t\t\t\tblock.text += event.delta.text;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"thinking_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinking += event.delta.thinking;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"input_json_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.partialJson += event.delta.partial_json;\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.partial_json,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"signature_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinkingSignature = block.thinkingSignature || \"\";\n\t\t\t\t\t\t\tblock.thinkingSignature += event.delta.signature;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_stop\") {\n\t\t\t\t\tconst replayBlock = replayBlocks.find((block) => block.index === event.index);\n\t\t\t\t\tif (replayBlock?.partialJson) {\n\t\t\t\t\t\treplayBlock.block.input = parseStreamingJson(replayBlock.partialJson);\n\t\t\t\t\t}\n\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\tif (block) {\n\t\t\t\t\t\tdelete (block as any).index;\n\t\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\t// Finalize in-place and strip the scratch buffer so replay only\n\t\t\t\t\t\t\t// carries parsed arguments.\n\t\t\t\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"message_delta\") {\n\t\t\t\t\tif (event.delta.stop_reason) {\n\t\t\t\t\t\toutput.rawStopReason = event.delta.stop_reason;\n\t\t\t\t\t\tconst stopReasonResult = mapStopReason(event.delta.stop_reason, event.delta.stop_details);\n\t\t\t\t\t\toutput.stopReason = stopReasonResult.stopReason;\n\t\t\t\t\t\tif (stopReasonResult.errorMessage) {\n\t\t\t\t\t\t\toutput.errorMessage = stopReasonResult.errorMessage;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Only update usage fields if present (not null).\n\t\t\t\t\t// Preserves input_tokens from message_start when proxies omit it in message_delta.\n\t\t\t\t\tif (event.usage) {\n\t\t\t\t\t\tif (event.usage.input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.input = event.usage.input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.output_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.output = event.usage.output_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.cache_read_input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.cacheRead = event.usage.cache_read_input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (event.usage.cache_creation_input_tokens != null) {\n\t\t\t\t\t\t\toutput.usage.cacheWrite = event.usage.cache_creation_input_tokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the\n\t\t\t\t\t\t// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from\n\t\t\t\t\t\t// its Usage type, so read it through a narrow cast. Verified against the live API.\n\t\t\t\t\t\tconst thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })\n\t\t\t\t\t\t\t.output_tokens_details?.thinking_tokens;\n\t\t\t\t\t\tif (thinkingTokens != null) {\n\t\t\t\t\t\t\toutput.usage.reasoning = thinkingTokens;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"pending\") {\n\t\t\t\tthrow new Error(\"Anthropic stream ended without a stop reason\");\n\t\t\t}\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(output.errorMessage || \"An unknown error occurred\");\n\t\t\t}\n\t\t\tconst providerContent = replayBlocks\n\t\t\t\t.sort((left, right) => left.index - right.index)\n\t\t\t\t.map((block) => block.block as JsonValue);\n\t\t\tif (\n\t\t\t\tproviderContent.some(\n\t\t\t\t\t(block) => typeof block === \"object\" && block !== null && !Array.isArray(block) && block.type !== \"text\",\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\t(output as ProviderReplayAssistantMessage).providerReplay = {\n\t\t\t\t\tprovider: model.provider,\n\t\t\t\t\tapi: model.api,\n\t\t\t\t\tmodel: model.id,\n\t\t\t\t\tcontent: providerContent,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\n/**\n * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.\n * Note: effort \"max\" is available on all adaptive-thinking Claude models, while native\n * \"xhigh\" is only available on Opus 4.7/4.8, Sonnet 5, and Fable 5.\n */\nfunction mapThinkingLevelToEffort(\n\tmodel: Model<\"anthropic-messages\">,\n\tlevel: SimpleStreamOptions[\"reasoning\"],\n): AnthropicEffort {\n\tconst mapped = level ? model.thinkingLevelMap?.[level] : undefined;\n\tif (typeof mapped === \"string\") return mapped as AnthropicEffort;\n\n\tswitch (level) {\n\t\tcase \"minimal\":\n\t\tcase \"low\":\n\t\t\treturn \"low\";\n\t\tcase \"medium\":\n\t\t\treturn \"medium\";\n\t\tcase \"high\":\n\t\t\treturn \"high\";\n\t\tdefault:\n\t\t\treturn \"high\";\n\t}\n}\n\nexport const streamSimple: StreamFunction<\"anthropic-messages\", SimpleStreamOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tassertRequestAuth(model.provider, options?.apiKey, options?.headers);\n\n\tconst base = buildBaseOptions(model, context, options, options?.apiKey);\n\tif (!options?.reasoning) {\n\t\treturn stream(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions);\n\t}\n\n\t// For models with adaptive thinking: use an effort level.\n\t// For older models: use budget-based thinking.\n\tif (model.compat?.forceAdaptiveThinking === true) {\n\t\tconst effort = mapThinkingLevelToEffort(model, options.reasoning);\n\t\treturn stream(model, context, {\n\t\t\t...base,\n\t\t\tthinkingEnabled: true,\n\t\t\teffort,\n\t\t} satisfies AnthropicOptions);\n\t}\n\n\t// Undefined means the caller did not request an output cap; let the helper use the model cap.\n\t// Do not coerce to 0 here, or the thinking budget would become the entire max_tokens value.\n\tconst adjusted = adjustMaxTokensForThinking(\n\t\tbase.maxTokens,\n\t\tmodel.maxTokens,\n\t\toptions.reasoning,\n\t\toptions.thinkingBudgets,\n\t);\n\n\tconst maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);\n\n\treturn stream(model, context, {\n\t\t...base,\n\t\tmaxTokens,\n\t\tthinkingEnabled: true,\n\t\tthinkingBudgetTokens: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),\n\t} satisfies AnthropicOptions);\n};\n\nfunction isOAuthToken(apiKey: string): boolean {\n\treturn apiKey.includes(\"sk-ant-oat\");\n}\n\nfunction createClient(\n\tmodel: Model<\"anthropic-messages\">,\n\tapiKey: string | undefined,\n\tinterleavedThinking: boolean,\n\tuseFineGrainedToolStreamingBeta: boolean,\n\toptionsHeaders?: ProviderHeaders,\n\tfetch?: typeof globalThis.fetch,\n\tdynamicHeaders?: Record<string, string>,\n\tsessionId?: string,\n): { client: Anthropic; isOAuthToken: boolean } {\n\t// Adaptive thinking models have interleaved thinking built in, so skip the beta header.\n\tconst needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;\n\tconst betaFeatures: string[] = [];\n\tif (useFineGrainedToolStreamingBeta) {\n\t\tbetaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA);\n\t}\n\tif (needsInterleavedBeta) {\n\t\tbetaFeatures.push(INTERLEAVED_THINKING_BETA);\n\t}\n\n\t// Copilot: Bearer auth, selective betas.\n\tif (model.provider === \"github-copilot\") {\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey ?? null,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tfetch,\n\t\t\tdefaultHeaders: mergeClientHeaders(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t...(betaFeatures.length > 0 ? { \"anthropic-beta\": betaFeatures.join(\",\") } : {}),\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\tdynamicHeaders,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: false };\n\t}\n\n\t// OAuth: Bearer auth, Claude Code identity headers\n\tif (apiKey && isOAuthToken(apiKey)) {\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tfetch,\n\t\t\tdefaultHeaders: mergeClientHeaders(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t\"anthropic-beta\": [\"claude-code-20250219\", \"oauth-2025-04-20\", ...betaFeatures].join(\",\"),\n\t\t\t\t\t\"user-agent\": `claude-cli/${claudeCodeVersion}`,\n\t\t\t\t\t\"x-app\": \"cli\",\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: true };\n\t}\n\n\t// API key or header-owned auth.\n\tconst sessionAffinityHeaders: ProviderHeaders =\n\t\tsessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { \"x-session-affinity\": sessionId } : {};\n\tconst defaultHeaders = mergeClientHeaders(\n\t\tmodel,\n\t\t{\n\t\t\taccept: \"application/json\",\n\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t...(betaFeatures.length > 0 ? { \"anthropic-beta\": betaFeatures.join(\",\") } : {}),\n\t\t},\n\t\tsessionAffinityHeaders,\n\t\tmodel.headers,\n\t\toptionsHeaders,\n\t);\n\tconst client = new Anthropic({\n\t\tapiKey: apiKey ?? null,\n\t\tauthToken: null,\n\t\tbaseURL: model.baseUrl,\n\t\tdangerouslyAllowBrowser: true,\n\t\tfetch,\n\t\tdefaultHeaders,\n\t});\n\n\treturn { client, isOAuthToken: false };\n}\n\n/** The request plus the deferral this model could not express, for the caller to report. */\ninterface BuiltParams {\n\tparams: MessageCreateParamsStreaming;\n\t/** Normalized names actually sent with `defer_loading`. */\n\tdeferredToolNames: string[];\n\t/** Normalized names that deferral would have withheld, but which were expanded. */\n\tunsupportedDeferredToolNames: string[];\n}\n\nfunction buildParams(\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\tisOAuthToken: boolean,\n\toptions?: AnthropicOptions,\n): BuiltParams {\n\tconst { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);\n\tconst compat = getAnthropicCompat(model);\n\tconst transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);\n\tconst normalizeToolName = isOAuthToken ? toClaudeCodeName : (name: string) => name;\n\tconst deferralContext = { ...context, messages: transformedMessages };\n\tconst toolPlacement = splitDeferredTools(deferralContext, {\n\t\tenabled: compat.supportsToolReferences,\n\t\t// Without server-side search, the only load point is a `tool_reference` in a tool\n\t\t// result, so a tool registered with `deferred: true` that no result introduces would\n\t\t// stay hidden for the whole session. Report those instead of losing them.\n\t\tregistrationDeferral: compat.supportsToolSearch,\n\t\t// The all-deferred safety floor exists so the model always has somewhere to start. The\n\t\t// search tool below is somewhere to start, so a tool set that is entirely deferred --\n\t\t// the all-MCP-proxy shape that gains the most here -- stays deferred rather than being\n\t\t// expanded wholesale.\n\t\tprovidesToolSearch: compat.supportsToolSearch,\n\t\tnormalizeName: normalizeToolName,\n\t});\n\tconst immediateTools = toolPlacement.immediate;\n\tconst deferredTools = [...toolPlacement.deferred.values()];\n\tconst deferredToolNames = new Set(deferredTools.map((tool) => normalizeToolName(tool.name)));\n\tconst params: MessageCreateParamsStreaming = {\n\t\tmodel: model.id,\n\t\tmessages: convertMessages(\n\t\t\ttransformedMessages,\n\t\t\tisOAuthToken,\n\t\t\tcacheControl,\n\t\t\tcompat.allowEmptySignature,\n\t\t\tdeferredToolNames,\n\t\t\tnormalizeToolName,\n\t\t),\n\t\tmax_tokens: options?.maxTokens ?? model.maxTokens,\n\t\tstream: true,\n\t};\n\n\t// For OAuth tokens, we MUST include Claude Code identity\n\tif (isOAuthToken) {\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: \"You are Claude Code, Anthropic's official CLI for Claude.\",\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t\tif (context.systemPrompt) {\n\t\t\tparams.system.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t});\n\t\t}\n\t} else if (context.systemPrompt) {\n\t\t// Add cache control to system prompt for non-OAuth tokens\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t}\n\n\t// Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.\n\tif (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {\n\t\tparams.temperature = options.temperature;\n\t}\n\n\tif (immediateTools.length > 0 || deferredTools.length > 0) {\n\t\tparams.tools = [\n\t\t\t...convertTools(\n\t\t\t\timmediateTools,\n\t\t\t\tisOAuthToken,\n\t\t\t\tcompat.supportsEagerToolInputStreaming,\n\t\t\t\tcompat.supportsStrictTools,\n\t\t\t\tcompat.supportsCacheControlOnTools ? cacheControl : undefined,\n\t\t\t),\n\t\t\t...convertTools(\n\t\t\t\tdeferredTools,\n\t\t\t\tisOAuthToken,\n\t\t\t\tcompat.supportsEagerToolInputStreaming,\n\t\t\t\tcompat.supportsStrictTools,\n\t\t\t\tundefined,\n\t\t\t\ttrue,\n\t\t\t),\n\t\t];\n\t\t// Appended last so it sits outside the cached prefix, which ends at the\n\t\t// `cache_control` breakpoint on the final immediate tool.\n\t\tif (deferredTools.length > 0 && compat.supportsToolSearch) {\n\t\t\tparams.tools.push(TOOL_SEARCH_TOOL);\n\t\t}\n\t}\n\n\t// Configure thinking mode: adaptive, budget-based, or explicitly disabled.\n\tif (model.reasoning) {\n\t\tif (options?.thinkingEnabled) {\n\t\t\t// Default to \"summarized\" so Opus 4.7 and Mythos Preview behave like\n\t\t\t// older Claude 4 models (whose API default is also \"summarized\").\n\t\t\tconst display: AnthropicThinkingDisplay = options.thinkingDisplay ?? \"summarized\";\n\t\t\tif (model.compat?.forceAdaptiveThinking === true) {\n\t\t\t\t// Adaptive thinking: Claude decides when and how much to think.\n\t\t\t\tparams.thinking = { type: \"adaptive\", display };\n\t\t\t\tif (options.effort) {\n\t\t\t\t\t// The Anthropic SDK types can lag newly supported effort values such as \"xhigh\".\n\t\t\t\t\tparams.output_config =\n\t\t\t\t\t\toptions.effort === \"xhigh\"\n\t\t\t\t\t\t\t? ({ effort: options.effort } as unknown as NonNullable<\n\t\t\t\t\t\t\t\t\tMessageCreateParamsStreaming[\"output_config\"]\n\t\t\t\t\t\t\t\t>)\n\t\t\t\t\t\t\t: { effort: options.effort };\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Budget-based thinking for older models\n\t\t\t\tparams.thinking = {\n\t\t\t\t\ttype: \"enabled\",\n\t\t\t\t\tbudget_tokens: options.thinkingBudgetTokens || 1024,\n\t\t\t\t\tdisplay,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.thinking = { type: \"disabled\" };\n\t\t}\n\t}\n\n\tif (options?.metadata) {\n\t\tconst userId = options.metadata.user_id;\n\t\tif (typeof userId === \"string\") {\n\t\t\tparams.metadata = { user_id: userId };\n\t\t}\n\t}\n\n\tif (options?.toolChoice) {\n\t\tif (typeof options.toolChoice === \"string\") {\n\t\t\tparams.tool_choice = { type: options.toolChoice };\n\t\t} else {\n\t\t\tparams.tool_choice = options.toolChoice;\n\t\t}\n\t}\n\n\treturn {\n\t\tparams,\n\t\tdeferredToolNames: [...deferredToolNames],\n\t\tunsupportedDeferredToolNames: toolPlacement.unsupported,\n\t};\n}\n\n// Normalize tool call IDs to match Anthropic's required pattern and length\nfunction normalizeToolCallId(id: string): string {\n\treturn id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n}\n\nfunction convertToolResult(\n\tmsg: ToolResultMessage,\n\tisOAuthToken: boolean,\n\tdeferredToolNames: ReadonlySet<string>,\n\tloadedToolNames: Set<string>,\n\tnormalizeToolName: (name: string) => string,\n): { toolResult: ContentBlockParam; siblingContent: ContentBlockParam[] } {\n\tconst references: Array<{ type: \"tool_reference\"; tool_name: string }> = [];\n\tfor (const name of msg.addedToolNames ?? []) {\n\t\tconst normalizedName = normalizeToolName(name);\n\t\tif (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName)) continue;\n\t\tloadedToolNames.add(normalizedName);\n\t\treferences.push({\n\t\t\ttype: \"tool_reference\",\n\t\t\ttool_name: isOAuthToken ? toClaudeCodeName(name) : name,\n\t\t});\n\t}\n\tconst convertedContent = convertContentBlocks(msg.content);\n\t// Anthropic rejects tool references mixed with ordinary tool-result content.\n\treturn {\n\t\ttoolResult: {\n\t\t\ttype: \"tool_result\",\n\t\t\ttool_use_id: msg.toolCallId,\n\t\t\tcontent: references.length > 0 ? references : convertedContent,\n\t\t\tis_error: msg.isError,\n\t\t},\n\t\tsiblingContent:\n\t\t\treferences.length === 0\n\t\t\t\t? []\n\t\t\t\t: typeof convertedContent === \"string\"\n\t\t\t\t\t? [{ type: \"text\", text: convertedContent }]\n\t\t\t\t\t: convertedContent,\n\t};\n}\n\nfunction convertMessages(\n\ttransformedMessages: Message[],\n\tisOAuthToken: boolean,\n\tcacheControl?: CacheControlEphemeral,\n\tallowEmptySignature = false,\n\tdeferredToolNames: ReadonlySet<string> = new Set(),\n\tnormalizeToolName: (name: string) => string = (name) => name,\n): MessageParam[] {\n\tconst params: MessageParam[] = [];\n\tconst loadedToolNames = new Set<string>();\n\tconst providerToolCallIds = new Map<string, string>();\n\n\tfor (let i = 0; i < transformedMessages.length; i++) {\n\t\tconst msg = transformedMessages[i];\n\n\t\tif (msg.role === \"user\") {\n\t\t\tif (typeof msg.content === \"string\") {\n\t\t\t\tif (msg.content.trim().length > 0) {\n\t\t\t\t\tparams.push({\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: sanitizeSurrogates(msg.content),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst blocks: ContentBlockParam[] = msg.content.map((item) => {\n\t\t\t\t\tif (item.type === \"text\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(item.text),\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"image\",\n\t\t\t\t\t\t\tsource: {\n\t\t\t\t\t\t\t\ttype: \"base64\",\n\t\t\t\t\t\t\t\tmedia_type: item.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconst filteredBlocks = blocks.filter((b) => {\n\t\t\t\t\tif (b.type === \"text\") {\n\t\t\t\t\t\treturn b.text.trim().length > 0;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t\t\tif (filteredBlocks.length === 0) continue;\n\t\t\t\tparams.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: filteredBlocks,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst blocks: ContentBlockParam[] = [];\n\t\t\tconst providerReplay = (msg as ProviderReplayAssistantMessage).providerReplay;\n\t\t\tconst canReplayProviderContent =\n\t\t\t\tproviderReplay?.provider === msg.provider &&\n\t\t\t\tproviderReplay.api === msg.api &&\n\t\t\t\tproviderReplay.model === msg.model &&\n\t\t\t\tproviderReplay.api === \"anthropic-messages\";\n\n\t\t\tif (canReplayProviderContent) {\n\t\t\t\tconst replayBlocks = structuredClone(providerReplay.content) as unknown as ContentBlockParam[];\n\t\t\t\tconst replayToolCalls = replayBlocks.filter(\n\t\t\t\t\t(block): block is Extract<ContentBlockParam, { type: \"tool_use\" }> => block.type === \"tool_use\",\n\t\t\t\t);\n\t\t\t\tconst localToolCalls = msg.content.filter((block): block is ToolCall => block.type === \"toolCall\");\n\t\t\t\tfor (let index = 0; index < Math.min(localToolCalls.length, replayToolCalls.length); index++) {\n\t\t\t\t\tproviderToolCallIds.set(localToolCalls[index].id, replayToolCalls[index].id);\n\t\t\t\t}\n\t\t\t\tblocks.push(...replayBlocks);\n\t\t\t} else {\n\t\t\t\tfor (let index = 0; index < msg.content.length - 1; index++) {\n\t\t\t\t\tconst current = msg.content[index];\n\t\t\t\t\tconst next = msg.content[index + 1];\n\t\t\t\t\tif (\n\t\t\t\t\t\tcurrent.type === \"thinking\" &&\n\t\t\t\t\t\tnext.type === \"thinking\" &&\n\t\t\t\t\t\tcurrent.thinkingSignature &&\n\t\t\t\t\t\tnext.thinkingSignature &&\n\t\t\t\t\t\tmsg.content.slice(index + 2).some((block) => block.type === \"toolCall\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Cannot safely replay this Anthropic assistant message: it contains adjacent signed thinking blocks but no provider-native replay data. mcpi 0.85.2 could drop native server-tool blocks between those thoughts. Start a new session or branch before this turn, or start a new session with thinking disabled.\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (const block of msg.content) {\n\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\tif (block.text.trim().length === 0) continue;\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\t// Redacted thinking: pass the opaque payload back as redacted_thinking\n\t\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\t\ttype: \"redacted_thinking\",\n\t\t\t\t\t\t\t\tdata: block.thinkingSignature!,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst thinkingSignature = block.thinkingSignature;\n\t\t\t\t\t\tconst hasThinkingSignature = !!thinkingSignature && thinkingSignature.trim().length > 0;\n\t\t\t\t\t\tif (block.thinking.trim().length === 0 && !hasThinkingSignature) continue;\n\t\t\t\t\t\t// If thinking signature is missing/empty (e.g., from aborted stream),\n\t\t\t\t\t\t// convert to plain text for Anthropic. Some compatible providers emit\n\t\t\t\t\t\t// and accept empty signatures, so let marked models preserve the block.\n\t\t\t\t\t\tif (!hasThinkingSignature) {\n\t\t\t\t\t\t\tblocks.push(\n\t\t\t\t\t\t\t\tallowEmptySignature\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\t\t\t\t\tthinking: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\t\t\t\t\tsignature: \"\",\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Signatures authenticate the exact thinking text. Never sanitize\n\t\t\t\t\t\t\t// or otherwise rewrite a signed block during replay.\n\t\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\t\tthinking: block.thinking,\n\t\t\t\t\t\t\t\tsignature: thinkingSignature,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"tool_use\",\n\t\t\t\t\t\t\tid: block.id,\n\t\t\t\t\t\t\tname: isOAuthToken ? toClaudeCodeName(block.name) : block.name,\n\t\t\t\t\t\t\tinput: block.arguments ?? {},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (blocks.length === 0) continue;\n\t\t\tparams.push({\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: blocks,\n\t\t\t});\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.\n\t\t\tconst toolResults: ContentBlockParam[] = [];\n\t\t\tconst siblingContent: ContentBlockParam[] = [];\n\t\t\tlet j = i;\n\t\t\twhile (j < transformedMessages.length && transformedMessages[j].role === \"toolResult\") {\n\t\t\t\tconst toolResult = transformedMessages[j] as ToolResultMessage;\n\t\t\t\tconst converted = convertToolResult(\n\t\t\t\t\t{\n\t\t\t\t\t\t...toolResult,\n\t\t\t\t\t\ttoolCallId: providerToolCallIds.get(toolResult.toolCallId) ?? toolResult.toolCallId,\n\t\t\t\t\t},\n\t\t\t\t\tisOAuthToken,\n\t\t\t\t\tdeferredToolNames,\n\t\t\t\t\tloadedToolNames,\n\t\t\t\t\tnormalizeToolName,\n\t\t\t\t);\n\t\t\t\ttoolResults.push(converted.toolResult);\n\t\t\t\tsiblingContent.push(...converted.siblingContent);\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\t// Skip the messages we've already processed.\n\t\t\ti = j - 1;\n\n\t\t\t// Displaced reference-bearing results must follow every tool_result block.\n\t\t\tparams.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [...toolResults, ...siblingContent],\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add cache_control to the last user message to cache conversation history\n\tif (cacheControl && params.length > 0) {\n\t\tconst lastMessage = params[params.length - 1];\n\t\tif (lastMessage.role === \"user\") {\n\t\t\tif (Array.isArray(lastMessage.content)) {\n\t\t\t\tconst lastBlock = lastMessage.content[lastMessage.content.length - 1];\n\t\t\t\tif (\n\t\t\t\t\tlastBlock &&\n\t\t\t\t\t(lastBlock.type === \"text\" || lastBlock.type === \"image\" || lastBlock.type === \"tool_result\")\n\t\t\t\t) {\n\t\t\t\t\t(lastBlock as any).cache_control = cacheControl;\n\t\t\t\t}\n\t\t\t} else if (typeof lastMessage.content === \"string\") {\n\t\t\t\tlastMessage.content = [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: lastMessage.content,\n\t\t\t\t\t\tcache_control: cacheControl,\n\t\t\t\t\t},\n\t\t\t\t] as any;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn params;\n}\n\nfunction shouldUseFineGrainedToolStreamingBeta(model: Model<\"anthropic-messages\">, context: Context): boolean {\n\treturn !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;\n}\n\nfunction convertTools(\n\ttools: Tool[],\n\tisOAuthToken: boolean,\n\tsupportsEagerToolInputStreaming: boolean,\n\tsupportsStrictTools: boolean,\n\tcacheControl?: CacheControlEphemeral,\n\tdeferLoading = false,\n): Anthropic.Messages.Tool[] {\n\tif (!tools) return [];\n\n\treturn tools.map((tool, index) => {\n\t\tconst strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools);\n\t\tconst parameters = getJsonSchemaToolParameters(tool, strict);\n\t\tconst schema = parameters as { properties?: unknown; required?: string[] };\n\t\tconst legacyInputSchema = {\n\t\t\ttype: \"object\" as const,\n\t\t\tproperties: schema.properties ?? {},\n\t\t\trequired: schema.required ?? [],\n\t\t};\n\t\tconst inputSchema =\n\t\t\tstrict === true\n\t\t\t\t? {\n\t\t\t\t\t\t...(parameters as Record<string, unknown>),\n\t\t\t\t\t\t...legacyInputSchema,\n\t\t\t\t\t}\n\t\t\t\t: legacyInputSchema;\n\n\t\treturn {\n\t\t\tname: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,\n\t\t\tdescription: tool.description,\n\t\t\t...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),\n\t\t\t...(strict === true ? { strict: true } : {}),\n\t\t\tinput_schema: inputSchema,\n\t\t\t...(deferLoading ? { defer_loading: true } : {}),\n\t\t\t...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),\n\t\t};\n\t});\n}\n\nfunction mapStopReason(\n\treason: Anthropic.Messages.StopReason | string,\n\tstopDetails?: RefusalStopDetails | null,\n): { stopReason: StopReason; errorMessage?: string } {\n\tswitch (reason) {\n\t\tcase \"end_turn\":\n\t\t\treturn { stopReason: \"stop\" };\n\t\tcase \"max_tokens\":\n\t\t\treturn { stopReason: \"length\" };\n\t\tcase \"tool_use\":\n\t\t\treturn { stopReason: \"toolUse\" };\n\t\tcase \"refusal\":\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: stopDetails?.explanation || `The model refused to complete the request`,\n\t\t\t};\n\t\tcase \"pause_turn\": // Stop is good enough -> resubmit\n\t\t\treturn { stopReason: \"stop\" };\n\t\tcase \"stop_sequence\":\n\t\t\treturn { stopReason: \"stop\" }; // We don't supply stop sequences, so this should never happen\n\t\tcase \"sensitive\": // Content flagged by safety filters (not yet in SDK types)\n\t\t\treturn { stopReason: \"error\", errorMessage: \"Provider stopped with: sensitive\" };\n\t\tdefault:\n\t\t\t// Handle unknown stop reasons gracefully (API may add new values)\n\t\t\tthrow new Error(`Unhandled stop reason: ${reason}`);\n\t}\n}\n"]}