import { APICallError, InvalidArgumentError, InvalidResponseDataError, type JSONObject, type LanguageModelV4, type LanguageModelV4CallOptions, type LanguageModelV4Content, type LanguageModelV4FinishReason, type LanguageModelV4FunctionTool, type LanguageModelV4GenerateResult, type LanguageModelV4Prompt, type LanguageModelV4Source, type LanguageModelV4StreamPart, type LanguageModelV4StreamResult, type LanguageModelV4ToolCall, type SharedV4ProviderMetadata, type SharedV4Warning, } from '@ai-sdk/provider'; import { combineHeaders, createEventSourceResponseHandler, createJsonResponseHandler, createProviderStreamError, createToolNameMapping, generateId, isCustomReasoning, isProviderStreamError, mapReasoningToProviderBudget, mapReasoningToProviderEffort, parseProviderOptions, postJsonToApi, resolve, resolveProviderReference, secureJsonParse, serializeModelOptions, WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE, type FetchFunction, type InferSchema, type ParseResult, type Resolvable, } from '@ai-sdk/provider-utils'; import { anthropicFailedResponseHandler } from './anthropic-error'; import type { AnthropicMessageMetadata, AnthropicUsageIteration, } from './anthropic-message-metadata'; import { mapAnthropicCitation } from './map-anthropic-citation'; import type { AnthropicTextProviderMetadata } from './anthropic-provider-metadata'; import { anthropicChunkSchema, anthropicResponseSchema, type AnthropicContainer, type AnthropicReasoningMetadata, type AnthropicResponseContextManagement, type AnthropicStopDetails, type AnthropicTool, type AnthropicToolCallCaller, type Citation, } from './anthropic-api'; import { anthropicLanguageModelOptions, type AnthropicModelId, type AnthropicLanguageModelOptions, } from './anthropic-language-model-options'; import { prepareTools } from './anthropic-prepare-tools'; import { convertAnthropicUsage, type AnthropicUsage, } from './convert-anthropic-usage'; import { convertToAnthropicPrompt, type AnthropicMediaPartConverter, } from './convert-to-anthropic-prompt'; import { CacheControlValidator } from './get-cache-control'; import { mapAnthropicStopReason } from './map-anthropic-stop-reason'; import { sanitizeJsonSchema } from './sanitize-json-schema'; function createAnthropicStreamError(error: { message: string; type: string; code?: string | number | null; statusCode?: number | null; isRetryable?: boolean | null; data?: unknown; }) { const inferredMetadata = getAnthropicStreamErrorMetadata(error.type); return createProviderStreamError({ message: error.message, type: error.type, code: error.code ?? undefined, statusCode: error.statusCode ?? inferredMetadata.statusCode, isRetryable: error.isRetryable ?? inferredMetadata.isRetryable, data: 'data' in error ? error.data : error, }); } function getAnthropicStreamErrorMetadata(type: string): { statusCode?: number; isRetryable?: boolean; } { switch (type) { case 'api_error': return { statusCode: 500, isRetryable: true }; case 'overloaded_error': return { statusCode: 529, isRetryable: true }; case 'rate_limit_error': return { statusCode: 429, isRetryable: true }; case 'request_too_large': return { statusCode: 413, isRetryable: false }; case 'authentication_error': return { statusCode: 401, isRetryable: false }; case 'permission_error': return { statusCode: 403, isRetryable: false }; case 'not_found_error': return { statusCode: 404, isRetryable: false }; case 'billing_error': case 'invalid_request_error': return { statusCode: 400, isRetryable: false }; default: return {}; } } export function createCitationSource( citation: Citation, citationDocuments: Array<{ title: string; filename?: string; mediaType: string; }>, generateId: () => string, ): LanguageModelV4Source | undefined { if (citation.type === 'web_search_result_location') { return { type: 'source', sourceType: 'url', id: generateId(), url: citation.url, title: citation.title ?? undefined, providerMetadata: { anthropic: { citedText: citation.cited_text, encryptedIndex: citation.encrypted_index, }, }, }; } if (citation.type !== 'page_location' && citation.type !== 'char_location') { return; } const documentInfo = citationDocuments[citation.document_index]; if (!documentInfo) { return; } return { type: 'source' as const, sourceType: 'document' as const, id: generateId(), mediaType: documentInfo.mediaType, title: citation.document_title ?? documentInfo.title, filename: documentInfo.filename, providerMetadata: { anthropic: citation.type === 'page_location' ? { citedText: citation.cited_text, startPageNumber: citation.start_page_number, endPageNumber: citation.end_page_number, } : { citedText: citation.cited_text, startCharIndex: citation.start_char_index, endCharIndex: citation.end_char_index, }, } satisfies SharedV4ProviderMetadata, }; } function getAnthropicCallerInfo(caller: AnthropicToolCallCaller | undefined) { return caller == null ? undefined : { type: caller.type, toolId: 'tool_id' in caller ? caller.tool_id : undefined, }; } function getAnthropicCallerMetadata( caller: AnthropicToolCallCaller | undefined, ) { const callerInfo = getAnthropicCallerInfo(caller); return callerInfo == null ? {} : { providerMetadata: { anthropic: { caller: callerInfo } } }; } /** * Maps the custom tool names of toolset tools (e.g. the computer toolset) to * the toolset name that the Anthropic API uses in `toolset_name`. */ function getAnthropicToolsetNames( tools: LanguageModelV4CallOptions['tools'], ): Record { const toolsetNames: Record = {}; for (const tool of tools ?? []) { if ( tool.type === 'provider' && tool.id === 'anthropic.computer_toolset_20260801' ) { toolsetNames[tool.name] = 'computer'; } } return toolsetNames; } /** * Toolset member calls (e.g. `left_click` from the computer toolset) are * exposed as a single tool call on the toolset tool. The member name is * injected as the `action` property, matching the input shape of the older * computer tool versions. */ function toToolsetMemberInput({ memberName, input, }: { memberName: string; input: unknown; }): JSONObject { return { action: memberName, ...(typeof input === 'object' && input !== null && !Array.isArray(input) ? (input as JSONObject) : {}), }; } function getProviderOptionsName(provider: string): string { const dotIndex = provider.indexOf('.'); return dotIndex === -1 ? provider : provider.substring(0, dotIndex); } export type AnthropicLanguageModelConfig = { provider: string; baseURL: string; headers?: Resolvable>; fetch?: FetchFunction; buildRequestUrl?: (baseURL: string, isStreaming: boolean) => string; /** Custom media conversion for compatible providers; undefined uses the default. */ convertMediaPart?: AnthropicMediaPartConverter; transformRequestBody?: ( args: Record, betas: Set, ) => Record; transformRequestHeaders?: ( headers: Record, providerOptions: LanguageModelV4CallOptions['providerOptions'], ) => | Record | PromiseLike>; supportedUrls?: () => LanguageModelV4['supportedUrls']; generateId?: () => string; supportsNativeStructuredOutput?: boolean; supportsStrictTools?: boolean; /** Maximum temperature for compatible providers. Defaults to 1. */ maxTemperature?: number; /** Preserve temperature and topP with thinking for compatible providers. */ supportsTemperatureAndTopPWithThinking?: boolean; /** Cache option namespace for compatible providers. Defaults to anthropic. */ cacheControlProviderOptionsName?: string; }; export class AnthropicLanguageModel implements LanguageModelV4 { readonly specificationVersion = 'v4'; readonly modelId: AnthropicModelId; protected readonly config: AnthropicLanguageModelConfig; protected readonly generateId: () => string; static [WORKFLOW_SERIALIZE](model: AnthropicLanguageModel) { return serializeModelOptions({ modelId: model.modelId, config: model.config, }); } static [WORKFLOW_DESERIALIZE](options: { modelId: AnthropicModelId; config: AnthropicLanguageModelConfig; }) { return new AnthropicLanguageModel(options.modelId, options.config); } constructor(modelId: AnthropicModelId, config: AnthropicLanguageModelConfig) { this.modelId = modelId; this.config = config; this.generateId = config.generateId ?? generateId; } supportsUrl(url: URL): boolean { return url.protocol === 'https:'; } get provider(): string { return this.config.provider; } get supportedUrls() { return this.config.supportedUrls?.() ?? {}; } static async prepareRequest({ modelId, config, options: { userSuppliedBetas, prompt, maxOutputTokens, temperature, topP, topK, frequencyPenalty, presencePenalty, stopSequences, responseFormat, seed, tools, toolChoice, reasoning, providerOptions, stream, }, }: { modelId: AnthropicModelId; config: AnthropicLanguageModelConfig; options: LanguageModelV4CallOptions & { stream: boolean; userSuppliedBetas: Set; }; }) { const warnings: SharedV4Warning[] = []; if (frequencyPenalty != null) { warnings.push({ type: 'unsupported', feature: 'frequencyPenalty' }); } if (presencePenalty != null) { warnings.push({ type: 'unsupported', feature: 'presencePenalty' }); } if (seed != null) { warnings.push({ type: 'unsupported', feature: 'seed' }); } const maxTemperature = config.maxTemperature ?? 1; const temperatureProvider = config.maxTemperature == null ? 'anthropic' : getProviderOptionsName(config.provider); if (temperature != null && temperature > maxTemperature) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `${temperature} exceeds ${temperatureProvider} maximum of ${maxTemperature.toFixed(1)}. clamped to ${maxTemperature.toFixed(1)}`, }); temperature = maxTemperature; } else if (temperature != null && temperature < 0) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `${temperature} is below ${temperatureProvider} minimum of 0. clamped to 0`, }); temperature = 0; } if (responseFormat?.type === 'json') { if (responseFormat.schema == null) { warnings.push({ type: 'unsupported', feature: 'responseFormat', details: 'JSON response format requires a schema. ' + 'The response format is ignored.', }); } } const providerOptionsName = getProviderOptionsName(config.provider); // Parse provider options from both canonical 'anthropic' key and custom key const canonicalOptions = await parseProviderOptions({ provider: 'anthropic', providerOptions, schema: anthropicLanguageModelOptions, }); const customProviderOptions = providerOptionsName !== 'anthropic' ? await parseProviderOptions({ provider: providerOptionsName, providerOptions, schema: anthropicLanguageModelOptions, }) : null; // Track if custom key was explicitly used const usedCustomProviderKey = customProviderOptions != null; // Merge options const anthropicOptions = Object.assign( {}, canonicalOptions ?? {}, customProviderOptions ?? {}, ); const { maxOutputTokens: maxOutputTokensForModel, supportsStructuredOutput: modelSupportsStructuredOutput, supportsAdaptiveThinking, rejectsSamplingParameters, supportsXhighEffort, rejectsThinkingDisabledAboveHighEffort, rejectsThinkingDisabled, rejectsBudgetThinking, rejectsForcedToolUse, supportsBetweenToolsThinking, isKnownModel, } = getModelCapabilities(modelId); if (!isKnownModel && maxOutputTokens == null) { warnings.push({ type: 'compatibility', feature: 'maxOutputTokens', details: `The model "${modelId}" is unknown. ` + `The max output tokens have been limited to ${maxOutputTokensForModel}. ` + `Set maxOutputTokens explicitly to override this limit.`, }); } if (rejectsSamplingParameters) { if (temperature != null) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `temperature is not supported by ${modelId} and will be ignored`, }); temperature = undefined; } if (topK != null) { warnings.push({ type: 'unsupported', feature: 'topK', details: `topK is not supported by ${modelId} and will be ignored`, }); topK = undefined; } if (topP != null) { warnings.push({ type: 'unsupported', feature: 'topP', details: `topP is not supported by ${modelId} and will be ignored`, }); topP = undefined; } } const isAnthropicModel = isKnownModel || modelId.includes('claude-'); const supportsStructuredOutput = (config.supportsNativeStructuredOutput ?? true) && modelSupportsStructuredOutput; const supportsStrictTools = (config.supportsStrictTools ?? true) && modelSupportsStructuredOutput; const structureOutputMode = anthropicOptions?.structuredOutputMode ?? 'auto'; let useStructuredOutput = structureOutputMode === 'outputFormat' || (structureOutputMode === 'auto' && supportsStructuredOutput); // The JSON response tool relies on forced tool use, which some models // reject (e.g. Opus 5.5, Fable 5.1). Fall back to native structured // outputs when the model supports them. if ( !useStructuredOutput && rejectsForcedToolUse && supportsStructuredOutput && responseFormat?.type === 'json' && responseFormat.schema != null ) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.structuredOutputMode', details: `structuredOutputMode 'jsonTool' is not supported by ${modelId} because it rejects forced tool use. ` + `Using 'outputFormat' instead.`, }); useStructuredOutput = true; } const jsonResponseTool: LanguageModelV4FunctionTool | undefined = responseFormat?.type === 'json' && responseFormat.schema != null && !useStructuredOutput ? { type: 'function', name: 'json', description: 'Respond with a JSON object.', inputSchema: responseFormat.schema, } : undefined; if ( jsonResponseTool != null && anthropicOptions?.disableParallelToolUse === false ) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.disableParallelToolUse', details: '`disableParallelToolUse: false` is ignored when using the JSON response tool. ' + 'Parallel tool use is disabled to ensure a single coherent JSON tool call.', }); } const contextManagement = anthropicOptions?.contextManagement; const compaction = anthropicOptions?.compaction; if (contextManagement != null && compaction != null) { throw new InvalidArgumentError({ argument: 'providerOptions', message: 'Anthropic provider options `compaction` and `contextManagement` cannot be used together.', }); } // Create a shared cache control validator to track breakpoints across tools and messages const cacheControlValidator = new CacheControlValidator( config.cacheControlProviderOptionsName, ); const toolNameMapping = createToolNameMapping({ tools, providerToolNames: { 'anthropic.code_execution_20250522': 'code_execution', 'anthropic.code_execution_20250825': 'code_execution', 'anthropic.code_execution_20260120': 'code_execution', 'anthropic.computer_20241022': 'computer', 'anthropic.computer_20250124': 'computer', 'anthropic.computer_20251124': 'computer', 'anthropic.computer_toolset_20260801': 'computer', 'anthropic.text_editor_20241022': 'str_replace_editor', 'anthropic.text_editor_20250124': 'str_replace_editor', 'anthropic.text_editor_20250429': 'str_replace_based_edit_tool', 'anthropic.text_editor_20250728': 'str_replace_based_edit_tool', 'anthropic.bash_20241022': 'bash', 'anthropic.bash_20250124': 'bash', 'anthropic.memory_20250818': 'memory', 'anthropic.web_search_20250305': 'web_search', 'anthropic.web_search_20260209': 'web_search', 'anthropic.web_search_20260318': 'web_search', 'anthropic.web_fetch_20250910': 'web_fetch', 'anthropic.web_fetch_20260209': 'web_fetch', 'anthropic.web_fetch_20260318': 'web_fetch', 'anthropic.tool_search_regex_20251119': 'tool_search_tool_regex', 'anthropic.tool_search_bm25_20251119': 'tool_search_tool_bm25', 'anthropic.advisor_20260301': 'advisor', }, }); const toolsetNames = getAnthropicToolsetNames(tools); const { prompt: messagesPrompt, betas } = await convertToAnthropicPrompt({ prompt, sendReasoning: anthropicOptions?.sendReasoning ?? true, warnings, cacheControlValidator, toolNameMapping, toolsetNames, convertMediaPart: config.convertMediaPart, }); /* * Map top-level `reasoning` to Anthropic thinking/effort when provider * options don't already specify them. Provider options always take precedence. */ if (isCustomReasoning(reasoning) && anthropicOptions?.effort == null) { const reasoningConfig = resolveAnthropicReasoningConfig({ reasoning, modelId, supportsAdaptiveThinking, supportsXhighEffort, rejectsThinkingDisabled, supportsBetweenToolsThinking, maxOutputTokensForModel, warnings, }); if (reasoningConfig != null) { if (anthropicOptions.thinking == null) { anthropicOptions.thinking = reasoningConfig.thinking; } if ( reasoningConfig.effort != null && anthropicOptions.thinking?.type !== 'disabled' ) { anthropicOptions.effort = reasoningConfig.effort; } } } // Some models always run adaptive thinking and reject `disabled` thinking // with a 400. Drop the unsupported setting and keep the request adaptive // so it still succeeds. if (rejectsThinkingDisabled && anthropicOptions?.thinking != null) { const thinking = anthropicOptions.thinking; if (thinking.type === 'disabled' && supportsBetweenToolsThinking) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.thinking', details: `thinking cannot be disabled for ${modelId}. ` + `Using 'between_tools' thinking, the lowest thinking setting, instead.`, }); anthropicOptions.thinking = { type: 'between_tools' }; } else if (thinking.type === 'disabled') { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.thinking', details: `thinking cannot be disabled for ${modelId}; it always uses adaptive thinking. ` + `The thinking setting has been removed. Lower 'effort' to reduce thinking.`, }); anthropicOptions.thinking = undefined; } } // Models that only support adaptive thinking reject budget-based thinking // with a 400. Use adaptive thinking instead and let `effort` control how // much the model thinks. if ( rejectsBudgetThinking && anthropicOptions?.thinking?.type === 'enabled' ) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.thinking', details: rejectsThinkingDisabled ? `budget-based thinking is not supported by ${modelId}; it always uses adaptive thinking. ` + `Using adaptive thinking instead. Use 'effort' to control how much the model thinks.` : `budget-based thinking is not supported by ${modelId}. ` + `Using adaptive thinking instead. Use 'effort' to control how much the model thinks.`, }); anthropicOptions.thinking = { type: 'adaptive' }; } // Newer models only allow disabling thinking at effort levels up to and // including `high`; at `xhigh` and `max` the API returns a 400. Lower // the effort to `high` to preserve the explicit request to run without // thinking. if ( rejectsThinkingDisabledAboveHighEffort && anthropicOptions?.thinking?.type === 'disabled' && (anthropicOptions.effort === 'xhigh' || anthropicOptions.effort === 'max') ) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.effort', details: `effort '${anthropicOptions.effort}' is not supported by ${modelId} when thinking is disabled. ` + `The effort has been lowered to 'high'.`, }); anthropicOptions.effort = 'high'; } // `between_tools` thinking is only accepted at `low`, `medium`, and // `high` effort. Lower the effort to `high` to keep the minimal thinking // setting instead of sending a request that the API would reject. if ( anthropicOptions?.thinking?.type === 'between_tools' && (anthropicOptions.effort === 'xhigh' || anthropicOptions.effort === 'max') ) { warnings.push({ type: 'unsupported', feature: 'providerOptions.anthropic.effort', details: `effort '${anthropicOptions.effort}' is not supported with 'between_tools' thinking. ` + `The effort has been lowered to 'high'.`, }); anthropicOptions.effort = 'high'; } const thinkingType = anthropicOptions?.thinking?.type; const isThinking = thinkingType === 'enabled' || thinkingType === 'adaptive' || thinkingType === 'between_tools'; const thinkingBlockBinding = anthropicOptions?.thinking != null && 'blockBinding' in anthropicOptions.thinking ? anthropicOptions.thinking.blockBinding : undefined; // `disabled` must still be forwarded to the API: some models (e.g. Sonnet 5) // default thinking on, so omitting it would leave thinking enabled and // consume the max_tokens budget. Binding-only recovery requests must also // send a thinking object without a type. const sendThinking = isThinking || thinkingType === 'disabled' || thinkingBlockBinding != null; let thinkingBudget = thinkingType === 'enabled' ? anthropicOptions?.thinking?.budgetTokens : undefined; const thinkingDisplay = thinkingType === 'adaptive' ? anthropicOptions?.thinking?.display : undefined; const maxTokens = maxOutputTokens ?? maxOutputTokensForModel; const baseArgs = { // model id: model: modelId, // standardized settings: max_tokens: maxTokens, temperature, top_k: topK, top_p: topP, stop_sequences: stopSequences, // provider specific settings: ...(sendThinking && { thinking: { ...(thinkingType != null && { type: thinkingType }), ...(thinkingBudget != null && { budget_tokens: thinkingBudget }), ...(thinkingDisplay != null && { display: thinkingDisplay }), ...(thinkingBlockBinding != null && { block_binding: { prefix_mismatch_behavior: thinkingBlockBinding.prefixMismatchBehavior, }, }), }, }), ...((anthropicOptions?.effort || anthropicOptions?.taskBudget || (useStructuredOutput && responseFormat?.type === 'json' && responseFormat.schema != null)) && { output_config: { ...(anthropicOptions?.effort && { effort: anthropicOptions.effort, }), ...(anthropicOptions?.taskBudget && { task_budget: { type: anthropicOptions.taskBudget.type, total: anthropicOptions.taskBudget.total, ...(anthropicOptions.taskBudget.remaining != null && { remaining: anthropicOptions.taskBudget.remaining, }), }, }), ...(useStructuredOutput && responseFormat?.type === 'json' && responseFormat.schema != null && { format: { type: 'json_schema', schema: sanitizeJsonSchema(responseFormat.schema), }, }), }, }), ...(anthropicOptions?.speed && { speed: anthropicOptions.speed, }), ...(anthropicOptions?.serviceTier && { service_tier: anthropicOptions.serviceTier, }), ...(anthropicOptions?.inferenceGeo && { inference_geo: anthropicOptions.inferenceGeo, }), ...(anthropicOptions?.fallbacks != null && (anthropicOptions.fallbacks === 'default' || anthropicOptions.fallbacks.length > 0) && { fallbacks: anthropicOptions.fallbacks, }), ...(anthropicOptions?.cacheControl && { cache_control: anthropicOptions.cacheControl, }), ...(anthropicOptions?.metadata?.userId != null && { metadata: { user_id: anthropicOptions.metadata.userId }, }), // mcp servers: ...(anthropicOptions?.mcpServers && anthropicOptions.mcpServers.length > 0 && { mcp_servers: anthropicOptions.mcpServers.map(server => ({ type: server.type, name: server.name, url: server.url, authorization_token: server.authorizationToken, tool_configuration: server.toolConfiguration ? { allowed_tools: server.toolConfiguration.allowedTools, enabled: server.toolConfiguration.enabled, } : undefined, })), }), // container: For programmatic tool calling (just an ID string) or agent skills (object with id and skills) ...(anthropicOptions?.container && { container: anthropicOptions.container.skills && anthropicOptions.container.skills.length > 0 ? // Object format when skills are provided (agent skills feature) ({ id: anthropicOptions.container.id, skills: anthropicOptions.container.skills.map(skill => ({ type: skill.type, skill_id: skill.type === 'custom' ? resolveProviderReference({ reference: skill.providerReference, provider: 'anthropic', }) : skill.skillId, version: skill.version, })), } satisfies AnthropicContainer) : // String format for container ID only (programmatic tool calling) anthropicOptions.container.id, }), // prompt: system: messagesPrompt.system, messages: messagesPrompt.messages, ...(anthropicOptions?.safeguards && anthropicOptions.safeguards.length > 0 && { safeguards: anthropicOptions.safeguards.map(safeguard => ({ type: safeguard.type, ...(safeguard.classifierContext !== undefined && { classifier_context: safeguard.classifierContext, }), })), }), ...(compaction && { compaction: { type: compaction.type, ...(compaction.instructions !== undefined && { instructions: compaction.instructions, }), }, }), ...(contextManagement && { context_management: { edits: contextManagement.edits .map(edit => { const strategy = edit.type; switch (strategy) { case 'clear_tool_uses_20250919': return { type: edit.type, ...(edit.trigger !== undefined && { trigger: edit.trigger, }), ...(edit.keep !== undefined && { keep: edit.keep }), ...(edit.clearAtLeast !== undefined && { clear_at_least: edit.clearAtLeast, }), ...(edit.clearToolInputs !== undefined && { clear_tool_inputs: edit.clearToolInputs, }), ...(edit.excludeTools !== undefined && { exclude_tools: edit.excludeTools, }), }; case 'clear_thinking_20251015': return { type: edit.type, ...(edit.keep !== undefined && { keep: edit.keep }), }; case 'compact_20260112': return { type: edit.type, ...(edit.trigger !== undefined && { trigger: edit.trigger, }), ...(edit.pauseAfterCompaction !== undefined && { pause_after_compaction: edit.pauseAfterCompaction, }), ...(edit.instructions !== undefined && { instructions: edit.instructions, }), }; default: warnings.push({ type: 'other', message: `Unknown context management strategy: ${strategy}`, }); return undefined; } }) .filter(edit => edit !== undefined), }, }), }; if (isThinking) { if (thinkingType === 'enabled' && thinkingBudget == null) { warnings.push({ type: 'compatibility', feature: 'extended thinking', details: 'thinking budget is required when thinking is enabled. using default budget of 1024 tokens.', }); baseArgs.thinking = { type: 'enabled', budget_tokens: 1024, }; thinkingBudget = 1024; } if ( baseArgs.temperature != null && !config.supportsTemperatureAndTopPWithThinking ) { baseArgs.temperature = undefined; warnings.push({ type: 'unsupported', feature: 'temperature', details: 'temperature is not supported when thinking is enabled', }); } if (topK != null) { baseArgs.top_k = undefined; warnings.push({ type: 'unsupported', feature: 'topK', details: 'topK is not supported when thinking is enabled', }); } if (topP != null && !config.supportsTemperatureAndTopPWithThinking) { baseArgs.top_p = undefined; warnings.push({ type: 'unsupported', feature: 'topP', details: 'topP is not supported when thinking is enabled', }); } // adjust max tokens to account for thinking: baseArgs.max_tokens = maxTokens + (thinkingBudget ?? 0); } else { // Only check temperature/topP mutual exclusivity for known Anthropic models // when thinking is not enabled. Non-Anthropic models using the Anthropic-compatible // API (e.g. Minimax) may require both parameters to be set. if (isAnthropicModel && topP != null && temperature != null) { warnings.push({ type: 'unsupported', feature: 'topP', details: `topP is not supported when temperature is set. topP is ignored.`, }); baseArgs.top_p = undefined; } } // limit to max output tokens for known models to enable model switching without breaking it: if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) { // only warn if max output tokens is provided as input: if (maxOutputTokens != null) { warnings.push({ type: 'unsupported', feature: 'maxOutputTokens', details: `${baseArgs.max_tokens} (maxOutputTokens + thinkingBudget) is greater than ${modelId} ${maxOutputTokensForModel} max output tokens. ` + `The max output tokens have been limited to ${maxOutputTokensForModel}.`, }); } baseArgs.max_tokens = maxOutputTokensForModel; } if ( anthropicOptions?.mcpServers && anthropicOptions.mcpServers.length > 0 ) { betas.add('mcp-client-2025-04-04'); } if ( anthropicOptions?.safeguards && anthropicOptions.safeguards.length > 0 ) { betas.add('dangerous-tool-use-2026-09-03'); } if (compaction) { betas.add('compact-2026-09-04'); } if (contextManagement) { betas.add('context-management-2025-06-27'); // Add compaction beta if compact edit is present if (contextManagement.edits.some(e => e.type === 'compact_20260112')) { betas.add('compact-2026-01-12'); } } if ( anthropicOptions?.container && anthropicOptions.container.skills && anthropicOptions.container.skills.length > 0 ) { betas.add('code-execution-2025-08-25'); betas.add('skills-2025-10-02'); betas.add('files-api-2025-04-14'); if ( !tools?.some( tool => tool.type === 'provider' && (tool.id === 'anthropic.code_execution_20250825' || tool.id === 'anthropic.code_execution_20260120'), ) ) { warnings.push({ type: 'other', message: 'code execution tool is required when using skills', }); } } if (anthropicOptions?.taskBudget) { betas.add('task-budgets-2026-03-13'); } if (anthropicOptions?.speed === 'fast') { betas.add('fast-mode-2026-02-01'); } if (thinkingDisplay === 'updates') { betas.add('thinking-display-updates-2026-08-18'); } if (thinkingBlockBinding != null) { betas.add('thinking-binding-controls-2026-08-01'); } if (anthropicOptions?.fallbacks === 'default') { betas.add('server-side-fallback-2026-07-01'); } else if ( anthropicOptions?.fallbacks && anthropicOptions.fallbacks.length > 0 ) { betas.add('server-side-fallback-2026-06-01'); } const defaultEagerInputStreaming = stream && (anthropicOptions?.toolStreaming ?? true); const { tools: anthropicTools, toolChoice: anthropicToolChoice, toolWarnings, betas: toolsBetas, } = await prepareTools( jsonResponseTool != null ? { tools: [...(tools ?? []), jsonResponseTool], toolChoice: { type: 'required' }, disableParallelToolUse: true, cacheControlValidator, supportsStructuredOutput: false, supportsStrictTools, defaultEagerInputStreaming, rejectsForcedToolUse: rejectsForcedToolUse || thinkingType === 'enabled', } : { tools: tools ?? [], toolChoice, disableParallelToolUse: anthropicOptions?.disableParallelToolUse, cacheControlValidator, supportsStructuredOutput, supportsStrictTools, defaultEagerInputStreaming, rejectsForcedToolUse: rejectsForcedToolUse || thinkingType === 'enabled', }, ); // Extract cache control warnings once at the end const cacheWarnings = cacheControlValidator.getWarnings(); return { args: { ...baseArgs, tools: anthropicTools, tool_choice: anthropicToolChoice, stream: stream === true ? true : undefined, // do not send when not streaming }, warnings: [...warnings, ...toolWarnings, ...cacheWarnings], betas: new Set([ ...betas, ...toolsBetas, ...userSuppliedBetas, ...(anthropicOptions?.anthropicBeta ?? []), ]), usesJsonResponseTool: jsonResponseTool != null, toolNameMapping, providerOptionsName, usedCustomProviderKey, }; } private getArgs( options: LanguageModelV4CallOptions & { stream: boolean; userSuppliedBetas: Set; }, ) { return AnthropicLanguageModel.prepareRequest({ modelId: this.modelId, config: this.config, options, }); } private async getHeaders({ betas, configHeaders, headers, providerOptions, }: { betas: Set; configHeaders: Record | undefined; headers: Record | undefined; providerOptions: LanguageModelV4CallOptions['providerOptions']; }) { const combinedHeaders = combineHeaders( configHeaders, headers, betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {}, ); return this.config.transformRequestHeaders ? this.config.transformRequestHeaders(combinedHeaders, providerOptions) : combinedHeaders; } private getBetasFromHeaders( configHeaders: Record | undefined, requestHeaders: Record | undefined, ) { const configBetaHeader = configHeaders?.['anthropic-beta'] ?? ''; const requestBetaHeader = requestHeaders?.['anthropic-beta'] ?? ''; return new Set( [ ...configBetaHeader.toLowerCase().split(','), ...requestBetaHeader.toLowerCase().split(','), ] .map(beta => beta.trim()) .filter(beta => beta !== ''), ); } private buildRequestUrl(isStreaming: boolean): string { return ( this.config.buildRequestUrl?.(this.config.baseURL, isStreaming) ?? `${this.config.baseURL}/messages` ); } protected transformRequestBody( args: Record, betas: Set, ): Record { return this.config.transformRequestBody?.(args, betas) ?? args; } private extractCitationDocuments(prompt: LanguageModelV4Prompt): Array<{ title: string; filename?: string; mediaType: string; }> { const isCitationPart = (part: { type: string; mediaType?: string; providerOptions?: { anthropic?: { citations?: { enabled?: boolean } } }; }) => { if (part.type !== 'file') { return false; } if ( part.mediaType !== 'application/pdf' && part.mediaType !== 'text/plain' ) { return false; } const anthropic = part.providerOptions?.anthropic; const citationsConfig = anthropic?.citations as | { enabled?: boolean } | undefined; return citationsConfig?.enabled ?? false; }; return prompt .filter(message => message.role === 'user') .flatMap(message => message.content) .filter(isCitationPart) .map(part => { // TypeScript knows this is a file part due to our filter const filePart = part as Extract; return { title: filePart.filename ?? 'Untitled Document', filename: filePart.filename, mediaType: filePart.mediaType, }; }); } async doGenerate( options: LanguageModelV4CallOptions, ): Promise { const configHeaders = this.config.headers ? await resolve(this.config.headers) : undefined; const { args, warnings, betas, usesJsonResponseTool, toolNameMapping, providerOptionsName, usedCustomProviderKey, } = await this.getArgs({ ...options, stream: false, userSuppliedBetas: this.getBetasFromHeaders( configHeaders, options.headers, ), }); // Extract citation documents for response processing const citationDocuments = [ ...this.extractCitationDocuments(options.prompt), ]; const markCodeExecutionDynamic = hasDynamicFilteringWebToolWithoutCodeExecution(args.tools); const { responseHeaders, value: response, rawValue: rawResponse, } = await postJsonToApi({ url: this.buildRequestUrl(false), headers: await this.getHeaders({ betas, configHeaders, headers: options.headers, providerOptions: options.providerOptions, }), body: this.transformRequestBody(args, betas), failedResponseHandler: anthropicFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler( anthropicResponseSchema, ), abortSignal: options.abortSignal, fetch: this.config.fetch, }); const content: Array = []; const mcpToolCalls: Record = {}; const serverToolCalls: Record = {}; // tool_use_id -> provider tool name let isJsonResponseFromTool = false; const hasWebSearchResults = response.content.some( part => part.type === 'web_search_tool_result' && Array.isArray(part.content) && part.content.length > 0, ); // map response content to content array for (const part of response.content) { switch (part.type) { case 'text': { if (!usesJsonResponseTool) { const citations = part.citations ?? []; content.push({ type: 'text', text: part.text, ...(citations.length > 0 && { citations: citations.map(citation => mapAnthropicCitation(citation, citationDocuments), ), }), ...(citations.length > 0 && { providerMetadata: { anthropic: { citations, }, } satisfies AnthropicTextProviderMetadata, }), }); // Process citations if present if (part.citations) { for (const citation of part.citations) { if ( citation.type === 'web_search_result_location' && hasWebSearchResults ) { continue; } const source = createCitationSource( citation, citationDocuments, this.generateId, ); if (source) { content.push(source); } } } } break; } case 'thinking': { content.push({ type: 'reasoning', text: part.thinking, providerMetadata: { anthropic: { signature: part.signature, } satisfies AnthropicReasoningMetadata, }, }); break; } case 'redacted_thinking': { content.push({ type: 'reasoning', text: '', providerMetadata: { anthropic: { redactedData: part.data, } satisfies AnthropicReasoningMetadata, }, }); break; } case 'container_upload': { content.push({ type: 'custom', kind: 'anthropic.container_upload', providerMetadata: { anthropic: { fileId: part.file_id } }, }); break; } case 'compaction': { if (!part.content) { break; } content.push({ type: 'text', text: part.content, providerMetadata: { anthropic: { type: 'compaction', ...(part.signature != null && { signature: part.signature, }), }, }, }); break; } case 'tool_use': { const isJsonResponseTool = usesJsonResponseTool && part.name === 'json'; if (isJsonResponseTool) { isJsonResponseFromTool = true; // when a json response tool is used, the tool call becomes the text: content.push({ type: 'text', text: JSON.stringify(part.input), }); } else if (part.toolset_name != null) { // toolset member calls (e.g. the computer toolset) are mapped to // the toolset tool with the member name as the `action`: content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName(part.toolset_name), input: JSON.stringify( toToolsetMemberInput({ memberName: part.name, input: part.input, }), ), providerMetadata: { anthropic: { toolsetName: part.toolset_name, ...getAnthropicCallerMetadata(part.caller).providerMetadata ?.anthropic, }, }, }); } else { content.push({ type: 'tool-call', toolCallId: part.id, toolName: part.name, input: JSON.stringify(part.input), ...getAnthropicCallerMetadata(part.caller), }); } break; } case 'server_tool_use': { // code execution 20250825 needs mapping: if ( part.name === 'text_editor_code_execution' || part.name === 'bash_code_execution' ) { // map tool names for the code execution 20250825 tool: // both map to 'code_execution'. const providerToolName = 'code_execution'; content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName('code_execution'), input: JSON.stringify({ type: part.name, ...part.input }), providerExecuted: true, // Specific 'web_fetch' or 'web_search' tools may need code execution, which the Anthropic API // implicitly allows. In this scenario, we need to allow 'code_execution' tool calls even if the // tool was not explicitly provided. We therefore bypass the general validation by marking the // tool as dynamic. ...(markCodeExecutionDynamic && providerToolName === 'code_execution' ? { dynamic: true } : {}), ...getAnthropicCallerMetadata(part.caller), }); } else if ( part.name === 'web_search' || part.name === 'code_execution' || part.name === 'web_fetch' ) { // For code_execution, inject 'programmatic-tool-call' type when input has { code } format const inputToSerialize = part.name === 'code_execution' && part.input != null && typeof part.input === 'object' && 'code' in part.input && !('type' in part.input) ? { type: 'programmatic-tool-call', ...part.input } : part.input; content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName(part.name), input: JSON.stringify(inputToSerialize), providerExecuted: true, // Specific 'web_fetch' or 'web_search' tools may need code execution, which the Anthropic API // implicitly allows. In this scenario, we need to allow 'code_execution' tool calls even if the // tool was not explicitly provided. We therefore bypass the general validation by marking the // tool as dynamic. ...(markCodeExecutionDynamic && part.name === 'code_execution' ? { dynamic: true } : {}), ...getAnthropicCallerMetadata(part.caller), }); } else if ( part.name === 'tool_search_tool_regex' || part.name === 'tool_search_tool_bm25' ) { serverToolCalls[part.id] = part.name; content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName(part.name), input: JSON.stringify(part.input), providerExecuted: true, ...getAnthropicCallerMetadata(part.caller), }); } else if (part.name === 'advisor') { content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName('advisor'), input: JSON.stringify(part.input), providerExecuted: true, ...getAnthropicCallerMetadata(part.caller), }); } break; } case 'mcp_tool_use': { mcpToolCalls[part.id] = { type: 'tool-call', toolCallId: part.id, toolName: part.name, input: JSON.stringify(part.input), providerExecuted: true, dynamic: true, providerMetadata: { anthropic: { type: 'mcp-tool-use', serverName: part.server_name, }, }, }; content.push(mcpToolCalls[part.id]); break; } case 'mcp_tool_result': { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: mcpToolCalls[part.tool_use_id].toolName, isError: part.is_error, result: part.content, dynamic: true, providerMetadata: mcpToolCalls[part.tool_use_id].providerMetadata, }); break; } case 'web_fetch_tool_result': { if (part.content.type === 'web_fetch_result') { citationDocuments.push({ title: part.content.content.title ?? part.content.url, mediaType: part.content.content.source.media_type, }); content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_fetch'), result: { type: 'web_fetch_result', url: part.content.url, retrievedAt: part.content.retrieved_at, content: { type: part.content.content.type, title: part.content.content.title, citations: part.content.content.citations, source: { type: part.content.content.source.type, mediaType: part.content.content.source.media_type, data: part.content.content.source.data, }, }, }, ...getAnthropicCallerMetadata(part.caller), }); } else if (part.content.type === 'web_fetch_tool_result_error') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_fetch'), isError: true, result: { type: 'web_fetch_tool_result_error', errorCode: part.content.error_code, }, ...getAnthropicCallerMetadata(part.caller), }); } break; } case 'web_search_tool_result': { if (Array.isArray(part.content)) { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_search'), result: part.content.map(result => ({ url: result.url, ...(result.title != null ? { title: result.title } : {}), pageAge: result.page_age ?? null, encryptedContent: result.encrypted_content, type: result.type, })), ...getAnthropicCallerMetadata(part.caller), }); for (const result of part.content) { content.push({ type: 'source', sourceType: 'url', id: this.generateId(), url: result.url, ...(result.title != null ? { title: result.title } : {}), providerMetadata: { anthropic: { pageAge: result.page_age ?? null, }, }, }); } } else { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_search'), isError: true, result: { type: 'web_search_tool_result_error', errorCode: part.content.error_code, }, ...getAnthropicCallerMetadata(part.caller), }); } break; } // code execution 20250522: case 'code_execution_tool_result': { if (part.content.type === 'code_execution_result') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: { type: part.content.type, stdout: part.content.stdout, stderr: part.content.stderr, return_code: part.content.return_code, content: part.content.content ?? [], }, }); } else if (part.content.type === 'encrypted_code_execution_result') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: { type: part.content.type, encrypted_stdout: part.content.encrypted_stdout, stderr: part.content.stderr, return_code: part.content.return_code, content: part.content.content ?? [], }, }); } else if (part.content.type === 'code_execution_tool_result_error') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), isError: true, result: { type: 'code_execution_tool_result_error', errorCode: part.content.error_code, }, }); } break; } // code execution 20250825: case 'bash_code_execution_tool_result': case 'text_editor_code_execution_tool_result': { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: part.content, }); break; } // tool search tool results: case 'tool_search_tool_result': { let providerToolName = serverToolCalls[part.tool_use_id]; if (providerToolName == null) { const bm25CustomName = toolNameMapping.toCustomToolName( 'tool_search_tool_bm25', ); const regexCustomName = toolNameMapping.toCustomToolName( 'tool_search_tool_regex', ); if (bm25CustomName !== 'tool_search_tool_bm25') { providerToolName = 'tool_search_tool_bm25'; } else if (regexCustomName !== 'tool_search_tool_regex') { providerToolName = 'tool_search_tool_regex'; } else { providerToolName = 'tool_search_tool_regex'; } } if (part.content.type === 'tool_search_tool_search_result') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName(providerToolName), result: part.content.tool_references.map(ref => ({ type: ref.type, toolName: ref.tool_name, })), }); } else { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName(providerToolName), isError: true, result: { type: 'tool_search_tool_result_error', errorCode: part.content.error_code, }, }); } break; } // advisor results for advisor_20260301: case 'advisor_tool_result': { const advisorToolName = toolNameMapping.toCustomToolName('advisor'); if (part.content.type === 'advisor_result') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, result: { type: 'advisor_result', text: part.content.text, ...(part.content.stop_reason != null && { stopReason: part.content.stop_reason, }), }, }); } else if (part.content.type === 'advisor_redacted_result') { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, result: { type: 'advisor_redacted_result', encryptedContent: part.content.encrypted_content, ...(part.content.stop_reason != null && { stopReason: part.content.stop_reason, }), }, }); } else { content.push({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, isError: true, result: { type: 'advisor_tool_result_error', errorCode: part.content.error_code, }, }); } break; } case 'fallback': { content.push({ type: 'custom', kind: 'anthropic.fallback', providerMetadata: { anthropic: { type: 'fallback', from: part.from, to: part.to, }, }, }); break; } } } return { content, finishReason: { unified: mapAnthropicStopReason({ finishReason: response.stop_reason, isJsonResponseFromTool, }), raw: response.stop_reason ?? undefined, }, usage: convertAnthropicUsage({ usage: response.usage }), request: { body: args }, response: { id: response.id ?? undefined, modelId: response.model ?? undefined, headers: responseHeaders, body: rawResponse, }, warnings, providerMetadata: (() => { const stopDetails = mapAnthropicStopDetails(response.stop_details); const anthropicMetadata = { usage: response.usage as JSONObject, stopSequence: response.stop_sequence ?? null, ...(stopDetails != null ? { stopDetails } : {}), ...(response.input_transformations != null ? { inputTransformations: response.input_transformations } : {}), ...(response.safeguard_results != null ? { safeguardResults: response.safeguard_results } : {}), iterations: response.usage.iterations ? response.usage.iterations.map( iter => ({ type: iter.type, ...(iter.model != null ? { model: iter.model } : {}), inputTokens: iter.input_tokens, outputTokens: iter.output_tokens, ...(iter.cache_creation_input_tokens ? { cacheCreationInputTokens: iter.cache_creation_input_tokens, } : {}), ...(iter.cache_read_input_tokens ? { cacheReadInputTokens: iter.cache_read_input_tokens, } : {}), }) satisfies AnthropicUsageIteration, ) : null, container: response.container ? { expiresAt: response.container.expires_at, id: response.container.id, skills: response.container.skills?.map(skill => ({ type: skill.type, skillId: skill.skill_id, version: skill.version, })) ?? null, } : null, contextManagement: mapAnthropicResponseContextManagement( response.context_management, ) ?? null, } satisfies AnthropicMessageMetadata; const providerMetadata: SharedV4ProviderMetadata = { anthropic: anthropicMetadata, }; if (usedCustomProviderKey && providerOptionsName !== 'anthropic') { providerMetadata[providerOptionsName] = anthropicMetadata; } return providerMetadata; })(), }; } async doStream( options: LanguageModelV4CallOptions, ): Promise { 'use step'; const configHeaders = this.config.headers ? await resolve(this.config.headers) : undefined; const { args: body, warnings, betas, usesJsonResponseTool, toolNameMapping, providerOptionsName, usedCustomProviderKey, } = await this.getArgs({ ...options, stream: true, userSuppliedBetas: this.getBetasFromHeaders( configHeaders, options.headers, ), }); // Extract citation documents for response processing const citationDocuments = [ ...this.extractCitationDocuments(options.prompt), ]; const markCodeExecutionDynamic = hasDynamicFilteringWebToolWithoutCodeExecution(body.tools); const url = this.buildRequestUrl(true); const { responseHeaders, value: response } = await postJsonToApi({ url, headers: await this.getHeaders({ betas, configHeaders, headers: options.headers, providerOptions: options.providerOptions, }), body: this.transformRequestBody(body, betas), failedResponseHandler: anthropicFailedResponseHandler, successfulResponseHandler: createEventSourceResponseHandler(anthropicChunkSchema), abortSignal: options.abortSignal, fetch: this.config.fetch, }); let finishReason: LanguageModelV4FinishReason = { unified: 'other', raw: undefined, }; const usage: AnthropicUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, iterations: null, }; const contentBlocks: Record< number, | { type: 'tool-call'; toolCallId: string; toolName: string; input: string; providerExecuted?: boolean; firstDelta: boolean; providerToolName?: string; providerToolInputType?: string; /** * Set for toolset member calls (e.g. the computer toolset). The raw * member input is accumulated and emitted as a single delta with the * member name injected as `action` when the block completes. */ toolset?: { name: string; memberName: string }; caller?: { type: | 'code_execution_20250825' | 'code_execution_20260120' | 'direct'; toolId?: string; }; } | { type: 'text'; citations: Citation[] } | { type: 'reasoning' } > = {}; const mcpToolCalls: Record = {}; const serverToolCalls: Record = {}; // tool_use_id -> provider tool name let contextManagement: | AnthropicMessageMetadata['contextManagement'] | null = null; let rawUsage: JSONObject | undefined = undefined; let stopSequence: string | null = null; let stopDetails: AnthropicMessageMetadata['stopDetails'] = undefined; let inputTransformations: AnthropicMessageMetadata['inputTransformations']; let safeguardResults: AnthropicMessageMetadata['safeguardResults']; let container: AnthropicMessageMetadata['container'] | null = null; let isJsonResponseFromTool = false; let hasWebSearchResults = false; const fallbackWebCitations: Citation[] = []; let isMessageOpen = false; let activeMessageId: string | null | undefined; let hasInvalidMessageSequence = false; let blockType: | 'text' | 'thinking' | 'tool_use' | 'redacted_thinking' | 'server_tool_use' | 'web_fetch_tool_result' | 'web_search_tool_result' | 'code_execution_tool_result' | 'text_editor_code_execution_tool_result' | 'bash_code_execution_tool_result' | 'tool_search_tool_result' | 'advisor_tool_result' | 'mcp_tool_use' | 'mcp_tool_result' | 'compaction' | undefined = undefined; const generateId = this.generateId; const transformedStream = response.pipeThrough( new TransformStream< ParseResult>, LanguageModelV4StreamPart >({ start(controller) { controller.enqueue({ type: 'stream-start', warnings }); }, transform(chunk, controller) { if (hasInvalidMessageSequence) { return; } if (options.includeRawChunks) { controller.enqueue({ type: 'raw', rawValue: chunk.rawValue }); } if (!chunk.success) { controller.enqueue({ type: 'error', error: chunk.error }); return; } const value = chunk.value; switch (value.type) { case 'ping': { return; // ignored } case 'content_block_start': { const part = value.content_block; const contentBlockType = part.type; if (contentBlockType === 'fallback') { controller.enqueue({ type: 'custom', kind: 'anthropic.fallback', providerMetadata: { anthropic: { type: 'fallback', from: part.from, to: part.to, }, }, }); return; } blockType = contentBlockType; switch (contentBlockType) { case 'text': { // when a json response tool is used, the tool call is returned as text, // so we ignore the text content: if (usesJsonResponseTool) { return; } contentBlocks[value.index] = { type: 'text', citations: [], }; controller.enqueue({ type: 'text-start', id: String(value.index), }); return; } case 'thinking': { contentBlocks[value.index] = { type: 'reasoning' }; controller.enqueue({ type: 'reasoning-start', id: String(value.index), }); return; } case 'redacted_thinking': { contentBlocks[value.index] = { type: 'reasoning' }; controller.enqueue({ type: 'reasoning-start', id: String(value.index), providerMetadata: { anthropic: { redactedData: part.data, } satisfies AnthropicReasoningMetadata, }, }); return; } case 'compaction': { contentBlocks[value.index] = { type: 'text', citations: [], }; controller.enqueue({ type: 'text-start', id: String(value.index), providerMetadata: { anthropic: { type: 'compaction', ...(part.signature != null && { signature: part.signature, }), }, }, }); // On-demand compaction blocks arrive complete in // content_block_start, without compaction_delta events. if ( part.signature != null && part.content != null && part.content !== '' ) { controller.enqueue({ type: 'text-delta', id: String(value.index), delta: part.content, }); } return; } case 'tool_use': { const isJsonResponseTool = usesJsonResponseTool && part.name === 'json'; if (isJsonResponseTool) { isJsonResponseFromTool = true; contentBlocks[value.index] = { type: 'text', citations: [], }; controller.enqueue({ type: 'text-start', id: String(value.index), }); } else if (part.toolset_name != null) { const callerInfo = getAnthropicCallerInfo(part.caller); const customToolName = toolNameMapping.toCustomToolName( part.toolset_name, ); const hasNonEmptyInput = part.input && Object.keys(part.input).length > 0; // toolset member input is accumulated and emitted when the // block completes (see content_block_stop): contentBlocks[value.index] = { type: 'tool-call', toolCallId: part.id, toolName: customToolName, input: hasNonEmptyInput ? JSON.stringify(part.input) : '', firstDelta: true, toolset: { name: part.toolset_name, memberName: part.name, }, ...(callerInfo && { caller: callerInfo }), }; controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: customToolName, }); } else { const callerInfo = getAnthropicCallerInfo(part.caller); // Programmatic tool calling: for deferred tool calls from code_execution, // input may be present directly in content_block_start. // Only use if non-empty (empty {} means input comes via deltas) const hasNonEmptyInput = part.input && Object.keys(part.input).length > 0; const initialInput = hasNonEmptyInput ? JSON.stringify(part.input) : ''; contentBlocks[value.index] = { type: 'tool-call', toolCallId: part.id, toolName: part.name, input: initialInput, firstDelta: initialInput.length === 0, ...(callerInfo && { caller: callerInfo }), }; controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: part.name, }); } return; } case 'server_tool_use': { const callerInfo = getAnthropicCallerInfo(part.caller); if ( [ 'web_fetch', 'web_search', // code execution 20250825: 'code_execution', // code execution 20250825 text editor: 'text_editor_code_execution', // code execution 20250825 bash: 'bash_code_execution', ].includes(part.name) ) { // map tool names for the code execution 20250825 tool: const providerToolName = part.name === 'text_editor_code_execution' || part.name === 'bash_code_execution' ? 'code_execution' : part.name; const providerToolInputType = part.name === 'text_editor_code_execution' || part.name === 'bash_code_execution' ? part.name : part.name === 'code_execution' ? 'programmatic-tool-call' : undefined; const customToolName = toolNameMapping.toCustomToolName(providerToolName); // Dynamic web tools provide input data here. // Other tools like 'code_execution_20260120' provide input data via deltas. // So we only use this if it's non-empty to avoid conflicts. const finalInput = part.input != null && typeof part.input === 'object' && Object.keys(part.input).length > 0 ? JSON.stringify(part.input) : ''; contentBlocks[value.index] = { type: 'tool-call', toolCallId: part.id, toolName: customToolName, input: finalInput, providerExecuted: true, // Specific 'web_fetch' or 'web_search' tools may need code execution, which the Anthropic API // implicitly allows. In this scenario, we need to allow 'code_execution' tool calls even if the // tool was not explicitly provided. We therefore bypass the general validation by marking the // tool as dynamic. ...(markCodeExecutionDynamic && providerToolName === 'code_execution' ? { dynamic: true } : {}), firstDelta: finalInput.length === 0, providerToolName, providerToolInputType, ...(callerInfo && { caller: callerInfo }), }; controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: customToolName, providerExecuted: true, // Specific 'web_fetch' or 'web_search' tools may need code execution, which the Anthropic API // implicitly allows. In this scenario, we need to allow 'code_execution' tool calls even if the // tool was not explicitly provided. We therefore bypass the general validation by marking the // tool as dynamic. ...(markCodeExecutionDynamic && providerToolName === 'code_execution' ? { dynamic: true } : {}), }); } else if ( part.name === 'tool_search_tool_regex' || part.name === 'tool_search_tool_bm25' ) { serverToolCalls[part.id] = part.name; const customToolName = toolNameMapping.toCustomToolName( part.name, ); contentBlocks[value.index] = { type: 'tool-call', toolCallId: part.id, toolName: customToolName, input: '', providerExecuted: true, firstDelta: true, providerToolName: part.name, ...(callerInfo && { caller: callerInfo }), }; controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: customToolName, providerExecuted: true, }); } else if (part.name === 'advisor') { const customToolName = toolNameMapping.toCustomToolName('advisor'); contentBlocks[value.index] = { type: 'tool-call', toolCallId: part.id, toolName: customToolName, input: '{}', providerExecuted: true, firstDelta: true, providerToolName: part.name, ...(callerInfo && { caller: callerInfo }), }; controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: customToolName, providerExecuted: true, }); } return; } case 'web_fetch_tool_result': { if (part.content.type === 'web_fetch_result') { citationDocuments.push({ title: part.content.content.title ?? part.content.url, mediaType: part.content.content.source.media_type, }); controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_fetch'), result: { type: 'web_fetch_result', url: part.content.url, retrievedAt: part.content.retrieved_at, content: { type: part.content.content.type, title: part.content.content.title, citations: part.content.content.citations, source: { type: part.content.content.source.type, mediaType: part.content.content.source.media_type, data: part.content.content.source.data, }, }, }, ...getAnthropicCallerMetadata(part.caller), }); } else if ( part.content.type === 'web_fetch_tool_result_error' ) { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_fetch'), isError: true, result: { type: 'web_fetch_tool_result_error', errorCode: part.content.error_code, }, ...getAnthropicCallerMetadata(part.caller), }); } return; } case 'web_search_tool_result': { if (Array.isArray(part.content)) { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_search'), result: part.content.map(result => ({ url: result.url, ...(result.title != null ? { title: result.title } : {}), pageAge: result.page_age ?? null, encryptedContent: result.encrypted_content, type: result.type, })), ...getAnthropicCallerMetadata(part.caller), }); for (const result of part.content) { hasWebSearchResults = true; controller.enqueue({ type: 'source', sourceType: 'url', id: generateId(), url: result.url, ...(result.title != null ? { title: result.title } : {}), providerMetadata: { anthropic: { pageAge: result.page_age ?? null, }, }, }); } } else { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('web_search'), isError: true, result: { type: 'web_search_tool_result_error', errorCode: part.content.error_code, }, ...getAnthropicCallerMetadata(part.caller), }); } return; } // code execution 20250522: case 'code_execution_tool_result': { if (part.content.type === 'code_execution_result') { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: { type: part.content.type, stdout: part.content.stdout, stderr: part.content.stderr, return_code: part.content.return_code, content: part.content.content ?? [], }, }); } else if ( part.content.type === 'encrypted_code_execution_result' ) { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: { type: part.content.type, encrypted_stdout: part.content.encrypted_stdout, stderr: part.content.stderr, return_code: part.content.return_code, content: part.content.content ?? [], }, }); } else if ( part.content.type === 'code_execution_tool_result_error' ) { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), isError: true, result: { type: 'code_execution_tool_result_error', errorCode: part.content.error_code, }, }); } return; } // code execution 20250825: case 'bash_code_execution_tool_result': case 'text_editor_code_execution_tool_result': { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName('code_execution'), result: part.content, }); return; } // tool search tool results: case 'tool_search_tool_result': { let providerToolName = serverToolCalls[part.tool_use_id]; if (providerToolName == null) { const bm25CustomName = toolNameMapping.toCustomToolName( 'tool_search_tool_bm25', ); const regexCustomName = toolNameMapping.toCustomToolName( 'tool_search_tool_regex', ); if (bm25CustomName !== 'tool_search_tool_bm25') { providerToolName = 'tool_search_tool_bm25'; } else if (regexCustomName !== 'tool_search_tool_regex') { providerToolName = 'tool_search_tool_regex'; } else { providerToolName = 'tool_search_tool_regex'; } } if (part.content.type === 'tool_search_tool_search_result') { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName(providerToolName), result: part.content.tool_references.map(ref => ({ type: ref.type, toolName: ref.tool_name, })), }); } else { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: toolNameMapping.toCustomToolName(providerToolName), isError: true, result: { type: 'tool_search_tool_result_error', errorCode: part.content.error_code, }, }); } return; } // advisor results for advisor_20260301: // arrives fully formed in a single content_block_start (no deltas). case 'advisor_tool_result': { const advisorToolName = toolNameMapping.toCustomToolName('advisor'); if (part.content.type === 'advisor_result') { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, result: { type: 'advisor_result', text: part.content.text, ...(part.content.stop_reason != null && { stopReason: part.content.stop_reason, }), }, }); } else if (part.content.type === 'advisor_redacted_result') { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, result: { type: 'advisor_redacted_result', encryptedContent: part.content.encrypted_content, ...(part.content.stop_reason != null && { stopReason: part.content.stop_reason, }), }, }); } else { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: advisorToolName, isError: true, result: { type: 'advisor_tool_result_error', errorCode: part.content.error_code, }, }); } return; } case 'mcp_tool_use': { mcpToolCalls[part.id] = { type: 'tool-call', toolCallId: part.id, toolName: part.name, input: JSON.stringify(part.input), providerExecuted: true, dynamic: true, providerMetadata: { anthropic: { type: 'mcp-tool-use', serverName: part.server_name, }, }, }; controller.enqueue(mcpToolCalls[part.id]); return; } case 'mcp_tool_result': { controller.enqueue({ type: 'tool-result', toolCallId: part.tool_use_id, toolName: mcpToolCalls[part.tool_use_id].toolName, isError: part.is_error, result: part.content, dynamic: true, providerMetadata: mcpToolCalls[part.tool_use_id].providerMetadata, }); return; } default: { const _exhaustiveCheck: never = contentBlockType; throw new Error( `Unsupported content block type: ${_exhaustiveCheck}`, ); } } } case 'content_block_stop': { // when finishing a tool call block, send the full tool call: if (contentBlocks[value.index] != null) { const contentBlock = contentBlocks[value.index]; switch (contentBlock.type) { case 'text': { controller.enqueue({ type: 'text-end', id: String(value.index), ...(contentBlock.citations.length > 0 && { citations: contentBlock.citations.map(citation => mapAnthropicCitation(citation, citationDocuments), ), providerMetadata: { anthropic: { citations: contentBlock.citations, }, } satisfies AnthropicTextProviderMetadata, }), }); break; } case 'reasoning': { controller.enqueue({ type: 'reasoning-end', id: String(value.index), }); break; } case 'tool-call': // when a json response tool is used, the tool call is returned as text, // so we ignore the tool call content: const isJsonResponseTool = usesJsonResponseTool && contentBlock.toolName === 'json'; if (!isJsonResponseTool) { // toolset member calls: emit the accumulated input with // the member name injected as `action` in one delta. if (contentBlock.toolset != null) { let memberInput: unknown = {}; try { memberInput = contentBlock.input === '' ? {} : secureJsonParse(contentBlock.input); } catch { // ignore parse errors, fall back to the raw input memberInput = undefined; } contentBlock.input = memberInput === undefined ? contentBlock.input : JSON.stringify( toToolsetMemberInput({ memberName: contentBlock.toolset.memberName, input: memberInput, }), ); controller.enqueue({ type: 'tool-input-delta', id: contentBlock.toolCallId, delta: contentBlock.input, }); } controller.enqueue({ type: 'tool-input-end', id: contentBlock.toolCallId, }); // For code_execution, inject 'programmatic-tool-call' type // when input has { code } format (programmatic tool calling) let finalInput = contentBlock.input === '' ? '{}' : contentBlock.input; if (contentBlock.providerToolName === 'code_execution') { try { const parsed = secureJsonParse(finalInput); if ( parsed != null && typeof parsed === 'object' && 'code' in parsed && !('type' in parsed) ) { finalInput = JSON.stringify({ type: 'programmatic-tool-call', ...parsed, }); } } catch { // ignore parse errors, use original input } } controller.enqueue({ type: 'tool-call', toolCallId: contentBlock.toolCallId, toolName: contentBlock.toolName, input: finalInput, providerExecuted: contentBlock.providerExecuted, // Specific 'web_fetch' or 'web_search' tools may need code execution, which the Anthropic API // implicitly allows. In this scenario, we need to allow 'code_execution' tool calls even if the // tool was not explicitly provided. We therefore bypass the general validation by marking the // tool as dynamic. ...(markCodeExecutionDynamic && contentBlock.providerToolName === 'code_execution' ? { dynamic: true } : {}), ...((contentBlock.caller || contentBlock.toolset) && { providerMetadata: { anthropic: { ...(contentBlock.toolset && { toolsetName: contentBlock.toolset.name, }), ...(contentBlock.caller && { caller: contentBlock.caller, }), }, }, }), }); } break; } delete contentBlocks[value.index]; } blockType = undefined; // reset block type return; } case 'content_block_delta': { const deltaType = value.delta.type; switch (deltaType) { case 'text_delta': { // when a json response tool is used, the tool call is returned as text, // so we ignore the text content: if (usesJsonResponseTool) { return; // excluding the text-start will also exclude the text-end } controller.enqueue({ type: 'text-delta', id: String(value.index), delta: value.delta.text, }); return; } case 'thinking_delta': { controller.enqueue({ type: 'reasoning-delta', id: String(value.index), delta: value.delta.thinking, }); return; } case 'signature_delta': { // signature are only supported on thinking blocks: if (blockType === 'thinking') { controller.enqueue({ type: 'reasoning-delta', id: String(value.index), delta: '', providerMetadata: { anthropic: { signature: value.delta.signature, } satisfies AnthropicReasoningMetadata, }, }); } return; } case 'compaction_delta': { if (value.delta.content != null) { controller.enqueue({ type: 'text-delta', id: String(value.index), delta: value.delta.content, }); } return; } case 'input_json_delta': { const contentBlock = contentBlocks[value.index]; let delta = value.delta.partial_json; // skip empty deltas to enable replacing the first character // in the code execution 20250825 tool. if (delta.length === 0) { return; } if (isJsonResponseFromTool) { if (contentBlock?.type !== 'text') { return; // exclude reasoning } controller.enqueue({ type: 'text-delta', id: String(value.index), delta, }); } else { if (contentBlock?.type !== 'tool-call') { return; } // toolset member input is emitted as a single delta once // the block is complete (the member name is injected): if (contentBlock.toolset != null) { contentBlock.input += delta; return; } // for the code execution 20250825, we need to add // the type to the delta and change the tool name. if ( contentBlock.firstDelta && contentBlock.providerToolInputType != null ) { delta = `{"type": "${contentBlock.providerToolInputType}",${delta.substring(1)}`; } controller.enqueue({ type: 'tool-input-delta', id: contentBlock.toolCallId, delta, }); contentBlock.input += delta; contentBlock.firstDelta = false; } return; } case 'citations_delta': { const citation = value.delta.citation; const contentBlock = contentBlocks[value.index]; if (contentBlock?.type === 'text') { contentBlock.citations.push(citation); } if (citation.type === 'web_search_result_location') { fallbackWebCitations.push(citation); } else { const source = createCitationSource( citation, citationDocuments, generateId, ); if (source) controller.enqueue(source); } return; } default: { const _exhaustiveCheck: never = deltaType; throw new Error( `Unsupported delta type: ${_exhaustiveCheck}`, ); } } } case 'message_start': { if (isMessageOpen) { if (activeMessageId === value.message.id) { return; } hasInvalidMessageSequence = true; controller.enqueue({ type: 'error', error: new InvalidResponseDataError({ data: value, message: `Received message_start for message ${JSON.stringify(value.message.id)} ` + `while message ${JSON.stringify(activeMessageId)} is still open.`, }), }); return; } isMessageOpen = true; activeMessageId = value.message.id; usage.input_tokens = value.message.usage.input_tokens; usage.cache_read_input_tokens = value.message.usage.cache_read_input_tokens ?? 0; usage.cache_creation_input_tokens = value.message.usage.cache_creation_input_tokens ?? 0; rawUsage = { ...(value.message.usage as JSONObject), }; if (value.message.input_transformations != null) { inputTransformations = value.message.input_transformations; } if (value.message.container != null) { container = { expiresAt: value.message.container.expires_at, id: value.message.container.id, skills: null, }; } if (value.message.stop_reason != null) { finishReason = { unified: mapAnthropicStopReason({ finishReason: value.message.stop_reason, isJsonResponseFromTool, }), raw: value.message.stop_reason, }; } controller.enqueue({ type: 'response-metadata', id: value.message.id ?? undefined, modelId: value.message.model ?? undefined, }); controller.enqueue({ type: 'custom', kind: 'anthropic.message_start', providerMetadata: { anthropic: { id: value.message.id ?? null, model: value.message.model ?? null, usage: rawUsage, }, }, }); // Programmatic tool calling: process pre-populated content blocks // (for deferred tool calls, content may be in message_start) if (value.message.content != null) { for ( let contentIndex = 0; contentIndex < value.message.content.length; contentIndex++ ) { const part = value.message.content[contentIndex]; if (part.type === 'tool_use') { const callerInfo = getAnthropicCallerInfo(part.caller); controller.enqueue({ type: 'tool-input-start', id: part.id, toolName: part.name, }); const inputStr = JSON.stringify(part.input ?? {}); controller.enqueue({ type: 'tool-input-delta', id: part.id, delta: inputStr, }); controller.enqueue({ type: 'tool-input-end', id: part.id, }); controller.enqueue({ type: 'tool-call', toolCallId: part.id, toolName: part.name, input: inputStr, ...(callerInfo && { providerMetadata: { anthropic: { caller: callerInfo, }, }, }), }); } } } return; } case 'message_delta': { if ( value.usage.input_tokens != null && usage.input_tokens !== value.usage.input_tokens ) { usage.input_tokens = value.usage.input_tokens; } usage.output_tokens = value.usage.output_tokens; if (value.usage.output_tokens_details != null) { usage.output_tokens_details = value.usage.output_tokens_details; } if (value.usage.cache_read_input_tokens != null) { usage.cache_read_input_tokens = value.usage.cache_read_input_tokens; } if (value.usage.cache_creation_input_tokens != null) { usage.cache_creation_input_tokens = value.usage.cache_creation_input_tokens; } if (value.usage.iterations != null) { usage.iterations = value.usage.iterations; } finishReason = { unified: mapAnthropicStopReason({ finishReason: value.delta.stop_reason, isJsonResponseFromTool, }), raw: value.delta.stop_reason ?? undefined, }; stopSequence = value.delta.stop_sequence ?? null; stopDetails = mapAnthropicStopDetails(value.delta.stop_details); container = value.delta.container != null ? { expiresAt: value.delta.container.expires_at, id: value.delta.container.id, skills: value.delta.container.skills?.map(skill => ({ type: skill.type, skillId: skill.skill_id, version: skill.version, })) ?? null, } : null; if (value.context_management) { contextManagement = mapAnthropicResponseContextManagement( value.context_management, ); } if (value.input_transformations != null) { inputTransformations = value.input_transformations; } // Earlier deltas may carry null while the classifier is still // running; the last non-null value is the final verdict. if (value.delta.safeguard_results != null) { safeguardResults = value.delta.safeguard_results; } rawUsage = { ...rawUsage, ...(value.usage as JSONObject), }; return; } case 'message_stop': { isMessageOpen = false; activeMessageId = undefined; const anthropicMetadata = { usage: (rawUsage as JSONObject) ?? null, stopSequence, ...(stopDetails != null ? { stopDetails } : {}), ...(inputTransformations != null ? { inputTransformations } : {}), ...(safeguardResults != null ? { safeguardResults } : {}), iterations: usage.iterations ? usage.iterations.map( iter => ({ type: iter.type, ...(iter.model != null ? { model: iter.model } : {}), inputTokens: iter.input_tokens, outputTokens: iter.output_tokens, ...(iter.cache_creation_input_tokens ? { cacheCreationInputTokens: iter.cache_creation_input_tokens, } : {}), ...(iter.cache_read_input_tokens ? { cacheReadInputTokens: iter.cache_read_input_tokens, } : {}), }) satisfies AnthropicUsageIteration, ) : null, container, contextManagement, } satisfies AnthropicMessageMetadata; const providerMetadata: SharedV4ProviderMetadata = { anthropic: anthropicMetadata, }; if ( usedCustomProviderKey && providerOptionsName !== 'anthropic' ) { providerMetadata[providerOptionsName] = anthropicMetadata; } if (!hasWebSearchResults) { for (const citation of fallbackWebCitations) { const source = createCitationSource( citation, citationDocuments, generateId, ); if (source) controller.enqueue(source); } } fallbackWebCitations.length = 0; controller.enqueue({ type: 'finish', finishReason, usage: convertAnthropicUsage({ usage, rawUsage }), providerMetadata, }); return; } case 'error': { controller.enqueue({ type: 'error', error: createAnthropicStreamError(value.error), }); return; } default: { const _exhaustiveCheck: never = value; throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`); } } }, }), ); let hasOutput = false; let hasStreamError = false; let lastFinish: | Extract | undefined; const streamWithFinishReason = transformedStream.pipeThrough( new TransformStream( { transform(chunk, controller) { switch (chunk.type) { case 'response-metadata': hasOutput = false; break; case 'finish': hasOutput = false; lastFinish = chunk; break; case 'error': hasStreamError = true; break; case 'text-delta': case 'reasoning-delta': case 'tool-input-delta': hasOutput ||= chunk.delta.length > 0; break; case 'tool-call': hasOutput = true; break; } controller.enqueue(chunk); }, flush(controller) { // Preserve the existing no-output error path. Only partial output // without a finish chunk receives an interrupted finish reason. if (hasOutput && !hasStreamError) { controller.enqueue({ type: 'finish', finishReason: { unified: 'interrupted', raw: undefined }, // Keep the usage and metadata core already had before the cutoff. usage: lastFinish?.usage ?? { inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: undefined, text: undefined, reasoning: undefined, }, }, ...(lastFinish?.providerMetadata != null ? { providerMetadata: lastFinish.providerMetadata } : {}), }); } }, }, ), ); // The first chunk needs to be pulled immediately to check if it is an error const [streamForFirstChunk, streamForConsumer] = streamWithFinishReason.tee(); const firstChunkReader = streamForFirstChunk.getReader(); try { await firstChunkReader.read(); // streamStart comes first, ignored let result = await firstChunkReader.read(); // when raw chunks are enabled, the first chunk is a raw chunk, so we need to read the next chunk if (result.value?.type === 'raw') { result = await firstChunkReader.read(); } // The Anthropic API returns 200 responses when there are overloaded errors. // We handle the case where the first chunk is an error here and transform // it into an APICallError. if (result.value?.type === 'error') { const error = result.value.error; if (!isProviderStreamError(error)) { throw new Error('Expected a normalized Anthropic stream error'); } throw new APICallError({ message: error.message, url, requestBodyValues: body, statusCode: error.statusCode ?? 500, responseHeaders, responseBody: JSON.stringify(error.data), isRetryable: error.isRetryable ?? false, }); } } finally { firstChunkReader.cancel().catch(() => {}); firstChunkReader.releaseLock(); } return { stream: streamForConsumer, request: { body }, response: { headers: responseHeaders }, }; } } /** * Returns the capabilities of a Claude model that are used for defaults and feature selection. * * @see https://docs.claude.com/en/docs/about-claude/models/overview#model-comparison-table * @see https://platform.claude.com/docs/en/build-with-claude/structured-outputs */ export function getModelCapabilities(modelId: string): { maxOutputTokens: number; supportsStructuredOutput: boolean; supportsAdaptiveThinking: boolean; rejectsSamplingParameters: boolean; supportsXhighEffort: boolean; rejectsThinkingDisabledAboveHighEffort: boolean; /** * Thinking is always adaptive: `thinking.type` `disabled` and `enabled` * are rejected with a 400. */ rejectsThinkingDisabled: boolean; /** * Budget-based thinking (`thinking.type` `enabled` with `budget_tokens`) is * rejected with a 400. Only adaptive thinking is supported. */ rejectsBudgetThinking: boolean; /** * Forced tool use (`tool_choice` `any` or a named tool) is rejected with a 400. */ rejectsForcedToolUse: boolean; /** * Supports `thinking.type` `between_tools`, the lowest thinking setting on * models that reject disabled thinking. */ supportsBetweenToolsThinking: boolean; isKnownModel: boolean; } { if (modelId.includes('claude-sonnet-5-5')) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: true, rejectsThinkingDisabled: true, rejectsBudgetThinking: true, rejectsForcedToolUse: true, supportsBetweenToolsThinking: true, isKnownModel: true, }; } else if (modelId.includes('claude-opus-5-5')) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: true, rejectsThinkingDisabled: true, rejectsBudgetThinking: true, rejectsForcedToolUse: true, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-haiku-5-5')) { // Thinking can be turned off, but only up to `high` effort. return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: true, rejectsThinkingDisabled: false, rejectsBudgetThinking: true, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-opus-5')) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: true, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-fable-5-1')) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: true, rejectsBudgetThinking: true, rejectsForcedToolUse: true, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-fable-5')) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: true, rejectsBudgetThinking: true, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if ( modelId.includes('claude-opus-4-8') || modelId.includes('claude-opus-4-7') || modelId.includes('claude-sonnet-5') ) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if ( modelId.includes('claude-sonnet-4-6') || modelId.includes('claude-opus-4-6') ) { return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if ( modelId.includes('claude-sonnet-4-5') || modelId.includes('claude-opus-4-5') || modelId.includes('claude-haiku-4-5') ) { return { maxOutputTokens: 64000, supportsStructuredOutput: true, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-opus-4-1')) { return { maxOutputTokens: 32000, supportsStructuredOutput: true, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (/claude-sonnet-4(?:-|@)/.test(modelId)) { return { maxOutputTokens: 64000, supportsStructuredOutput: false, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (/claude-opus-4(?:-|@)/.test(modelId)) { return { maxOutputTokens: 32000, supportsStructuredOutput: false, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if (modelId.includes('claude-3-haiku')) { return { maxOutputTokens: 4096, supportsStructuredOutput: false, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: true, }; } else if ( /claude-(?:instant(?:-|$)|v?2(?=$|[-.:])|3(?=$|[-.]))/.test(modelId) ) { return { maxOutputTokens: 4096, supportsStructuredOutput: false, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: false, }; } else if (modelId.includes('claude-')) { // Known and legacy Claude families are handled above, so any remaining // Claude ID is assumed to be newer than the known list. Keep it unknown so // callers still receive the maxOutputTokens compatibility warning. return { maxOutputTokens: 128000, supportsStructuredOutput: true, supportsAdaptiveThinking: true, rejectsSamplingParameters: true, supportsXhighEffort: true, rejectsThinkingDisabledAboveHighEffort: true, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: false, }; } else { // Non-Claude models (e.g. served through Anthropic-compatible APIs) // keep conservative defaults. return { maxOutputTokens: 4096, supportsStructuredOutput: false, supportsAdaptiveThinking: false, rejectsSamplingParameters: false, supportsXhighEffort: false, rejectsThinkingDisabledAboveHighEffort: false, rejectsThinkingDisabled: false, rejectsBudgetThinking: false, rejectsForcedToolUse: false, supportsBetweenToolsThinking: false, isKnownModel: false, }; } } export function hasDynamicFilteringWebToolWithoutCodeExecution( tools: AnthropicTool[] | undefined, ): boolean { if (!tools) { return false; } let hasDynamicFilteringWebTool = false; let hasCodeExecutionTool = false; for (const tool of tools) { if ( 'type' in tool && (tool.type === 'web_fetch_20260209' || tool.type === 'web_fetch_20260318' || tool.type === 'web_search_20260209' || tool.type === 'web_search_20260318') ) { hasDynamicFilteringWebTool = true; continue; } if ( 'type' in tool && (tool.type === 'code_execution_20250522' || tool.type === 'code_execution_20250825' || tool.type === 'code_execution_20260120') ) { hasCodeExecutionTool = true; break; } } return hasDynamicFilteringWebTool && !hasCodeExecutionTool; } function resolveAnthropicReasoningConfig({ reasoning, modelId, supportsAdaptiveThinking, supportsXhighEffort, rejectsThinkingDisabled, supportsBetweenToolsThinking, maxOutputTokensForModel, warnings, }: { reasoning: LanguageModelV4CallOptions['reasoning']; modelId: string; supportsAdaptiveThinking: boolean; supportsXhighEffort: boolean; rejectsThinkingDisabled: boolean; supportsBetweenToolsThinking: boolean; maxOutputTokensForModel: number; warnings: SharedV4Warning[]; }): Pick | undefined { if (!isCustomReasoning(reasoning)) { return undefined; } if (reasoning === 'none') { // `between_tools` is the lowest thinking setting on models that support // it: no upfront thinking, only short progress notes between tool calls. if (supportsBetweenToolsThinking) { return { thinking: { type: 'between_tools' } }; } // Models that always run adaptive thinking cannot disable it. Use low // effort to keep thinking short instead of sending a request that the // API would reject. if (rejectsThinkingDisabled) { warnings.push({ type: 'compatibility', feature: 'reasoning', details: `reasoning 'none' is not supported by ${modelId}; it always uses adaptive thinking. ` + `Using effort 'low' to minimize thinking instead.`, }); return { effort: 'low' }; } return { thinking: { type: 'disabled' } }; } if (supportsAdaptiveThinking) { const effort = mapReasoningToProviderEffort({ reasoning, effortMap: { minimal: 'low' as const, low: 'low' as const, medium: 'medium' as const, high: 'high' as const, xhigh: supportsXhighEffort ? ('xhigh' as const) : ('max' as const), max: 'max' as const, }, warnings, }); return { thinking: { type: 'adaptive', display: 'summarized' }, effort, }; } const budgetTokens = mapReasoningToProviderBudget({ reasoning, maxOutputTokens: maxOutputTokensForModel, maxReasoningBudget: maxOutputTokensForModel, warnings, }); if (budgetTokens == null) { return undefined; } return { thinking: { type: 'enabled', budgetTokens } }; } function mapAnthropicResponseContextManagement( contextManagement: AnthropicResponseContextManagement | null | undefined, ): AnthropicMessageMetadata['contextManagement'] | null { return contextManagement ? { appliedEdits: contextManagement.applied_edits .map(edit => { const strategy = edit.type; switch (strategy) { case 'clear_tool_uses_20250919': return { type: edit.type, clearedToolUses: edit.cleared_tool_uses, clearedInputTokens: edit.cleared_input_tokens, }; case 'clear_thinking_20251015': return { type: edit.type, clearedThinkingTurns: edit.cleared_thinking_turns, clearedInputTokens: edit.cleared_input_tokens, }; case 'compact_20260112': return { type: edit.type, }; } }) .filter(edit => edit !== undefined), } : null; } function mapAnthropicStopDetails( stopDetails: AnthropicStopDetails | null | undefined, ): AnthropicMessageMetadata['stopDetails'] | undefined { if (stopDetails == null) { return undefined; } return { type: stopDetails.type, ...(stopDetails.category != null ? { category: stopDetails.category } : {}), ...(stopDetails.explanation != null ? { explanation: stopDetails.explanation } : {}), ...(stopDetails.recommended_model != null ? { recommendedModel: stopDetails.recommended_model } : {}), }; }