import { UnsupportedFunctionalityError, type JSONObject, type JSONValue, type LanguageModelV4FilePart, type SharedV4Warning, type LanguageModelV4Message, type LanguageModelV4Prompt, type SharedV4ProviderMetadata, } from '@ai-sdk/provider'; import { convertBase64ToUint8Array, convertToBase64, getTopLevelMediaType, isNonNullable, parseProviderOptions, resolveFullMediaType, resolveProviderReference, safeValidateTypes, secureJsonParse, validateTypes, type ToolNameMapping, } from '@ai-sdk/provider-utils'; import { anthropicFallbackContentSchemaValidator, anthropicReasoningMetadataSchema, type AnthropicAssistantMessage, type AnthropicImageContent, type AnthropicPrompt, type AnthropicSystemMessage, type AnthropicTextContent, type AnthropicToolChangeContent, type AnthropicToolCallCaller, type AnthropicToolResultContent, type AnthropicUserMessage, type AnthropicVideoContent, type AnthropicWebFetchToolResultContent, type Citation, } from './anthropic-api'; import { anthropicFilePartProviderOptions, anthropicSystemMessageProviderOptions, } from './anthropic-language-model-options'; import { CacheControlValidator } from './get-cache-control'; import { advisor_20260301OutputSchema } from './tool/advisor_20260301'; import { codeExecution_20250522OutputSchema } from './tool/code-execution_20250522'; import { codeExecution_20250825OutputSchema } from './tool/code-execution_20250825'; import { codeExecution_20260120OutputSchema } from './tool/code-execution_20260120'; import { toolSearchRegex_20251119OutputSchema as toolSearchOutputSchema } from './tool/tool-search-regex_20251119'; import { webFetch_20250910OutputSchema } from './tool/web-fetch-20250910'; import { webSearch_20250305OutputSchema } from './tool/web-search_20250305'; function convertBytesDataToString(data: Uint8Array | string): string { if (typeof data === 'string') { return new TextDecoder().decode(convertBase64ToUint8Array(data)); } return new TextDecoder().decode(data); } /** * Extract error information from a provider tool error result value. * Handles both stringified JSON and plain object forms. */ function extractErrorValue(value: unknown): { errorCode?: string } { try { if (typeof value === 'string') { return secureJsonParse(value); } else if (typeof value === 'object' && value !== null) { return value as { errorCode?: string }; } } catch { const extractedErrorCode = (value as Record)?.errorCode; return { errorCode: typeof extractedErrorCode === 'string' ? extractedErrorCode : 'unavailable', }; } return {}; } /** Return undefined to use the default file conversion. */ export type AnthropicMediaPartConverter = ( part: LanguageModelV4FilePart, ) => PromiseLike< | Omit | undefined >; export async function convertToAnthropicPrompt({ prompt, sendReasoning, warnings, cacheControlValidator, toolNameMapping, toolsetNames = {}, convertMediaPart, }: { prompt: LanguageModelV4Prompt; sendReasoning: boolean; warnings: SharedV4Warning[]; cacheControlValidator?: CacheControlValidator; toolNameMapping: ToolNameMapping; convertMediaPart?: AnthropicMediaPartConverter; /** * Maps custom tool names of toolset tools (e.g. the computer toolset) to * the Anthropic `toolset_name`. Tool calls and results of these tools are * serialized as toolset member calls. */ toolsetNames?: Record; }): Promise<{ prompt: AnthropicPrompt; betas: Set; }> { const betas = new Set(); const blocks = groupIntoBlocks(prompt); const validator = cacheControlValidator || new CacheControlValidator(); let system: AnthropicPrompt['system'] = undefined; const messages: AnthropicPrompt['messages'] = []; let lastUserMessageIndex = -1; async function shouldEnableCitations( providerMetadata: SharedV4ProviderMetadata | undefined, ): Promise { const anthropicOptions = await parseProviderOptions({ provider: 'anthropic', providerOptions: providerMetadata, schema: anthropicFilePartProviderOptions, }); return anthropicOptions?.citations?.enabled ?? false; } async function shouldUseContainerUpload( providerMetadata: SharedV4ProviderMetadata | undefined, ): Promise { const anthropicOptions = await parseProviderOptions({ provider: 'anthropic', providerOptions: providerMetadata, schema: anthropicFilePartProviderOptions, }); return anthropicOptions?.containerUpload ?? false; } async function getDocumentMetadata( providerMetadata: SharedV4ProviderMetadata | undefined, ): Promise<{ title?: string; context?: string }> { const anthropicOptions = await parseProviderOptions({ provider: 'anthropic', providerOptions: providerMetadata, schema: anthropicFilePartProviderOptions, }); return { title: anthropicOptions?.title, context: anthropicOptions?.context, }; } for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; const isLastBlock = i === blocks.length - 1; const type = block.type; switch (type) { case 'system': { const convertedMessages: Array<{ content: AnthropicSystemMessage['content']; clearAt?: 'next_user_message'; effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'; toolChangeCount: number; }> = []; for (const { content: text, providerOptions } of block.messages) { const systemMessageOptions = await parseProviderOptions({ provider: 'anthropic', providerOptions, schema: anthropicSystemMessageProviderOptions, }); const toolChanges = systemMessageOptions?.toolChanges ?? []; const content: AnthropicSystemMessage['content'] = []; // A system message that only carries message-level controls may have // empty text; do not emit an empty text block for it. if ( text !== '' || (toolChanges.length === 0 && systemMessageOptions?.clearAt == null && systemMessageOptions?.effort == null) ) { content.push({ type: 'text' as const, text, cache_control: validator.getCacheControl(providerOptions, { type: 'system message', canCache: true, }), }); } for (const toolChange of toolChanges) { content.push({ type: toolChange.type, tool: { type: 'tool_reference', name: toolNameMapping.toProviderToolName(toolChange.toolName), }, } satisfies AnthropicToolChangeContent); } convertedMessages.push({ content, clearAt: systemMessageOptions?.clearAt, effort: systemMessageOptions?.effort, toolChangeCount: toolChanges.length, }); } const toolChangeCount = convertedMessages.reduce( (count, message) => count + message.toolChangeCount, 0, ); const hasInlineSystemOptions = convertedMessages.some( message => message.clearAt != null || message.effort != null, ); if ( i === 0 || (system == null && toolChangeCount === 0 && !hasInlineSystemOptions) ) { if (toolChangeCount > 0) { warnings.push({ type: 'other', message: 'tool changes on the initial system message are not supported by Anthropic. ' + 'Configure the initial tool set via the tools option instead. ' + 'The tool changes have been ignored.', }); } // Initial instruction text goes in the top-level system field. // Effort-only messages stay in the messages array. for (const message of convertedMessages) { if ( message.content.length === 0 && message.clearAt == null && message.effort != null ) { messages.push({ role: 'system', content: [], output_config: { effort: message.effort }, }); betas.add('mid-conversation-output-config-2026-07-01'); } else if (message.clearAt != null || message.effort != null) { warnings.push({ type: 'other', message: 'clearAt and effort on this initial system message are not supported by Anthropic. ' + 'Use a separate effort-only system message with empty content to set effort. ' + 'These options have been ignored.', }); } } system = convertedMessages.flatMap(message => message.content.filter( (part): part is AnthropicTextContent => part.type === 'text', ), ); } else { betas.add('mid-conversation-system-2026-04-07'); for (const message of convertedMessages) { messages.push({ role: 'system', content: message.content, ...(message.clearAt != null && { clear_at: message.clearAt }), ...(message.effort != null && { output_config: { effort: message.effort }, }), }); if (message.toolChangeCount > 0) { betas.add('mid-conversation-tool-changes-2026-07-01'); } if (message.clearAt != null) { betas.add('mid-conversation-system-clear-at-2026-08-21'); } if (message.effort != null) { betas.add('mid-conversation-output-config-2026-07-01'); } } } break; } case 'user': { // combines all user and tool messages in this block into a single message: const anthropicContent: AnthropicUserMessage['content'] = []; for (const message of block.messages) { const { role, content } = message; switch (role) { case 'user': { if (content.length > 0) { lastUserMessageIndex = messages.length; } for (let j = 0; j < content.length; j++) { const part = content[j]; // cache control: first add cache control from part. // for the last part of a message, // check also if the message has cache control. const isLastPart = j === content.length - 1; const cacheControl = validator.getCacheControl(part.providerOptions, { type: 'user message part', canCache: true, }) ?? (isLastPart ? validator.getCacheControl(message.providerOptions, { type: 'user message', canCache: true, }) : undefined); switch (part.type) { case 'text': { anthropicContent.push({ type: 'text', text: part.text, cache_control: cacheControl, }); break; } case 'file': { const convertedPart = await convertMediaPart?.(part); if (convertedPart != null) { anthropicContent.push({ ...convertedPart, cache_control: cacheControl, }); break; } switch (part.data.type) { case 'reference': { const fileId = resolveProviderReference({ reference: part.data.reference, provider: 'anthropic', }); betas.add('files-api-2025-04-14'); if ( await shouldUseContainerUpload(part.providerOptions) ) { anthropicContent.push({ type: 'container_upload', file_id: fileId, }); } else if ( getTopLevelMediaType(part.mediaType) === 'image' ) { anthropicContent.push({ type: 'image', source: { type: 'file', file_id: fileId }, cache_control: cacheControl, }); } else { anthropicContent.push({ type: 'document', source: { type: 'file', file_id: fileId }, cache_control: cacheControl, }); } break; } case 'text': { const enableCitations = await shouldEnableCitations( part.providerOptions, ); const metadata = await getDocumentMetadata( part.providerOptions, ); anthropicContent.push({ type: 'document', source: { type: 'text', media_type: 'text/plain', data: part.data.text, }, title: metadata.title ?? part.filename, ...(metadata.context && { context: metadata.context, }), ...(enableCitations && { citations: { enabled: true }, }), cache_control: cacheControl, }); break; } case 'url': case 'data': { const topLevel = getTopLevelMediaType(part.mediaType); if (topLevel === 'image') { anthropicContent.push({ type: 'image', source: part.data.type === 'url' ? { type: 'url', url: part.data.url.toString(), } : { type: 'base64', media_type: resolveFullMediaType({ part }), data: convertToBase64(part.data.data), }, cache_control: cacheControl, }); } else if ( topLevel === 'application' && (part.data.type === 'url' ? part.mediaType === 'application/pdf' : resolveFullMediaType({ part }) === 'application/pdf') ) { betas.add('pdfs-2024-09-25'); const enableCitations = await shouldEnableCitations( part.providerOptions, ); const metadata = await getDocumentMetadata( part.providerOptions, ); anthropicContent.push({ type: 'document', source: part.data.type === 'url' ? { type: 'url', url: part.data.url.toString(), } : { type: 'base64', media_type: 'application/pdf', data: convertToBase64(part.data.data), }, title: metadata.title ?? part.filename, ...(metadata.context && { context: metadata.context, }), ...(enableCitations && { citations: { enabled: true }, }), cache_control: cacheControl, }); } else if (part.mediaType === 'text/plain') { const enableCitations = await shouldEnableCitations( part.providerOptions, ); const metadata = await getDocumentMetadata( part.providerOptions, ); anthropicContent.push({ type: 'document', source: part.data.type === 'url' ? { type: 'url', url: part.data.url.toString(), } : { type: 'text', media_type: 'text/plain', data: convertBytesDataToString( part.data.data, ), }, title: metadata.title ?? part.filename, ...(metadata.context && { context: metadata.context, }), ...(enableCitations && { citations: { enabled: true }, }), cache_control: cacheControl, }); } else { throw new UnsupportedFunctionalityError({ functionality: `media type: ${part.mediaType}`, }); } break; } } break; } } } break; } case 'tool': { for (let i = 0; i < content.length; i++) { const part = content[i]; if (part.type === 'tool-approval-response') { continue; } const output = part.output; const outputProviderOptions = 'providerOptions' in output ? output.providerOptions : output.type === 'content' ? output.value.find( contentPart => contentPart.providerOptions != null, )?.providerOptions : undefined; // cache control: first add cache control from part. // then from tool result output, and for the last part of a // message, check also if the message has cache control. const isLastPart = i === content.length - 1; const cacheControl = validator.getCacheControl(part.providerOptions, { type: 'tool result part', canCache: true, }) ?? validator.getCacheControl(outputProviderOptions, { type: 'tool result output', canCache: true, }) ?? (isLastPart ? validator.getCacheControl(message.providerOptions, { type: 'tool result message', canCache: true, }) : undefined); let contentValue: AnthropicToolResultContent['content']; switch (output.type) { case 'content': contentValue = output.value .map(contentPart => { switch (contentPart.type) { case 'text': return { type: 'text' as const, text: contentPart.text, }; case 'file': { const topLevel = getTopLevelMediaType( contentPart.mediaType, ); if (contentPart.data.type === 'reference') { const fileId = resolveProviderReference({ reference: contentPart.data.reference, provider: 'anthropic', }); betas.add('files-api-2025-04-14'); if (topLevel === 'image') { return { type: 'image' as const, source: { type: 'file' as const, file_id: fileId, }, }; } return { type: 'document' as const, source: { type: 'file' as const, file_id: fileId, }, }; } if (contentPart.data.type === 'url') { if (topLevel === 'image') { return { type: 'image' as const, source: { type: 'url' as const, url: contentPart.data.url.toString(), }, }; } return { type: 'document' as const, source: { type: 'url' as const, url: contentPart.data.url.toString(), }, }; } if (contentPart.data.type === 'data') { if (topLevel === 'image') { return { type: 'image' as const, source: { type: 'base64' as const, media_type: resolveFullMediaType({ part: contentPart, }), data: convertToBase64( contentPart.data.data, ), }, }; } if ( resolveFullMediaType({ part: contentPart }) === 'application/pdf' ) { betas.add('pdfs-2024-09-25'); return { type: 'document' as const, source: { type: 'base64' as const, media_type: 'application/pdf', data: convertToBase64( contentPart.data.data, ), }, }; } warnings.push({ type: 'other', message: `unsupported tool content part type: ${contentPart.type} with media type: ${contentPart.mediaType}`, }); return undefined; } warnings.push({ type: 'other', message: `unsupported tool content part type: ${contentPart.type} with data type: ${contentPart.data.type}`, }); return undefined; } case 'custom': { const anthropicOptions = contentPart.providerOptions ?.anthropic as | { type: string; toolName?: string } | undefined; if (anthropicOptions?.type === 'tool-reference') { return { type: 'tool_reference' as const, tool_name: anthropicOptions.toolName!, }; } warnings.push({ type: 'other', message: `unsupported custom tool content part`, }); return undefined; } default: { warnings.push({ type: 'other', message: `unsupported tool content part type: ${(contentPart as { type: string }).type}`, }); return undefined; } } }) .filter(isNonNullable); break; case 'text': case 'error-text': contentValue = output.value; break; case 'execution-denied': contentValue = output.reason ?? 'Tool call execution denied.'; break; case 'json': case 'error-json': default: contentValue = JSON.stringify(output.value); break; } const toolsetName = getAnthropicToolsetName({ toolName: part.toolName, providerOptions: part.providerOptions, toolsetNames, }); anthropicContent.push({ type: 'tool_result', tool_use_id: part.toolCallId, ...(toolsetName != null && { toolset_name: toolsetName }), content: contentValue, is_error: output.type === 'error-text' || output.type === 'error-json' ? true : undefined, cache_control: cacheControl, }); } break; } default: { const _exhaustiveCheck: never = role; throw new Error(`Unsupported role: ${_exhaustiveCheck}`); } } } messages.push({ role: 'user', content: anthropicContent }); break; } case 'assistant': { // combines multiple assistant messages in this block into a single message: const anthropicContent: AnthropicAssistantMessage['content'] = []; const mcpToolUseIds = new Set(); for (let j = 0; j < block.messages.length; j++) { const message = block.messages[j]; const isLastMessage = j === block.messages.length - 1; const { content } = message; for (let k = 0; k < content.length; k++) { const part = content[k]; const isLastContentPart = k === content.length - 1; // cache control: first add cache control from part. // for the last part of a message, // check also if the message has cache control. const cacheControl = validator.getCacheControl(part.providerOptions, { type: 'assistant message part', canCache: true, }) ?? (isLastContentPart ? validator.getCacheControl(message.providerOptions, { type: 'assistant message', canCache: true, }) : undefined); switch (part.type) { case 'text': { // Check if this is a compaction block (via providerMetadata) const textMetadata = part.providerOptions?.anthropic as | { type?: string; citations?: Citation[]; signature?: string; } | undefined; if (textMetadata?.type === 'compaction') { if (part.text === '') { break; } if (typeof textMetadata.signature === 'string') { betas.add('compact-2026-09-04'); } anthropicContent.push({ type: 'compaction', content: part.text, ...(typeof textMetadata.signature === 'string' && { signature: textMetadata.signature, }), cache_control: cacheControl, }); } else { anthropicContent.push({ type: 'text', text: // trim the last text part if it's the last message in the block // because Anthropic does not allow trailing whitespace // in pre-filled assistant responses isLastBlock && isLastMessage && isLastContentPart ? part.text.trim() : part.text, ...(textMetadata?.citations != null && { citations: textMetadata.citations, }), cache_control: cacheControl, }); } break; } case 'reasoning': { if (sendReasoning) { const reasoningMetadata = await parseProviderOptions({ provider: 'anthropic', providerOptions: part.providerOptions, schema: anthropicReasoningMetadataSchema, }); if (reasoningMetadata != null) { if (reasoningMetadata.signature != null) { // Note: thinking blocks cannot have cache_control directly // They are cached implicitly when in previous assistant turns // Validate to provide helpful error message validator.getCacheControl(part.providerOptions, { type: 'thinking block', canCache: false, }); anthropicContent.push({ type: 'thinking', thinking: part.text, signature: reasoningMetadata.signature, }); } else if (reasoningMetadata.redactedData != null) { // Note: redacted thinking blocks cannot have cache_control directly // They are cached implicitly when in previous assistant turns // Validate to provide helpful error message validator.getCacheControl(part.providerOptions, { type: 'redacted thinking block', canCache: false, }); anthropicContent.push({ type: 'redacted_thinking', data: reasoningMetadata.redactedData, }); } else { warnings.push({ type: 'other', message: 'unsupported reasoning metadata', }); } } else { warnings.push({ type: 'other', message: 'unsupported reasoning metadata', }); } } else { warnings.push({ type: 'other', message: 'sending reasoning content is disabled for this model', }); } break; } case 'custom': { if (part.kind !== 'anthropic.fallback') { break; } const fallbackMetadata = await safeValidateTypes({ value: part.providerOptions?.anthropic, schema: anthropicFallbackContentSchemaValidator, }); if (!fallbackMetadata.success) { warnings.push({ type: 'other', message: 'anthropic fallback metadata must include from.model and to.model', }); break; } betas.add('server-side-fallback-2026-06-01'); anthropicContent.push({ type: 'fallback', from: fallbackMetadata.value.from, to: fallbackMetadata.value.to, }); break; } case 'tool-call': { const caller = getAnthropicCaller(part.providerOptions); if (part.providerExecuted) { const providerToolName = toolNameMapping.toProviderToolName( part.toolName, ); const isMcpToolUse = part.providerOptions?.anthropic?.type === 'mcp-tool-use'; if (isMcpToolUse) { mcpToolUseIds.add(part.toolCallId); const serverName = part.providerOptions?.anthropic?.serverName; if (serverName == null || typeof serverName !== 'string') { warnings.push({ type: 'other', message: 'mcp tool use server name is required and must be a string', }); break; } anthropicContent.push({ type: 'mcp_tool_use', id: part.toolCallId, name: part.toolName, input: part.input, server_name: serverName, cache_control: cacheControl, }); } else if ( // code execution subtools: providerToolName === 'code_execution' && part.input != null && typeof part.input === 'object' && 'type' in part.input && typeof part.input.type === 'string' && (part.input.type === 'bash_code_execution' || part.input.type === 'text_editor_code_execution') ) { const { type: codeExecutionType, ...inputWithoutType } = part.input; anthropicContent.push({ type: 'server_tool_use', id: part.toolCallId, name: codeExecutionType, // map back to subtool name input: inputWithoutType, ...(caller && { caller }), cache_control: cacheControl, }); } else if ( // code execution 20250825 programmatic tool calling: // Strip the fake 'programmatic-tool-call' type before sending to Anthropic providerToolName === 'code_execution' && part.input != null && typeof part.input === 'object' && 'type' in part.input && part.input.type === 'programmatic-tool-call' ) { const { type: _, ...inputWithoutType } = part.input as { type: string; code: string; }; anthropicContent.push({ type: 'server_tool_use', id: part.toolCallId, name: 'code_execution', input: inputWithoutType, ...(caller && { caller }), cache_control: cacheControl, }); } else { if ( providerToolName === 'code_execution' || // code execution 20250522 providerToolName === 'web_fetch' || providerToolName === 'web_search' ) { anthropicContent.push({ type: 'server_tool_use', id: part.toolCallId, name: providerToolName, input: part.input, ...(caller && { caller }), cache_control: cacheControl, }); } else if ( providerToolName === 'tool_search_tool_regex' || providerToolName === 'tool_search_tool_bm25' ) { anthropicContent.push({ type: 'server_tool_use', id: part.toolCallId, name: providerToolName, input: part.input, ...(caller && { caller }), cache_control: cacheControl, }); } else if (providerToolName === 'advisor') { // The advisor server_tool_use.input is always {}. anthropicContent.push({ type: 'server_tool_use', id: part.toolCallId, name: 'advisor', input: {}, ...(caller && { caller }), cache_control: cacheControl, }); } else { warnings.push({ type: 'other', message: `provider executed tool call for tool ${part.toolName} is not supported`, }); } } break; } const toolsetName = getAnthropicToolsetName({ toolName: part.toolName, providerOptions: part.providerOptions, toolsetNames, }); if (toolsetName != null) { // toolset member call: the `action` is the member tool name const rawInput = toAnthropicToolInput(part.input); const { action } = rawInput; const hasAction = typeof action === 'string'; if (!hasAction) { warnings.push({ type: 'other', message: `toolset tool call for tool ${part.toolName} is missing the action`, }); } anthropicContent.push({ type: 'tool_use', id: part.toolCallId, name: hasAction ? action : toolsetName, toolset_name: toolsetName, input: rawInput, ...(caller && { caller }), cache_control: cacheControl, }); break; } anthropicContent.push({ type: 'tool_use', id: part.toolCallId, name: part.toolName, input: toAnthropicToolInput(part.input), ...(caller && { caller }), cache_control: cacheControl, }); break; } case 'tool-result': { const providerToolName = toolNameMapping.toProviderToolName( part.toolName, ); const caller = getAnthropicCaller(part.providerOptions); if (mcpToolUseIds.has(part.toolCallId)) { const output = part.output; if (output.type !== 'json' && output.type !== 'error-json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } anthropicContent.push({ type: 'mcp_tool_result', tool_use_id: part.toolCallId, is_error: output.type === 'error-json', content: output.value as unknown as | string | Array<{ type: 'text'; text: string }>, cache_control: cacheControl, }); } else if (providerToolName === 'code_execution') { const output = part.output; // Handle error types for code_execution tools (e.g., from programmatic tool calling) if ( output.type === 'error-text' || output.type === 'error-json' ) { let errorInfo: { type?: string; errorCode?: string } = {}; try { if (typeof output.value === 'string') { errorInfo = secureJsonParse(output.value); } else if ( typeof output.value === 'object' && output.value !== null ) { errorInfo = output.value as typeof errorInfo; } } catch {} if (errorInfo.type === 'code_execution_tool_result_error') { anthropicContent.push({ type: 'code_execution_tool_result', tool_use_id: part.toolCallId, content: { type: 'code_execution_tool_result_error' as const, error_code: errorInfo.errorCode ?? 'unknown', }, cache_control: cacheControl, }); } else { anthropicContent.push({ type: 'bash_code_execution_tool_result', tool_use_id: part.toolCallId, cache_control: cacheControl, content: { type: 'bash_code_execution_tool_result_error' as const, error_code: errorInfo.errorCode ?? 'unknown', }, }); } break; } if (output.type !== 'json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } if ( output.value == null || typeof output.value !== 'object' || !('type' in output.value) || typeof output.value.type !== 'string' ) { warnings.push({ type: 'other', message: `provider executed tool result output value is not a valid code execution result for tool ${part.toolName}`, }); break; } // to distinguish between code execution 20250522, 20250825, // and encrypted results from implicitly provisioned code // execution for dynamic web tools, // we check the type property in output.value if (output.value.type === 'code_execution_result') { // code execution 20250522 const codeExecutionOutput = await validateTypes({ value: output.value, schema: codeExecution_20250522OutputSchema, }); anthropicContent.push({ type: 'code_execution_tool_result', tool_use_id: part.toolCallId, content: { type: codeExecutionOutput.type, stdout: codeExecutionOutput.stdout, stderr: codeExecutionOutput.stderr, return_code: codeExecutionOutput.return_code, content: codeExecutionOutput.content ?? [], }, cache_control: cacheControl, }); } else if ( output.value.type === 'encrypted_code_execution_result' ) { // code execution 20260120 encrypted result const codeExecutionOutput = await validateTypes({ value: output.value, schema: codeExecution_20260120OutputSchema, }); if ( codeExecutionOutput.type === 'encrypted_code_execution_result' ) { anthropicContent.push({ type: 'code_execution_tool_result', tool_use_id: part.toolCallId, content: { type: codeExecutionOutput.type, encrypted_stdout: codeExecutionOutput.encrypted_stdout, stderr: codeExecutionOutput.stderr, return_code: codeExecutionOutput.return_code, content: codeExecutionOutput.content ?? [], }, cache_control: cacheControl, }); } } else { // code execution 20250825 const codeExecutionOutput = await validateTypes({ value: output.value, schema: codeExecution_20250825OutputSchema, }); if (codeExecutionOutput.type === 'code_execution_result') { anthropicContent.push({ type: 'code_execution_tool_result', tool_use_id: part.toolCallId, content: { type: codeExecutionOutput.type, stdout: codeExecutionOutput.stdout, stderr: codeExecutionOutput.stderr, return_code: codeExecutionOutput.return_code, content: codeExecutionOutput.content ?? [], }, cache_control: cacheControl, }); } else if ( codeExecutionOutput.type === 'bash_code_execution_result' || codeExecutionOutput.type === 'bash_code_execution_tool_result_error' ) { anthropicContent.push({ type: 'bash_code_execution_tool_result', tool_use_id: part.toolCallId, cache_control: cacheControl, // Prompt caching requires stable key ordering: // https://platform.claude.com/docs/en/build-with-claude/prompt-caching#troubleshooting-common-issues content: codeExecutionOutput.type === 'bash_code_execution_result' ? { type: codeExecutionOutput.type, stdout: codeExecutionOutput.stdout, stderr: codeExecutionOutput.stderr, return_code: codeExecutionOutput.return_code, content: codeExecutionOutput.content, } : codeExecutionOutput, }); } else { anthropicContent.push({ type: 'text_editor_code_execution_tool_result', tool_use_id: part.toolCallId, cache_control: cacheControl, content: codeExecutionOutput, }); } } break; } if (providerToolName === 'web_fetch') { const output = part.output; if (output.type === 'error-json') { anthropicContent.push({ type: 'web_fetch_tool_result', tool_use_id: part.toolCallId, content: { type: 'web_fetch_tool_result_error', error_code: extractErrorValue(output.value).errorCode ?? 'unavailable', }, ...(caller && { caller }), cache_control: cacheControl, }); break; } if (output.type !== 'json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } // ideally we'd switch schema based on the tool version, but // all supported versions share an identical output schema, // so we use one here. const webFetchOutput = await validateTypes({ value: output.value, schema: webFetch_20250910OutputSchema, }); anthropicContent.push({ type: 'web_fetch_tool_result', tool_use_id: part.toolCallId, content: { type: 'web_fetch_result', url: webFetchOutput.url, retrieved_at: webFetchOutput.retrievedAt, content: { type: 'document', title: webFetchOutput.content.title, citations: webFetchOutput.content.citations, source: { type: webFetchOutput.content.source.type, media_type: webFetchOutput.content.source.mediaType, data: webFetchOutput.content.source.data, } as Extract< AnthropicWebFetchToolResultContent['content'], { type: 'web_fetch_result' } >['content']['source'], }, }, ...(caller && { caller }), cache_control: cacheControl, }); break; } if (providerToolName === 'web_search') { const output = part.output; if (output.type === 'error-json') { anthropicContent.push({ type: 'web_search_tool_result', tool_use_id: part.toolCallId, content: { type: 'web_search_tool_result_error', error_code: extractErrorValue(output.value).errorCode ?? 'unavailable', }, ...(caller && { caller }), cache_control: cacheControl, }); break; } if (output.type !== 'json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } // ideally we'd switch schema based on the tool version, but // all supported versions share an identical output schema, // so we use one here. const webSearchOutput = await validateTypes({ value: output.value, schema: webSearch_20250305OutputSchema, }); anthropicContent.push({ type: 'web_search_tool_result', tool_use_id: part.toolCallId, content: webSearchOutput.map(result => ({ url: result.url, title: result.title, page_age: result.pageAge, encrypted_content: result.encryptedContent, type: result.type, })), ...(caller && { caller }), cache_control: cacheControl, }); break; } if ( providerToolName === 'tool_search_tool_regex' || providerToolName === 'tool_search_tool_bm25' ) { const output = part.output; if ( output.type === 'error-json' || (output.type === 'json' && output.value != null && typeof output.value === 'object' && !Array.isArray(output.value) && 'type' in output.value && output.value.type === 'tool_search_tool_result_error') ) { anthropicContent.push({ type: 'tool_search_tool_result', tool_use_id: part.toolCallId, content: { type: 'tool_search_tool_result_error', error_code: extractErrorValue(output.value).errorCode ?? 'unavailable', }, cache_control: cacheControl, }); break; } if (output.type !== 'json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } const toolSearchOutput = await validateTypes({ value: output.value, schema: toolSearchOutputSchema, }); // Convert tool references back to API format const toolReferences = toolSearchOutput.map(ref => ({ type: 'tool_reference' as const, tool_name: ref.toolName, })); anthropicContent.push({ type: 'tool_search_tool_result', tool_use_id: part.toolCallId, content: { type: 'tool_search_tool_search_result', tool_references: toolReferences, }, cache_control: cacheControl, }); break; } if (providerToolName === 'advisor') { const output = part.output; if (output.type !== 'json' && output.type !== 'error-json') { warnings.push({ type: 'other', message: `provider executed tool result output type ${output.type} for tool ${part.toolName} is not supported`, }); break; } const advisorOutput = await validateTypes({ value: output.value, schema: advisor_20260301OutputSchema, }); if (advisorOutput.type === 'advisor_result') { anthropicContent.push({ type: 'advisor_tool_result', tool_use_id: part.toolCallId, content: { type: 'advisor_result', text: advisorOutput.text, ...(advisorOutput.stopReason !== undefined && { stop_reason: advisorOutput.stopReason, }), }, cache_control: cacheControl, }); } else if (advisorOutput.type === 'advisor_redacted_result') { anthropicContent.push({ type: 'advisor_tool_result', tool_use_id: part.toolCallId, content: { type: 'advisor_redacted_result', encrypted_content: advisorOutput.encryptedContent, ...(advisorOutput.stopReason !== undefined && { stop_reason: advisorOutput.stopReason, }), }, cache_control: cacheControl, }); } else { anthropicContent.push({ type: 'advisor_tool_result', tool_use_id: part.toolCallId, content: { type: 'advisor_tool_result_error', error_code: advisorOutput.errorCode, }, cache_control: cacheControl, }); } break; } warnings.push({ type: 'other', message: `provider executed tool result for tool ${part.toolName} is not supported`, }); break; } } } } if (anthropicContent.length > 0) { messages.push({ role: 'assistant', content: moveToolUseBlocksToEnd(anthropicContent), }); } break; } default: { const _exhaustiveCheck: never = type; throw new Error(`content type: ${_exhaustiveCheck}`); } } } // Pruning can remove a code execution call while retaining tool calls that // reference it. Check the converted blocks, since unsupported source calls // may also have been omitted during conversion. const codeExecutionToolCallIds = new Set(); for (const message of messages) { if (message.role !== 'assistant') { continue; } for (const part of message.content) { if (part.type === 'server_tool_use' && part.name === 'code_execution') { codeExecutionToolCallIds.add(part.id); } } } // Only normalize history before a subsequent user message. Tool-result // messages do not end a turn: their caller metadata must remain intact so // Anthropic can resume an active code execution. const warnedToolCallIds = new Set(); for (let i = 0; i < lastUserMessageIndex; i++) { const message = messages[i]; if (message.role !== 'assistant') { continue; } for (const part of message.content) { if (!('caller' in part)) { continue; } const caller = part.caller; if ( caller == null || caller.type === 'direct' || codeExecutionToolCallIds.has(caller.tool_id) ) { continue; } const toolCallId = 'id' in part ? part.id : part.tool_use_id; delete part.caller; if (!warnedToolCallIds.has(toolCallId)) { warnedToolCallIds.add(toolCallId); warnings.push({ type: 'other', message: `Omitted caller metadata for tool ${toolCallId} because source code execution tool ${caller.tool_id} is missing from the conversation history.`, }); } } } return { prompt: { system, messages }, betas, }; } type SystemBlock = { type: 'system'; messages: Array; }; type AssistantBlock = { type: 'assistant'; messages: Array; }; type UserBlock = { type: 'user'; messages: Array; }; function groupIntoBlocks( prompt: LanguageModelV4Prompt, ): Array { const blocks: Array = []; let currentBlock: SystemBlock | AssistantBlock | UserBlock | undefined = undefined; for (const message of prompt) { const { role } = message; switch (role) { case 'system': { if (currentBlock?.type !== 'system') { currentBlock = { type: 'system', messages: [] }; blocks.push(currentBlock); } currentBlock.messages.push(message); break; } case 'assistant': { if (currentBlock?.type !== 'assistant') { currentBlock = { type: 'assistant', messages: [] }; blocks.push(currentBlock); } currentBlock.messages.push(message); break; } case 'user': { if (currentBlock?.type !== 'user') { currentBlock = { type: 'user', messages: [] }; blocks.push(currentBlock); } currentBlock.messages.push(message); break; } case 'tool': { if (currentBlock?.type !== 'user') { currentBlock = { type: 'user', messages: [] }; blocks.push(currentBlock); } currentBlock.messages.push(message); break; } default: { const _exhaustiveCheck: never = role; throw new Error(`Unsupported role: ${_exhaustiveCheck}`); } } } return blocks; } function moveToolUseBlocksToEnd( content: AnthropicAssistantMessage['content'], ): AnthropicAssistantMessage['content'] { const result: AnthropicAssistantMessage['content'] = []; let segment: AnthropicAssistantMessage['content'] = []; function flushSegment() { result.push( ...segment.filter(part => part.type !== 'tool_use'), ...segment.filter(part => part.type === 'tool_use'), ); segment = []; } for (const part of content) { if ( part.type === 'thinking' || part.type === 'redacted_thinking' || part.type === 'fallback' ) { flushSegment(); result.push(part); } else { segment.push(part); } } flushSegment(); return result; } /** * Resolves the Anthropic `toolset_name` for a tool call or tool result. The * toolset is identified either through the tools passed to the request or * through the `toolsetName` provider metadata of a previous response. */ function getAnthropicToolsetName({ toolName, providerOptions, toolsetNames, }: { toolName: string; providerOptions: SharedV4ProviderMetadata | undefined; toolsetNames: Record; }): string | undefined { const fromTools = toolsetNames[toolName]; if (fromTools != null) { return fromTools; } const fromMetadata = ( providerOptions?.anthropic as { toolsetName?: unknown } | undefined )?.toolsetName; return typeof fromMetadata === 'string' ? fromMetadata : undefined; } function getAnthropicCaller( providerOptions: SharedV4ProviderMetadata | undefined, ): AnthropicToolCallCaller | undefined { const caller = ( providerOptions?.anthropic as | { caller?: { type: string; toolId?: string } } | undefined )?.caller; if ( (caller?.type === 'code_execution_20250825' || caller?.type === 'code_execution_20260120') && caller.toolId ) { return { type: caller.type, tool_id: caller.toolId, }; } return caller?.type === 'direct' ? { type: 'direct' } : undefined; } // wrap invalid tool call input because Anthropic requires it to be an object function toAnthropicToolInput(input: unknown): JSONObject { return typeof input === 'object' && input !== null && !Array.isArray(input) ? (input as JSONObject) : { rawInvalidInput: input as JSONValue }; }