UNPKG

@ai-sdk/anthropic

Version:

The **[Anthropic provider](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post).

1,544 lines (1,417 loc) 90.6 kB
import { APICallError, type JSONObject, type LanguageModelV3, type LanguageModelV3CallOptions, type LanguageModelV3Content, type LanguageModelV3FinishReason, type LanguageModelV3FunctionTool, type LanguageModelV3GenerateResult, type LanguageModelV3Prompt, type LanguageModelV3Source, type LanguageModelV3StreamPart, type LanguageModelV3StreamResult, type LanguageModelV3ToolCall, type SharedV3ProviderMetadata, type SharedV3Warning, } from '@ai-sdk/provider'; import { combineHeaders, createEventSourceResponseHandler, createJsonResponseHandler, createToolNameMapping, generateId, parseProviderOptions, postJsonToApi, resolve, 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 { anthropicMessagesChunkSchema, anthropicMessagesResponseSchema, type AnthropicContainer, type AnthropicReasoningMetadata, type AnthropicResponseContextManagement, type AnthropicTool, type Citation, } from './anthropic-messages-api'; import { anthropicLanguageModelOptions, type AnthropicMessagesModelId, } from './anthropic-messages-options'; import { prepareTools } from './anthropic-prepare-tools'; import { convertAnthropicMessagesUsage, type AnthropicMessagesUsage, } from './convert-anthropic-messages-usage'; import { convertToAnthropicMessagesPrompt } from './convert-to-anthropic-messages-prompt'; import { CacheControlValidator } from './get-cache-control'; import { mapAnthropicStopReason } from './map-anthropic-stop-reason'; import { sanitizeJsonSchema } from './sanitize-json-schema'; function createCitationSource( citation: Citation, citationDocuments: Array<{ title: string; filename?: string; mediaType: string; }>, generateId: () => string, ): LanguageModelV3Source | undefined { if (citation.type === 'web_search_result_location') { return { type: 'source' as const, sourceType: 'url' as const, id: generateId(), url: citation.url, title: citation.title, providerMetadata: { anthropic: { citedText: citation.cited_text, encryptedIndex: citation.encrypted_index, }, } satisfies SharedV3ProviderMetadata, }; } 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 SharedV3ProviderMetadata, }; } type AnthropicMessagesConfig = { provider: string; baseURL: string; headers: Resolvable<Record<string, string | undefined>>; fetch?: FetchFunction; buildRequestUrl?: (baseURL: string, isStreaming: boolean) => string; transformRequestBody?: ( args: Record<string, any>, betas: Set<string>, ) => Record<string, any>; supportedUrls?: () => LanguageModelV3['supportedUrls']; generateId?: () => string; /** * When false, the model will use JSON tool fallback for structured outputs. */ supportsNativeStructuredOutput?: boolean; /** * When false, `strict` on tool definitions will be ignored and a warning emitted. * Defaults to true. */ supportsStrictTools?: boolean; }; export class AnthropicMessagesLanguageModel implements LanguageModelV3 { readonly specificationVersion = 'v3'; readonly modelId: AnthropicMessagesModelId; private readonly config: AnthropicMessagesConfig; private readonly generateId: () => string; constructor( modelId: AnthropicMessagesModelId, config: AnthropicMessagesConfig, ) { 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; } /** * Extracts the dynamic provider name from the config.provider string. * e.g., 'my-custom-anthropic.messages' -> 'my-custom-anthropic' */ private get providerOptionsName(): string { const provider = this.config.provider; const dotIndex = provider.indexOf('.'); return dotIndex === -1 ? provider : provider.substring(0, dotIndex); } get supportedUrls() { return this.config.supportedUrls?.() ?? {}; } private async getArgs({ userSuppliedBetas, prompt, maxOutputTokens, temperature, topP, topK, frequencyPenalty, presencePenalty, stopSequences, responseFormat, seed, tools, toolChoice, providerOptions, stream, }: LanguageModelV3CallOptions & { stream: boolean; userSuppliedBetas: Set<string>; }) { const warnings: SharedV3Warning[] = []; 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' }); } if (temperature != null && temperature > 1) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `${temperature} exceeds anthropic maximum of 1.0. clamped to 1.0`, }); temperature = 1; } else if (temperature != null && temperature < 0) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `${temperature} is below anthropic 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 = this.providerOptionsName; // 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, rejectsSamplingParameters, isKnownModel, } = getModelCapabilities(this.modelId); if (rejectsSamplingParameters) { if (temperature != null) { warnings.push({ type: 'unsupported', feature: 'temperature', details: `temperature is not supported by ${this.modelId} and will be ignored`, }); temperature = undefined; } if (topK != null) { warnings.push({ type: 'unsupported', feature: 'topK', details: `topK is not supported by ${this.modelId} and will be ignored`, }); topK = undefined; } if (topP != null) { warnings.push({ type: 'unsupported', feature: 'topP', details: `topP is not supported by ${this.modelId} and will be ignored`, }); topP = undefined; } } const isAnthropicModel = isKnownModel || this.modelId.startsWith('claude-'); const supportsStructuredOutput = (this.config.supportsNativeStructuredOutput ?? true) && modelSupportsStructuredOutput; const supportsStrictTools = (this.config.supportsStrictTools ?? true) && modelSupportsStructuredOutput; const structureOutputMode = anthropicOptions?.structuredOutputMode ?? 'auto'; const useStructuredOutput = structureOutputMode === 'outputFormat' || (structureOutputMode === 'auto' && supportsStructuredOutput); const jsonResponseTool: LanguageModelV3FunctionTool | undefined = responseFormat?.type === 'json' && responseFormat.schema != null && !useStructuredOutput ? { type: 'function', name: 'json', description: 'Respond with a JSON object.', inputSchema: responseFormat.schema, } : undefined; const contextManagement = anthropicOptions?.contextManagement; // Create a shared cache control validator to track breakpoints across tools and messages const cacheControlValidator = new CacheControlValidator(); 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.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_fetch_20250910': 'web_fetch', 'anthropic.web_fetch_20260209': '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 { prompt: messagesPrompt, betas } = await convertToAnthropicMessagesPrompt({ prompt, sendReasoning: anthropicOptions?.sendReasoning ?? true, warnings, cacheControlValidator, toolNameMapping, }); const thinkingType = anthropicOptions?.thinking?.type; const isThinking = thinkingType === 'enabled' || thinkingType === 'adaptive'; 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: this.modelId, // standardized settings: max_tokens: maxTokens, temperature, top_k: topK, top_p: topP, stop_sequences: stopSequences, // provider specific settings: ...(isThinking && { thinking: { type: thinkingType, ...(thinkingBudget != null && { budget_tokens: thinkingBudget }), ...(thinkingDisplay != null && { display: thinkingDisplay }), }, }), ...((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?.inferenceGeo && { inference_geo: anthropicOptions.inferenceGeo, }), ...(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.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, ...(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) { 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) { 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 ${this.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 (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'); } 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, } : { tools: tools ?? [], toolChoice, disableParallelToolUse: anthropicOptions?.disableParallelToolUse, cacheControlValidator, supportsStructuredOutput, supportsStrictTools, defaultEagerInputStreaming, }, ); // 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 async getHeaders({ betas, headers, }: { betas: Set<string>; headers: Record<string, string | undefined> | undefined; }) { return combineHeaders( await resolve(this.config.headers), headers, betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {}, ); } private async getBetasFromHeaders( requestHeaders: Record<string, string | undefined> | undefined, ) { const configHeaders = await resolve(this.config.headers); 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` ); } private transformRequestBody( args: Record<string, any>, betas: Set<string>, ): Record<string, any> { return this.config.transformRequestBody?.(args, betas) ?? args; } private extractCitationDocuments(prompt: LanguageModelV3Prompt): 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<typeof part, { type: 'file' }>; return { title: filePart.filename ?? 'Untitled Document', filename: filePart.filename, mediaType: filePart.mediaType, }; }); } async doGenerate( options: LanguageModelV3CallOptions, ): Promise<LanguageModelV3GenerateResult> { const { args, warnings, betas, usesJsonResponseTool, toolNameMapping, providerOptionsName, usedCustomProviderKey, } = await this.getArgs({ ...options, stream: false, userSuppliedBetas: await this.getBetasFromHeaders(options.headers), }); // Extract citation documents for response processing const citationDocuments = [ ...this.extractCitationDocuments(options.prompt), ]; const markCodeExecutionDynamic = hasWebTool20260209WithoutCodeExecution( args.tools, ); const { responseHeaders, value: response, rawValue: rawResponse, } = await postJsonToApi({ url: this.buildRequestUrl(false), headers: await this.getHeaders({ betas, headers: options.headers }), body: this.transformRequestBody(args, betas), failedResponseHandler: anthropicFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler( anthropicMessagesResponseSchema, ), abortSignal: options.abortSignal, fetch: this.config.fetch, }); const content: Array<LanguageModelV3Content> = []; const mcpToolCalls: Record<string, LanguageModelV3ToolCall> = {}; const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name let isJsonResponseFromTool = false; // map response content to content array for (const part of response.content) { switch (part.type) { case 'text': { if (!usesJsonResponseTool) { content.push({ type: 'text', text: part.text }); // Process citations if present if (part.citations) { for (const citation of part.citations) { 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 'compaction': { content.push({ type: 'text', text: part.content, providerMetadata: { anthropic: { type: 'compaction', }, }, }); 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 { const caller = part.caller; const callerInfo = caller ? { type: caller.type, toolId: 'tool_id' in caller ? caller.tool_id : undefined, } : undefined; content.push({ type: 'tool-call', toolCallId: part.id, toolName: part.name, input: JSON.stringify(part.input), ...(callerInfo && { providerMetadata: { anthropic: { caller: callerInfo, }, }, }), }); } break; } case 'server_tool_use': { // code execution 20250825 needs mapping: if ( part.name === 'text_editor_code_execution' || part.name === 'bash_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, }); } 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, // We want this 'code_execution' tool call to be allowed even if the tool is not explicitly provided. // Since the validation generally bypasses dynamic tools, we mark this specific tool as dynamic. ...(markCodeExecutionDynamic && part.name === 'code_execution' ? { dynamic: true } : {}), }); } 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, }); } else if (part.name === 'advisor') { content.push({ type: 'tool-call', toolCallId: part.id, toolName: toolNameMapping.toCustomToolName('advisor'), input: JSON.stringify(part.input), providerExecuted: true, }); } 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, }, }, }, }); } 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, }, }); } 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, title: result.title, pageAge: result.page_age ?? null, encryptedContent: result.encrypted_content, type: result.type, })), }); for (const result of part.content) { content.push({ type: 'source', sourceType: 'url', id: this.generateId(), url: result.url, 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, }, }); } 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, }, }); } 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, }, }); } 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; } } } return { content, finishReason: { unified: mapAnthropicStopReason({ finishReason: response.stop_reason, isJsonResponseFromTool, }), raw: response.stop_reason ?? undefined, }, usage: convertAnthropicMessagesUsage({ usage: response.usage }), request: { body: args }, response: { id: response.id ?? undefined, modelId: response.model ?? undefined, headers: responseHeaders, body: rawResponse, }, warnings, providerMetadata: (() => { const anthropicMetadata = { usage: response.usage as JSONObject, cacheCreationInputTokens: response.usage.cache_creation_input_tokens ?? null, stopSequence: response.stop_sequence ?? null, iterations: response.usage.iterations ? response.usage.iterations.map(iter => iter.type === 'advisor_message' ? ({ type: iter.type, 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) : ({ type: iter.type, 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: SharedV3ProviderMetadata = { anthropic: anthropicMetadata, }; if (usedCustomProviderKey && providerOptionsName !== 'anthropic') { providerMetadata[providerOptionsName] = anthropicMetadata; } return providerMetadata; })(), }; } async doStream( options: LanguageModelV3CallOptions, ): Promise<LanguageModelV3StreamResult> { const { args: body, warnings, betas, usesJsonResponseTool, toolNameMapping, providerOptionsName, usedCustomProviderKey, } = await this.getArgs({ ...options, stream: true, userSuppliedBetas: await this.getBetasFromHeaders(options.headers), }); // Extract citation documents for response processing const citationDocuments = [ ...this.extractCitationDocuments(options.prompt), ]; const markCodeExecutionDynamic = hasWebTool20260209WithoutCodeExecution( body.tools, ); const url = this.buildRequestUrl(true); const { responseHeaders, value: response } = await postJsonToApi({ url, headers: await this.getHeaders({ betas, headers: options.headers }), body: this.transformRequestBody(body, betas), failedResponseHandler: anthropicFailedResponseHandler, successfulResponseHandler: createEventSourceResponseHandler( anthropicMessagesChunkSchema, ), abortSignal: options.abortSignal, fetch: this.config.fetch, }); let finishReason: LanguageModelV3FinishReason = { unified: 'other', raw: undefined, }; const usage: AnthropicMessagesUsage = { 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; caller?: { type: | 'code_execution_20250825' | 'code_execution_20260120' | 'direct'; toolId?: string; }; } | { type: 'text' | 'reasoning' } > = {}; const mcpToolCalls: Record<string, LanguageModelV3ToolCall> = {}; const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name let contextManagement: | AnthropicMessageMetadata['contextManagement'] | null = null; let rawUsage: JSONObject | undefined = undefined; let cacheCreationInputTokens: number | null = null; let stopSequence: string | null = null; let container: AnthropicMessageMetadata['container'] | null = null; let isJsonResponseFromTool = 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<InferSchema<typeof anthropicMessagesChunkSchema>>, LanguageModelV3StreamPart >({ start(controller) { controller.enqueue({ type: 'stream-start', warnings }); }, transform(chunk, controller) { 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; 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' }; 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' }; controller.enqueue({