@earendil-works/pi-ai 0.80.5 → 0.80.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +102 -50
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.d.ts +1 -1
- package/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +19 -11
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts +2 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +6 -3
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +12 -3
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts +3 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +57 -4
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts +2 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +24 -8
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts +97 -0
- package/dist/api/pi-messages.d.ts.map +1 -0
- package/dist/api/pi-messages.js +309 -0
- package/dist/api/pi-messages.js.map +1 -0
- package/dist/api/pi-messages.lazy.d.ts +3 -0
- package/dist/api/pi-messages.lazy.d.ts.map +1 -0
- package/dist/api/pi-messages.lazy.js +3 -0
- package/dist/api/pi-messages.lazy.js.map +1 -0
- package/dist/api/simple-options.d.ts +1 -1
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +1 -1
- package/dist/api/simple-options.js.map +1 -1
- package/dist/compat.d.ts +2 -0
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +5 -1
- package/dist/compat.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +706 -215
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.js +15 -6
- package/dist/models.js.map +1 -1
- package/dist/providers/all.d.ts +9 -5
- package/dist/providers/all.d.ts.map +1 -1
- package/dist/providers/all.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +5 -1
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/amazon-bedrock.models.d.ts +122 -5
- package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.models.js +86 -20
- package/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/dist/providers/anthropic.models.d.ts +11 -1
- package/dist/providers/anthropic.models.d.ts.map +1 -1
- package/dist/providers/anthropic.models.js +6 -4
- package/dist/providers/anthropic.models.js.map +1 -1
- package/dist/providers/azure-openai-responses.models.d.ts +7 -8
- package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.models.js +20 -21
- package/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/dist/providers/cerebras.models.d.ts.map +1 -1
- package/dist/providers/cerebras.models.js +1 -1
- package/dist/providers/cerebras.models.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.d.ts +103 -1
- package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js +78 -4
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/dist/providers/cloudflare-auth.js +9 -6
- package/dist/providers/cloudflare-auth.js.map +1 -1
- package/dist/providers/deepseek.models.d.ts +2 -2
- package/dist/providers/deepseek.models.d.ts.map +1 -1
- package/dist/providers/deepseek.models.js +2 -2
- package/dist/providers/deepseek.models.js.map +1 -1
- package/dist/providers/fireworks.models.d.ts +2 -2
- package/dist/providers/fireworks.models.d.ts.map +1 -1
- package/dist/providers/fireworks.models.js +2 -2
- package/dist/providers/fireworks.models.js.map +1 -1
- package/dist/providers/github-copilot.models.d.ts +101 -8
- package/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/dist/providers/github-copilot.models.js +65 -7
- package/dist/providers/github-copilot.models.js.map +1 -1
- package/dist/providers/openai-codex.models.d.ts +56 -0
- package/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/dist/providers/openai-codex.models.js +20 -9
- package/dist/providers/openai-codex.models.js.map +1 -1
- package/dist/providers/openai.models.d.ts +77 -8
- package/dist/providers/openai.models.d.ts.map +1 -1
- package/dist/providers/openai.models.js +37 -24
- package/dist/providers/openai.models.js.map +1 -1
- package/dist/providers/opencode-go.models.d.ts +3 -3
- package/dist/providers/opencode-go.models.d.ts.map +1 -1
- package/dist/providers/opencode-go.models.js +3 -3
- package/dist/providers/opencode-go.models.js.map +1 -1
- package/dist/providers/opencode.models.d.ts +137 -4
- package/dist/providers/opencode.models.d.ts.map +1 -1
- package/dist/providers/opencode.models.js +83 -8
- package/dist/providers/opencode.models.js.map +1 -1
- package/dist/providers/openrouter.models.d.ts +48 -44
- package/dist/providers/openrouter.models.d.ts.map +1 -1
- package/dist/providers/openrouter.models.js +112 -127
- package/dist/providers/openrouter.models.js.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.d.ts +19 -111
- package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.js +42 -142
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/dist/providers/zai-coding-cn.models.d.ts +1 -1
- package/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
- package/dist/providers/zai-coding-cn.models.js +1 -1
- package/dist/providers/zai-coding-cn.models.js.map +1 -1
- package/dist/providers/zai.models.d.ts +1 -1
- package/dist/providers/zai.models.d.ts.map +1 -1
- package/dist/providers/zai.models.js +1 -1
- package/dist/providers/zai.models.js.map +1 -1
- package/dist/types.d.ts +41 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/deferred-tools.d.ts +9 -0
- package/dist/utils/deferred-tools.d.ts.map +1 -0
- package/dist/utils/deferred-tools.js +36 -0
- package/dist/utils/deferred-tools.js.map +1 -0
- package/dist/utils/estimate.d.ts +3 -3
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +35 -14
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +1 -0
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +9 -0
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/radius.d.ts +39 -0
- package/dist/utils/oauth/radius.d.ts.map +1 -0
- package/dist/utils/oauth/radius.js +415 -0
- package/dist/utils/oauth/radius.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -730,7 +730,7 @@ if (model.reasoning) {
|
|
|
730
730
|
const response = await models.completeSimple(model, {
|
|
731
731
|
messages: [{ role: 'user', content: 'Solve: 2x + 5 = 13', timestamp: Date.now() }]
|
|
732
732
|
}, {
|
|
733
|
-
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
733
|
+
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
|
734
734
|
});
|
|
735
735
|
|
|
736
736
|
// Access thinking and text blocks
|
|
@@ -743,6 +743,8 @@ for (const block of response.content) {
|
|
|
743
743
|
}
|
|
744
744
|
```
|
|
745
745
|
|
|
746
|
+
`xhigh` and `max` are model-specific, opt-in levels. Use `getSupportedThinkingLevels(model)` to determine whether a concrete model exposes either level; models such as GPT-5.6 can expose both.
|
|
747
|
+
|
|
746
748
|
### Provider-Specific Options (stream/complete)
|
|
747
749
|
|
|
748
750
|
`models.stream()`/`complete()` accept the owning API's full option set. Use `hasApi()` to narrow a dynamically looked-up model to its API for full option typing:
|
|
@@ -999,7 +1001,7 @@ Custom models can carry `headers` (e.g. proxies behind bot detection) and `compa
|
|
|
999
1001
|
|
|
1000
1002
|
Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so the system prompt is sent as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too. This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers.
|
|
1001
1003
|
|
|
1002
|
-
Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`). Missing
|
|
1004
|
+
Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Missing standard levels through `high` use provider defaults; `xhigh` and `max` are opt-in and require a non-null map entry. String values are sent to the provider, `null` marks a level unsupported, and maps may skip levels.
|
|
1003
1005
|
|
|
1004
1006
|
```typescript
|
|
1005
1007
|
const ollamaReasoningModel: Model<'openai-completions'> = {
|
|
@@ -1068,7 +1070,8 @@ interface OpenAICompletionsCompat {
|
|
|
1068
1070
|
supportsReasoningEffort?: boolean; // Whether provider supports `reasoning_effort` (default: true)
|
|
1069
1071
|
supportsUsageInStreaming?: boolean; // Whether provider supports `stream_options: { include_usage: true }` (default: true)
|
|
1070
1072
|
supportsStrictMode?: boolean; // Whether provider supports `strict` in tool definitions (default: true)
|
|
1071
|
-
sendSessionAffinityHeaders?: boolean; //
|
|
1073
|
+
sendSessionAffinityHeaders?: boolean; // Send session-affinity data from `sessionId` (default: false)
|
|
1074
|
+
sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter'; // Format for session affinity: 'openai' uses `prompt_cache_key`, `session_id`, `x-client-request-id`, and `x-session-affinity`; 'openai-nosession' uses `prompt_cache_key`, `x-client-request-id`, and `x-session-affinity`; 'openrouter' uses `x-session-id` (default: auto-detected)
|
|
1072
1075
|
maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
|
|
1073
1076
|
requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
|
|
1074
1077
|
requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
|
|
@@ -1083,7 +1086,7 @@ interface OpenAICompletionsCompat {
|
|
|
1083
1086
|
|
|
1084
1087
|
interface OpenAIResponsesCompat {
|
|
1085
1088
|
supportsDeveloperRole?: boolean; // Whether provider supports `developer` role vs `system` (default: true)
|
|
1086
|
-
|
|
1089
|
+
sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter'; // Session-affinity header format: 'openai' sends `session_id` and `x-client-request-id`; 'openai-nosession' sends `x-client-request-id`; 'openrouter' sends `x-session-id`. Does not affect the `prompt_cache_key` body param (default: auto-detected)
|
|
1087
1090
|
supportsLongCacheRetention?: boolean; // Whether provider supports `prompt_cache_retention: "24h"` (default: true)
|
|
1088
1091
|
}
|
|
1089
1092
|
```
|
|
@@ -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;AAoIrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAkBhE,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;AAiOD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAiRzE,CAAC;AA0BF,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 { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\nimport { headersToRecord } from \"../utils/headers.ts\";\nimport { parseJsonWithRepair, parseStreamingJson } from \"../utils/json-parse.ts\";\nimport { getProviderEnvValue } from \"../utils/provider-env.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\n\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 PI_CACHE_RETENTION for backward compatibility.\n */\nfunction resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {\n\tif (cacheRetention) {\n\t\treturn cacheRetention;\n\t}\n\tif (getProviderEnvValue(\"PI_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};\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 only)\n\t * - \"xhigh\": Highest reasoning level (Opus 4.7+, 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 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: \"stop\",\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\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: options?.maxRetries ?? 0,\n\t\t\t};\n\t\t\tconst response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();\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: \"\",\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: \"\",\n\t\t\t\t\t\t\tthinkingSignature: \"\",\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\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.input_tokens != null) {\n\t\t\t\t\t\toutput.usage.input = event.usage.input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.output_tokens != null) {\n\t\t\t\t\t\toutput.usage.output = event.usage.output_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.cache_read_input_tokens != null) {\n\t\t\t\t\t\toutput.usage.cacheRead = event.usage.cache_read_input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.cache_creation_input_tokens != null) {\n\t\t\t\t\t\toutput.usage.cacheWrite = event.usage.cache_creation_input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\t// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the\n\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// its Usage type, so read it through a narrow cast. Verified against the live API.\n\t\t\t\t\tconst thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })\n\t\t\t\t\t\t.output_tokens_details?.thinking_tokens;\n\t\t\t\t\tif (thinkingTokens != null) {\n\t\t\t\t\t\toutput.usage.reasoning = thinkingTokens;\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 === \"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 only valid on Opus 4.6, while Opus 4.7+ and Fable 5 support \"xhigh\".\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\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\tdefaultHeaders: mergeHeaders(\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\tdefaultHeaders: mergeHeaders(\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 = mergeHeaders(\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\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 params: MessageCreateParamsStreaming = {\n\t\tmodel: model.id,\n\t\tmessages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),\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 (context.tools && context.tools.length > 0) {\n\t\tparams.tools = convertTools(\n\t\t\tcontext.tools,\n\t\t\tisOAuthToken,\n\t\t\tcompat.supportsEagerToolInputStreaming,\n\t\t\tcompat.supportsCacheControlOnTools ? cacheControl : undefined,\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 convertMessages(\n\tmessages: Message[],\n\tmodel: Model<\"anthropic-messages\">,\n\tisOAuthToken: boolean,\n\tcacheControl?: CacheControlEphemeral,\n\tallowEmptySignature = false,\n): MessageParam[] {\n\tconst params: MessageParam[] = [];\n\n\t// Transform messages for cross-provider compatibility\n\tconst transformedMessages = transformMessages(messages, model, normalizeToolCallId);\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\tif (block.thinking.trim().length === 0) 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 (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {\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: block.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\n\t\t\t// Add the current tool result\n\t\t\ttoolResults.push({\n\t\t\t\ttype: \"tool_result\",\n\t\t\t\ttool_use_id: msg.toolCallId,\n\t\t\t\tcontent: convertContentBlocks(msg.content),\n\t\t\t\tis_error: msg.isError,\n\t\t\t});\n\n\t\t\t// Look ahead for consecutive toolResult messages\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < transformedMessages.length && transformedMessages[j].role === \"toolResult\") {\n\t\t\t\tconst nextMsg = transformedMessages[j] as ToolResultMessage; // We know it's a toolResult\n\t\t\t\ttoolResults.push({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttool_use_id: nextMsg.toolCallId,\n\t\t\t\t\tcontent: convertContentBlocks(nextMsg.content),\n\t\t\t\t\tis_error: nextMsg.isError,\n\t\t\t\t});\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// Add a single user message with all tool results\n\t\t\tparams.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: toolResults,\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\tcacheControl?: CacheControlEphemeral,\n): Anthropic.Messages.Tool[] {\n\tif (!tools) return [];\n\n\treturn tools.map((tool, index) => {\n\t\tconst schema = tool.parameters as { properties?: unknown; required?: string[] };\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\tinput_schema: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: schema.properties ?? {},\n\t\t\t\trequired: schema.required ?? [],\n\t\t\t},\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\" };\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;AAU1C,OAAO,KAAK,EAWX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAqIrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAiChE,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;AAiOD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAmRzE,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 { getProviderEnvValue } from \"../utils/provider-env.ts\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.ts\";\n\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 PI_CACHE_RETENTION for backward compatibility.\n */\nfunction resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {\n\tif (cacheRetention) {\n\t\treturn cacheRetention;\n\t}\n\tif (getProviderEnvValue(\"PI_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\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 only)\n\t * - \"xhigh\": Highest reasoning level (Opus 4.7+, 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 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: \"stop\",\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\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: options?.maxRetries ?? 0,\n\t\t\t};\n\t\t\tconst response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();\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: \"\",\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: \"\",\n\t\t\t\t\t\t\tthinkingSignature: \"\",\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\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 === \"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\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\tdefaultHeaders: mergeHeaders(\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\tdefaultHeaders: mergeHeaders(\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 = mergeHeaders(\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\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.supportsCacheControlOnTools ? cacheControl : undefined,\n\t\t\t),\n\t\t\t...convertTools(deferredTools, isOAuthToken, compat.supportsEagerToolInputStreaming, undefined, true),\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\tcacheControl?: CacheControlEphemeral,\n\tdeferLoading = false,\n): Anthropic.Messages.Tool[] {\n\tif (!tools) return [];\n\n\treturn tools.map((tool, index) => {\n\t\tconst schema = tool.parameters as { properties?: unknown; required?: string[] };\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\tinput_schema: {\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: schema.properties ?? {},\n\t\t\t\trequired: schema.required ?? [],\n\t\t\t},\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\" };\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,5 +1,6 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { calculateCost } from "../models.js";
|
|
3
|
+
import { splitDeferredTools } from "../utils/deferred-tools.js";
|
|
3
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
4
5
|
import { headersToRecord } from "../utils/headers.js";
|
|
5
6
|
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
|
|
@@ -114,8 +115,24 @@ function getAnthropicCompat(model) {
|
|
|
114
115
|
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,
|
|
115
116
|
supportsTemperature: model.compat?.supportsTemperature ?? true,
|
|
116
117
|
allowEmptySignature: model.compat?.allowEmptySignature ?? false,
|
|
118
|
+
supportsToolReferences: model.compat?.supportsToolReferences ?? defaultSupportsToolReferences(model),
|
|
117
119
|
};
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Default for `supportsToolReferences`: first-party Anthropic models except
|
|
123
|
+
* Haiku (rejects client-side tool_reference blocks) and models that predate
|
|
124
|
+
* tool search (Claude 3.x, Opus/Sonnet 4.0, Opus 4.1).
|
|
125
|
+
*/
|
|
126
|
+
function defaultSupportsToolReferences(model) {
|
|
127
|
+
if (model.provider !== "anthropic" || model.id.includes("haiku"))
|
|
128
|
+
return false;
|
|
129
|
+
const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
|
|
130
|
+
if (!version)
|
|
131
|
+
return false;
|
|
132
|
+
const major = Number(version[1]);
|
|
133
|
+
const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
|
|
134
|
+
return major > 4 || (major === 4 && minor >= 5);
|
|
135
|
+
}
|
|
119
136
|
function mergeHeaders(...headerSources) {
|
|
120
137
|
const merged = {};
|
|
121
138
|
for (const headers of headerSources) {
|
|
@@ -510,25 +527,27 @@ export const stream = (model, context, options) => {
|
|
|
510
527
|
}
|
|
511
528
|
// Only update usage fields if present (not null).
|
|
512
529
|
// Preserves input_tokens from message_start when proxies omit it in message_delta.
|
|
513
|
-
if (event.usage
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
.
|
|
530
|
-
|
|
531
|
-
|
|
530
|
+
if (event.usage) {
|
|
531
|
+
if (event.usage.input_tokens != null) {
|
|
532
|
+
output.usage.input = event.usage.input_tokens;
|
|
533
|
+
}
|
|
534
|
+
if (event.usage.output_tokens != null) {
|
|
535
|
+
output.usage.output = event.usage.output_tokens;
|
|
536
|
+
}
|
|
537
|
+
if (event.usage.cache_read_input_tokens != null) {
|
|
538
|
+
output.usage.cacheRead = event.usage.cache_read_input_tokens;
|
|
539
|
+
}
|
|
540
|
+
if (event.usage.cache_creation_input_tokens != null) {
|
|
541
|
+
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
|
542
|
+
}
|
|
543
|
+
// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the
|
|
544
|
+
// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from
|
|
545
|
+
// its Usage type, so read it through a narrow cast. Verified against the live API.
|
|
546
|
+
const thinkingTokens = event.usage
|
|
547
|
+
.output_tokens_details?.thinking_tokens;
|
|
548
|
+
if (thinkingTokens != null) {
|
|
549
|
+
output.usage.reasoning = thinkingTokens;
|
|
550
|
+
}
|
|
532
551
|
}
|
|
533
552
|
// Anthropic doesn't provide total_tokens, compute from components
|
|
534
553
|
output.usage.totalTokens =
|
|
@@ -561,7 +580,8 @@ export const stream = (model, context, options) => {
|
|
|
561
580
|
};
|
|
562
581
|
/**
|
|
563
582
|
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
|
|
564
|
-
* Note: effort "max" is
|
|
583
|
+
* Note: effort "max" is available on all adaptive-thinking Claude models, while native
|
|
584
|
+
* "xhigh" is only available on Opus 4.7/4.8, Sonnet 5, and Fable 5.
|
|
565
585
|
*/
|
|
566
586
|
function mapThinkingLevelToEffort(model, level) {
|
|
567
587
|
const mapped = level ? model.thinkingLevelMap?.[level] : undefined;
|
|
@@ -670,9 +690,19 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
670
690
|
function buildParams(model, context, isOAuthToken, options) {
|
|
671
691
|
const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
|
|
672
692
|
const compat = getAnthropicCompat(model);
|
|
693
|
+
const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
|
|
694
|
+
const normalizeToolName = isOAuthToken ? toClaudeCodeName : (name) => name;
|
|
695
|
+
const toolPlacement = splitDeferredTools({ ...context, messages: transformedMessages }, compat.supportsToolReferences, normalizeToolName);
|
|
696
|
+
let immediateTools = toolPlacement.immediate;
|
|
697
|
+
let deferredTools = [...toolPlacement.deferred.values()];
|
|
698
|
+
if (immediateTools.length === 0 && deferredTools.length > 0) {
|
|
699
|
+
immediateTools = deferredTools;
|
|
700
|
+
deferredTools = [];
|
|
701
|
+
}
|
|
702
|
+
const deferredToolNames = new Set(deferredTools.map((tool) => normalizeToolName(tool.name)));
|
|
673
703
|
const params = {
|
|
674
704
|
model: model.id,
|
|
675
|
-
messages: convertMessages(
|
|
705
|
+
messages: convertMessages(transformedMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, deferredToolNames, normalizeToolName),
|
|
676
706
|
max_tokens: options?.maxTokens ?? model.maxTokens,
|
|
677
707
|
stream: true,
|
|
678
708
|
};
|
|
@@ -707,8 +737,11 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
707
737
|
if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
|
|
708
738
|
params.temperature = options.temperature;
|
|
709
739
|
}
|
|
710
|
-
if (
|
|
711
|
-
params.tools =
|
|
740
|
+
if (immediateTools.length > 0 || deferredTools.length > 0) {
|
|
741
|
+
params.tools = [
|
|
742
|
+
...convertTools(immediateTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined),
|
|
743
|
+
...convertTools(deferredTools, isOAuthToken, compat.supportsEagerToolInputStreaming, undefined, true),
|
|
744
|
+
];
|
|
712
745
|
}
|
|
713
746
|
// Configure thinking mode: adaptive, budget-based, or explicitly disabled.
|
|
714
747
|
if (model.reasoning) {
|
|
@@ -760,10 +793,37 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
760
793
|
function normalizeToolCallId(id) {
|
|
761
794
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
762
795
|
}
|
|
763
|
-
function
|
|
796
|
+
function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName) {
|
|
797
|
+
const references = [];
|
|
798
|
+
for (const name of msg.addedToolNames ?? []) {
|
|
799
|
+
const normalizedName = normalizeToolName(name);
|
|
800
|
+
if (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName))
|
|
801
|
+
continue;
|
|
802
|
+
loadedToolNames.add(normalizedName);
|
|
803
|
+
references.push({
|
|
804
|
+
type: "tool_reference",
|
|
805
|
+
tool_name: isOAuthToken ? toClaudeCodeName(name) : name,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const convertedContent = convertContentBlocks(msg.content);
|
|
809
|
+
// Anthropic rejects tool references mixed with ordinary tool-result content.
|
|
810
|
+
return {
|
|
811
|
+
toolResult: {
|
|
812
|
+
type: "tool_result",
|
|
813
|
+
tool_use_id: msg.toolCallId,
|
|
814
|
+
content: references.length > 0 ? references : convertedContent,
|
|
815
|
+
is_error: msg.isError,
|
|
816
|
+
},
|
|
817
|
+
siblingContent: references.length === 0
|
|
818
|
+
? []
|
|
819
|
+
: typeof convertedContent === "string"
|
|
820
|
+
? [{ type: "text", text: convertedContent }]
|
|
821
|
+
: convertedContent,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowEmptySignature = false, deferredToolNames = new Set(), normalizeToolName = (name) => name) {
|
|
764
825
|
const params = [];
|
|
765
|
-
|
|
766
|
-
const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
|
|
826
|
+
const loadedToolNames = new Set();
|
|
767
827
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
768
828
|
const msg = transformedMessages[i];
|
|
769
829
|
if (msg.role === "user") {
|
|
@@ -828,12 +888,14 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmpty
|
|
|
828
888
|
});
|
|
829
889
|
continue;
|
|
830
890
|
}
|
|
831
|
-
|
|
891
|
+
const thinkingSignature = block.thinkingSignature;
|
|
892
|
+
const hasThinkingSignature = !!thinkingSignature && thinkingSignature.trim().length > 0;
|
|
893
|
+
if (block.thinking.trim().length === 0 && !hasThinkingSignature)
|
|
832
894
|
continue;
|
|
833
895
|
// If thinking signature is missing/empty (e.g., from aborted stream),
|
|
834
896
|
// convert to plain text for Anthropic. Some compatible providers emit
|
|
835
897
|
// and accept empty signatures, so let marked models preserve the block.
|
|
836
|
-
if (!
|
|
898
|
+
if (!hasThinkingSignature) {
|
|
837
899
|
blocks.push(allowEmptySignature
|
|
838
900
|
? {
|
|
839
901
|
type: "thinking",
|
|
@@ -849,7 +911,7 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmpty
|
|
|
849
911
|
blocks.push({
|
|
850
912
|
type: "thinking",
|
|
851
913
|
thinking: sanitizeSurrogates(block.thinking),
|
|
852
|
-
signature:
|
|
914
|
+
signature: thinkingSignature,
|
|
853
915
|
});
|
|
854
916
|
}
|
|
855
917
|
}
|
|
@@ -870,33 +932,22 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmpty
|
|
|
870
932
|
});
|
|
871
933
|
}
|
|
872
934
|
else if (msg.role === "toolResult") {
|
|
873
|
-
// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint
|
|
935
|
+
// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
|
|
874
936
|
const toolResults = [];
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
type: "tool_result",
|
|
878
|
-
tool_use_id: msg.toolCallId,
|
|
879
|
-
content: convertContentBlocks(msg.content),
|
|
880
|
-
is_error: msg.isError,
|
|
881
|
-
});
|
|
882
|
-
// Look ahead for consecutive toolResult messages
|
|
883
|
-
let j = i + 1;
|
|
937
|
+
const siblingContent = [];
|
|
938
|
+
let j = i;
|
|
884
939
|
while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
|
|
885
|
-
const
|
|
886
|
-
toolResults.push(
|
|
887
|
-
|
|
888
|
-
tool_use_id: nextMsg.toolCallId,
|
|
889
|
-
content: convertContentBlocks(nextMsg.content),
|
|
890
|
-
is_error: nextMsg.isError,
|
|
891
|
-
});
|
|
940
|
+
const converted = convertToolResult(transformedMessages[j], isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName);
|
|
941
|
+
toolResults.push(converted.toolResult);
|
|
942
|
+
siblingContent.push(...converted.siblingContent);
|
|
892
943
|
j++;
|
|
893
944
|
}
|
|
894
|
-
// Skip the messages we've already processed
|
|
945
|
+
// Skip the messages we've already processed.
|
|
895
946
|
i = j - 1;
|
|
896
|
-
//
|
|
947
|
+
// Displaced reference-bearing results must follow every tool_result block.
|
|
897
948
|
params.push({
|
|
898
949
|
role: "user",
|
|
899
|
-
content: toolResults,
|
|
950
|
+
content: [...toolResults, ...siblingContent],
|
|
900
951
|
});
|
|
901
952
|
}
|
|
902
953
|
}
|
|
@@ -927,7 +978,7 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmpty
|
|
|
927
978
|
function shouldUseFineGrainedToolStreamingBeta(model, context) {
|
|
928
979
|
return !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
|
|
929
980
|
}
|
|
930
|
-
function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, cacheControl) {
|
|
981
|
+
function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, cacheControl, deferLoading = false) {
|
|
931
982
|
if (!tools)
|
|
932
983
|
return [];
|
|
933
984
|
return tools.map((tool, index) => {
|
|
@@ -941,6 +992,7 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, cach
|
|
|
941
992
|
properties: schema.properties ?? {},
|
|
942
993
|
required: schema.required ?? [],
|
|
943
994
|
},
|
|
995
|
+
...(deferLoading ? { defer_loading: true } : {}),
|
|
944
996
|
...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
|
|
945
997
|
};
|
|
946
998
|
});
|