@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
text/typescript
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({