@ai-sdk/anthropic 3.0.17 → 3.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +57 -30
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +57 -30
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/internal/index.js +56 -29
  7. package/dist/internal/index.js.map +1 -1
  8. package/dist/internal/index.mjs +56 -29
  9. package/dist/internal/index.mjs.map +1 -1
  10. package/package.json +3 -2
  11. package/src/__fixtures__/anthropic-code-execution-20250825.1.chunks.txt +248 -0
  12. package/src/__fixtures__/anthropic-code-execution-20250825.1.json +70 -0
  13. package/src/__fixtures__/anthropic-code-execution-20250825.2.chunks.txt +984 -0
  14. package/src/__fixtures__/anthropic-code-execution-20250825.2.json +111 -0
  15. package/src/__fixtures__/anthropic-code-execution-20250825.pptx-skill.chunks.txt +691 -0
  16. package/src/__fixtures__/anthropic-code-execution-20250825.pptx-skill.json +1801 -0
  17. package/src/__fixtures__/anthropic-json-other-tool.1.chunks.txt +13 -0
  18. package/src/__fixtures__/anthropic-json-other-tool.1.json +26 -0
  19. package/src/__fixtures__/anthropic-json-output-format.1.chunks.txt +120 -0
  20. package/src/__fixtures__/anthropic-json-output-format.1.json +25 -0
  21. package/src/__fixtures__/anthropic-json-tool.1.chunks.txt +9 -0
  22. package/src/__fixtures__/anthropic-json-tool.1.json +37 -0
  23. package/src/__fixtures__/anthropic-json-tool.2.chunks.txt +14 -0
  24. package/src/__fixtures__/anthropic-mcp.1.chunks.txt +17 -0
  25. package/src/__fixtures__/anthropic-mcp.1.json +39 -0
  26. package/src/__fixtures__/anthropic-memory-20250818.1.json +28 -0
  27. package/src/__fixtures__/anthropic-message-delta-input-tokens.chunks.txt +8 -0
  28. package/src/__fixtures__/anthropic-programmatic-tool-calling.1.chunks.txt +278 -0
  29. package/src/__fixtures__/anthropic-programmatic-tool-calling.1.json +106 -0
  30. package/src/__fixtures__/anthropic-tool-no-args.chunks.txt +13 -0
  31. package/src/__fixtures__/anthropic-tool-no-args.json +31 -0
  32. package/src/__fixtures__/anthropic-tool-search-bm25.1.chunks.txt +47 -0
  33. package/src/__fixtures__/anthropic-tool-search-bm25.1.json +67 -0
  34. package/src/__fixtures__/anthropic-tool-search-regex.1.chunks.txt +51 -0
  35. package/src/__fixtures__/anthropic-tool-search-regex.1.json +65 -0
  36. package/src/__fixtures__/anthropic-web-fetch-tool.1.chunks.txt +64 -0
  37. package/src/__fixtures__/anthropic-web-fetch-tool.1.json +54 -0
  38. package/src/__fixtures__/anthropic-web-fetch-tool.2.json +56 -0
  39. package/src/__fixtures__/anthropic-web-fetch-tool.error.json +46 -0
  40. package/src/__fixtures__/anthropic-web-search-tool.1.chunks.txt +120 -0
  41. package/src/__fixtures__/anthropic-web-search-tool.1.json +181 -0
  42. package/src/__snapshots__/anthropic-messages-language-model.test.ts.snap +16719 -0
  43. package/src/anthropic-error.test.ts +42 -0
  44. package/src/anthropic-error.ts +26 -0
  45. package/src/anthropic-message-metadata.ts +105 -0
  46. package/src/anthropic-messages-api.ts +1188 -0
  47. package/src/anthropic-messages-language-model.test.ts +7170 -0
  48. package/src/anthropic-messages-language-model.ts +2067 -0
  49. package/src/anthropic-messages-options.ts +213 -0
  50. package/src/anthropic-prepare-tools.test.ts +1219 -0
  51. package/src/anthropic-prepare-tools.ts +341 -0
  52. package/src/anthropic-provider.test.ts +162 -0
  53. package/src/anthropic-provider.ts +152 -0
  54. package/src/anthropic-tools.ts +182 -0
  55. package/src/convert-anthropic-messages-usage.ts +32 -0
  56. package/src/convert-to-anthropic-messages-prompt.test.ts +2902 -0
  57. package/src/convert-to-anthropic-messages-prompt.ts +1050 -0
  58. package/src/forward-anthropic-container-id-from-last-step.ts +38 -0
  59. package/src/get-cache-control.ts +63 -0
  60. package/src/index.ts +10 -0
  61. package/src/internal/index.ts +4 -0
  62. package/src/map-anthropic-stop-reason.ts +28 -0
  63. package/src/tool/bash_20241022.ts +33 -0
  64. package/src/tool/bash_20250124.ts +33 -0
  65. package/src/tool/code-execution_20250522.ts +61 -0
  66. package/src/tool/code-execution_20250825.ts +281 -0
  67. package/src/tool/computer_20241022.ts +87 -0
  68. package/src/tool/computer_20250124.ts +130 -0
  69. package/src/tool/memory_20250818.ts +62 -0
  70. package/src/tool/text-editor_20241022.ts +63 -0
  71. package/src/tool/text-editor_20250124.ts +63 -0
  72. package/src/tool/text-editor_20250429.ts +64 -0
  73. package/src/tool/text-editor_20250728.ts +80 -0
  74. package/src/tool/tool-search-bm25_20251119.ts +98 -0
  75. package/src/tool/tool-search-regex_20251119.ts +110 -0
  76. package/src/tool/web-fetch-20250910.ts +145 -0
  77. package/src/tool/web-search_20250305.ts +136 -0
  78. package/src/version.ts +6 -0
@@ -0,0 +1,2067 @@
1
+ import {
2
+ APICallError,
3
+ JSONObject,
4
+ LanguageModelV3,
5
+ LanguageModelV3CallOptions,
6
+ LanguageModelV3Content,
7
+ LanguageModelV3FinishReason,
8
+ LanguageModelV3FunctionTool,
9
+ LanguageModelV3GenerateResult,
10
+ LanguageModelV3Prompt,
11
+ LanguageModelV3Source,
12
+ LanguageModelV3StreamPart,
13
+ LanguageModelV3StreamResult,
14
+ LanguageModelV3ToolCall,
15
+ SharedV3ProviderMetadata,
16
+ SharedV3Warning,
17
+ } from '@ai-sdk/provider';
18
+ import {
19
+ combineHeaders,
20
+ createEventSourceResponseHandler,
21
+ createJsonResponseHandler,
22
+ createToolNameMapping,
23
+ FetchFunction,
24
+ generateId,
25
+ InferSchema,
26
+ parseProviderOptions,
27
+ ParseResult,
28
+ postJsonToApi,
29
+ Resolvable,
30
+ resolve,
31
+ } from '@ai-sdk/provider-utils';
32
+ import { anthropicFailedResponseHandler } from './anthropic-error';
33
+ import { AnthropicMessageMetadata } from './anthropic-message-metadata';
34
+ import {
35
+ AnthropicContainer,
36
+ anthropicMessagesChunkSchema,
37
+ anthropicMessagesResponseSchema,
38
+ AnthropicReasoningMetadata,
39
+ AnthropicResponseContextManagement,
40
+ Citation,
41
+ } from './anthropic-messages-api';
42
+ import {
43
+ AnthropicMessagesModelId,
44
+ anthropicProviderOptions,
45
+ } from './anthropic-messages-options';
46
+ import { prepareTools } from './anthropic-prepare-tools';
47
+ import {
48
+ AnthropicMessagesUsage,
49
+ convertAnthropicMessagesUsage,
50
+ } from './convert-anthropic-messages-usage';
51
+ import { convertToAnthropicMessagesPrompt } from './convert-to-anthropic-messages-prompt';
52
+ import { CacheControlValidator } from './get-cache-control';
53
+ import { mapAnthropicStopReason } from './map-anthropic-stop-reason';
54
+
55
+ function createCitationSource(
56
+ citation: Citation,
57
+ citationDocuments: Array<{
58
+ title: string;
59
+ filename?: string;
60
+ mediaType: string;
61
+ }>,
62
+ generateId: () => string,
63
+ ): LanguageModelV3Source | undefined {
64
+ if (citation.type === 'web_search_result_location') {
65
+ return {
66
+ type: 'source' as const,
67
+ sourceType: 'url' as const,
68
+ id: generateId(),
69
+ url: citation.url,
70
+ title: citation.title,
71
+ providerMetadata: {
72
+ anthropic: {
73
+ citedText: citation.cited_text,
74
+ encryptedIndex: citation.encrypted_index,
75
+ },
76
+ } satisfies SharedV3ProviderMetadata,
77
+ };
78
+ }
79
+
80
+ if (citation.type !== 'page_location' && citation.type !== 'char_location') {
81
+ return;
82
+ }
83
+
84
+ const documentInfo = citationDocuments[citation.document_index];
85
+
86
+ if (!documentInfo) {
87
+ return;
88
+ }
89
+
90
+ return {
91
+ type: 'source' as const,
92
+ sourceType: 'document' as const,
93
+ id: generateId(),
94
+ mediaType: documentInfo.mediaType,
95
+ title: citation.document_title ?? documentInfo.title,
96
+ filename: documentInfo.filename,
97
+ providerMetadata: {
98
+ anthropic:
99
+ citation.type === 'page_location'
100
+ ? {
101
+ citedText: citation.cited_text,
102
+ startPageNumber: citation.start_page_number,
103
+ endPageNumber: citation.end_page_number,
104
+ }
105
+ : {
106
+ citedText: citation.cited_text,
107
+ startCharIndex: citation.start_char_index,
108
+ endCharIndex: citation.end_char_index,
109
+ },
110
+ } satisfies SharedV3ProviderMetadata,
111
+ };
112
+ }
113
+
114
+ type AnthropicMessagesConfig = {
115
+ provider: string;
116
+ baseURL: string;
117
+ headers: Resolvable<Record<string, string | undefined>>;
118
+ fetch?: FetchFunction;
119
+ buildRequestUrl?: (baseURL: string, isStreaming: boolean) => string;
120
+ transformRequestBody?: (args: Record<string, any>) => Record<string, any>;
121
+ supportedUrls?: () => LanguageModelV3['supportedUrls'];
122
+ generateId?: () => string;
123
+
124
+ /**
125
+ * When false, the model will use JSON tool fallback for structured outputs.
126
+ */
127
+ supportsNativeStructuredOutput?: boolean;
128
+ };
129
+
130
+ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
131
+ readonly specificationVersion = 'v3';
132
+
133
+ readonly modelId: AnthropicMessagesModelId;
134
+
135
+ private readonly config: AnthropicMessagesConfig;
136
+ private readonly generateId: () => string;
137
+
138
+ constructor(
139
+ modelId: AnthropicMessagesModelId,
140
+ config: AnthropicMessagesConfig,
141
+ ) {
142
+ this.modelId = modelId;
143
+ this.config = config;
144
+ this.generateId = config.generateId ?? generateId;
145
+ }
146
+
147
+ supportsUrl(url: URL): boolean {
148
+ return url.protocol === 'https:';
149
+ }
150
+
151
+ get provider(): string {
152
+ return this.config.provider;
153
+ }
154
+
155
+ get supportedUrls() {
156
+ return this.config.supportedUrls?.() ?? {};
157
+ }
158
+
159
+ private async getArgs({
160
+ userSuppliedBetas,
161
+ prompt,
162
+ maxOutputTokens,
163
+ temperature,
164
+ topP,
165
+ topK,
166
+ frequencyPenalty,
167
+ presencePenalty,
168
+ stopSequences,
169
+ responseFormat,
170
+ seed,
171
+ tools,
172
+ toolChoice,
173
+ providerOptions,
174
+ stream,
175
+ }: LanguageModelV3CallOptions & {
176
+ stream: boolean;
177
+ userSuppliedBetas: Set<string>;
178
+ }) {
179
+ const warnings: SharedV3Warning[] = [];
180
+
181
+ if (frequencyPenalty != null) {
182
+ warnings.push({ type: 'unsupported', feature: 'frequencyPenalty' });
183
+ }
184
+
185
+ if (presencePenalty != null) {
186
+ warnings.push({ type: 'unsupported', feature: 'presencePenalty' });
187
+ }
188
+
189
+ if (seed != null) {
190
+ warnings.push({ type: 'unsupported', feature: 'seed' });
191
+ }
192
+
193
+ if (temperature != null && temperature > 1) {
194
+ warnings.push({
195
+ type: 'unsupported',
196
+ feature: 'temperature',
197
+ details: `${temperature} exceeds anthropic maximum of 1.0. clamped to 1.0`,
198
+ });
199
+ temperature = 1;
200
+ } else if (temperature != null && temperature < 0) {
201
+ warnings.push({
202
+ type: 'unsupported',
203
+ feature: 'temperature',
204
+ details: `${temperature} is below anthropic minimum of 0. clamped to 0`,
205
+ });
206
+ temperature = 0;
207
+ }
208
+
209
+ if (responseFormat?.type === 'json') {
210
+ if (responseFormat.schema == null) {
211
+ warnings.push({
212
+ type: 'unsupported',
213
+ feature: 'responseFormat',
214
+ details:
215
+ 'JSON response format requires a schema. ' +
216
+ 'The response format is ignored.',
217
+ });
218
+ }
219
+ }
220
+
221
+ const anthropicOptions = await parseProviderOptions({
222
+ provider: 'anthropic',
223
+ providerOptions,
224
+ schema: anthropicProviderOptions,
225
+ });
226
+
227
+ const {
228
+ maxOutputTokens: maxOutputTokensForModel,
229
+ supportsStructuredOutput: modelSupportsStructuredOutput,
230
+ isKnownModel,
231
+ } = getModelCapabilities(this.modelId);
232
+
233
+ const supportsStructuredOutput =
234
+ (this.config.supportsNativeStructuredOutput ?? true) &&
235
+ modelSupportsStructuredOutput;
236
+
237
+ const structureOutputMode =
238
+ anthropicOptions?.structuredOutputMode ?? 'auto';
239
+ const useStructuredOutput =
240
+ structureOutputMode === 'outputFormat' ||
241
+ (structureOutputMode === 'auto' && supportsStructuredOutput);
242
+
243
+ const jsonResponseTool: LanguageModelV3FunctionTool | undefined =
244
+ responseFormat?.type === 'json' &&
245
+ responseFormat.schema != null &&
246
+ !useStructuredOutput
247
+ ? {
248
+ type: 'function',
249
+ name: 'json',
250
+ description: 'Respond with a JSON object.',
251
+ inputSchema: responseFormat.schema,
252
+ }
253
+ : undefined;
254
+
255
+ const contextManagement = anthropicOptions?.contextManagement;
256
+
257
+ // Create a shared cache control validator to track breakpoints across tools and messages
258
+ const cacheControlValidator = new CacheControlValidator();
259
+
260
+ const toolNameMapping = createToolNameMapping({
261
+ tools,
262
+ providerToolNames: {
263
+ 'anthropic.code_execution_20250522': 'code_execution',
264
+ 'anthropic.code_execution_20250825': 'code_execution',
265
+ 'anthropic.computer_20241022': 'computer',
266
+ 'anthropic.computer_20250124': 'computer',
267
+ 'anthropic.text_editor_20241022': 'str_replace_editor',
268
+ 'anthropic.text_editor_20250124': 'str_replace_editor',
269
+ 'anthropic.text_editor_20250429': 'str_replace_based_edit_tool',
270
+ 'anthropic.text_editor_20250728': 'str_replace_based_edit_tool',
271
+ 'anthropic.bash_20241022': 'bash',
272
+ 'anthropic.bash_20250124': 'bash',
273
+ 'anthropic.memory_20250818': 'memory',
274
+ 'anthropic.web_search_20250305': 'web_search',
275
+ 'anthropic.web_fetch_20250910': 'web_fetch',
276
+ 'anthropic.tool_search_regex_20251119': 'tool_search_tool_regex',
277
+ 'anthropic.tool_search_bm25_20251119': 'tool_search_tool_bm25',
278
+ },
279
+ });
280
+
281
+ const { prompt: messagesPrompt, betas } =
282
+ await convertToAnthropicMessagesPrompt({
283
+ prompt,
284
+ sendReasoning: anthropicOptions?.sendReasoning ?? true,
285
+ warnings,
286
+ cacheControlValidator,
287
+ toolNameMapping,
288
+ });
289
+
290
+ const isThinking = anthropicOptions?.thinking?.type === 'enabled';
291
+ let thinkingBudget = anthropicOptions?.thinking?.budgetTokens;
292
+
293
+ const maxTokens = maxOutputTokens ?? maxOutputTokensForModel;
294
+
295
+ const baseArgs = {
296
+ // model id:
297
+ model: this.modelId,
298
+
299
+ // standardized settings:
300
+ max_tokens: maxTokens,
301
+ temperature,
302
+ top_k: topK,
303
+ top_p: topP,
304
+ stop_sequences: stopSequences,
305
+
306
+ // provider specific settings:
307
+ ...(isThinking && {
308
+ thinking: { type: 'enabled', budget_tokens: thinkingBudget },
309
+ }),
310
+ ...(anthropicOptions?.effort && {
311
+ output_config: { effort: anthropicOptions.effort },
312
+ }),
313
+
314
+ // structured output:
315
+ ...(useStructuredOutput &&
316
+ responseFormat?.type === 'json' &&
317
+ responseFormat.schema != null && {
318
+ output_format: {
319
+ type: 'json_schema',
320
+ schema: responseFormat.schema,
321
+ },
322
+ }),
323
+
324
+ // mcp servers:
325
+ ...(anthropicOptions?.mcpServers &&
326
+ anthropicOptions.mcpServers.length > 0 && {
327
+ mcp_servers: anthropicOptions.mcpServers.map(server => ({
328
+ type: server.type,
329
+ name: server.name,
330
+ url: server.url,
331
+ authorization_token: server.authorizationToken,
332
+ tool_configuration: server.toolConfiguration
333
+ ? {
334
+ allowed_tools: server.toolConfiguration.allowedTools,
335
+ enabled: server.toolConfiguration.enabled,
336
+ }
337
+ : undefined,
338
+ })),
339
+ }),
340
+
341
+ // container: For programmatic tool calling (just an ID string) or agent skills (object with id and skills)
342
+ ...(anthropicOptions?.container && {
343
+ container:
344
+ anthropicOptions.container.skills &&
345
+ anthropicOptions.container.skills.length > 0
346
+ ? // Object format when skills are provided (agent skills feature)
347
+ ({
348
+ id: anthropicOptions.container.id,
349
+ skills: anthropicOptions.container.skills.map(skill => ({
350
+ type: skill.type,
351
+ skill_id: skill.skillId,
352
+ version: skill.version,
353
+ })),
354
+ } satisfies AnthropicContainer)
355
+ : // String format for container ID only (programmatic tool calling)
356
+ anthropicOptions.container.id,
357
+ }),
358
+
359
+ // prompt:
360
+ system: messagesPrompt.system,
361
+ messages: messagesPrompt.messages,
362
+
363
+ ...(contextManagement && {
364
+ context_management: {
365
+ edits: contextManagement.edits
366
+ .map(edit => {
367
+ const strategy = edit.type;
368
+ switch (strategy) {
369
+ case 'clear_tool_uses_20250919':
370
+ return {
371
+ type: edit.type,
372
+ ...(edit.trigger !== undefined && {
373
+ trigger: edit.trigger,
374
+ }),
375
+ ...(edit.keep !== undefined && { keep: edit.keep }),
376
+ ...(edit.clearAtLeast !== undefined && {
377
+ clear_at_least: edit.clearAtLeast,
378
+ }),
379
+ ...(edit.clearToolInputs !== undefined && {
380
+ clear_tool_inputs: edit.clearToolInputs,
381
+ }),
382
+ ...(edit.excludeTools !== undefined && {
383
+ exclude_tools: edit.excludeTools,
384
+ }),
385
+ };
386
+
387
+ case 'clear_thinking_20251015':
388
+ return {
389
+ type: edit.type,
390
+ ...(edit.keep !== undefined && { keep: edit.keep }),
391
+ };
392
+
393
+ default:
394
+ warnings.push({
395
+ type: 'other',
396
+ message: `Unknown context management strategy: ${strategy}`,
397
+ });
398
+ return undefined;
399
+ }
400
+ })
401
+ .filter(edit => edit !== undefined),
402
+ },
403
+ }),
404
+ };
405
+
406
+ if (isThinking) {
407
+ if (thinkingBudget == null) {
408
+ warnings.push({
409
+ type: 'compatibility',
410
+ feature: 'extended thinking',
411
+ details:
412
+ 'thinking budget is required when thinking is enabled. using default budget of 1024 tokens.',
413
+ });
414
+
415
+ baseArgs.thinking = {
416
+ type: 'enabled',
417
+ budget_tokens: 1024,
418
+ };
419
+
420
+ thinkingBudget = 1024;
421
+ }
422
+
423
+ if (baseArgs.temperature != null) {
424
+ baseArgs.temperature = undefined;
425
+ warnings.push({
426
+ type: 'unsupported',
427
+ feature: 'temperature',
428
+ details: 'temperature is not supported when thinking is enabled',
429
+ });
430
+ }
431
+
432
+ if (topK != null) {
433
+ baseArgs.top_k = undefined;
434
+ warnings.push({
435
+ type: 'unsupported',
436
+ feature: 'topK',
437
+ details: 'topK is not supported when thinking is enabled',
438
+ });
439
+ }
440
+
441
+ if (topP != null) {
442
+ baseArgs.top_p = undefined;
443
+ warnings.push({
444
+ type: 'unsupported',
445
+ feature: 'topP',
446
+ details: 'topP is not supported when thinking is enabled',
447
+ });
448
+ }
449
+
450
+ // adjust max tokens to account for thinking:
451
+ baseArgs.max_tokens = maxTokens + (thinkingBudget ?? 0);
452
+ } else {
453
+ // Only check temperature/topP mutual exclusivity when thinking is not enabled
454
+ if (topP != null && temperature != null) {
455
+ warnings.push({
456
+ type: 'unsupported',
457
+ feature: 'topP',
458
+ details: `topP is not supported when temperature is set. topP is ignored.`,
459
+ });
460
+ baseArgs.top_p = undefined;
461
+ }
462
+ }
463
+
464
+ // limit to max output tokens for known models to enable model switching without breaking it:
465
+ if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) {
466
+ // only warn if max output tokens is provided as input:
467
+ if (maxOutputTokens != null) {
468
+ warnings.push({
469
+ type: 'unsupported',
470
+ feature: 'maxOutputTokens',
471
+ details:
472
+ `${baseArgs.max_tokens} (maxOutputTokens + thinkingBudget) is greater than ${this.modelId} ${maxOutputTokensForModel} max output tokens. ` +
473
+ `The max output tokens have been limited to ${maxOutputTokensForModel}.`,
474
+ });
475
+ }
476
+ baseArgs.max_tokens = maxOutputTokensForModel;
477
+ }
478
+
479
+ if (
480
+ anthropicOptions?.mcpServers &&
481
+ anthropicOptions.mcpServers.length > 0
482
+ ) {
483
+ betas.add('mcp-client-2025-04-04');
484
+ }
485
+
486
+ if (contextManagement) {
487
+ betas.add('context-management-2025-06-27');
488
+ }
489
+
490
+ if (
491
+ anthropicOptions?.container &&
492
+ anthropicOptions.container.skills &&
493
+ anthropicOptions.container.skills.length > 0
494
+ ) {
495
+ betas.add('code-execution-2025-08-25');
496
+ betas.add('skills-2025-10-02');
497
+ betas.add('files-api-2025-04-14');
498
+
499
+ if (
500
+ !tools?.some(
501
+ tool =>
502
+ tool.type === 'provider' &&
503
+ tool.id === 'anthropic.code_execution_20250825',
504
+ )
505
+ ) {
506
+ warnings.push({
507
+ type: 'other',
508
+ message: 'code execution tool is required when using skills',
509
+ });
510
+ }
511
+ }
512
+
513
+ if (anthropicOptions?.effort) {
514
+ betas.add('effort-2025-11-24');
515
+ }
516
+
517
+ // only when streaming: enable fine-grained tool streaming
518
+ if (stream && (anthropicOptions?.toolStreaming ?? true)) {
519
+ betas.add('fine-grained-tool-streaming-2025-05-14');
520
+ }
521
+
522
+ // structured output:
523
+ // Only pass beta when actually using native output_format
524
+ const usingNativeOutputFormat =
525
+ useStructuredOutput &&
526
+ responseFormat?.type === 'json' &&
527
+ responseFormat.schema != null;
528
+
529
+ if (usingNativeOutputFormat) {
530
+ betas.add('structured-outputs-2025-11-13');
531
+ }
532
+
533
+ const {
534
+ tools: anthropicTools,
535
+ toolChoice: anthropicToolChoice,
536
+ toolWarnings,
537
+ betas: toolsBetas,
538
+ } = await prepareTools(
539
+ jsonResponseTool != null
540
+ ? {
541
+ tools: [...(tools ?? []), jsonResponseTool],
542
+ toolChoice: { type: 'required' },
543
+ disableParallelToolUse: true,
544
+ cacheControlValidator,
545
+ supportsStructuredOutput: false,
546
+ }
547
+ : {
548
+ tools: tools ?? [],
549
+ toolChoice,
550
+ disableParallelToolUse: anthropicOptions?.disableParallelToolUse,
551
+ cacheControlValidator,
552
+ supportsStructuredOutput,
553
+ },
554
+ );
555
+
556
+ // Extract cache control warnings once at the end
557
+ const cacheWarnings = cacheControlValidator.getWarnings();
558
+
559
+ return {
560
+ args: {
561
+ ...baseArgs,
562
+ tools: anthropicTools,
563
+ tool_choice: anthropicToolChoice,
564
+ stream: stream === true ? true : undefined, // do not send when not streaming
565
+ },
566
+ warnings: [...warnings, ...toolWarnings, ...cacheWarnings],
567
+ betas: new Set([...betas, ...toolsBetas, ...userSuppliedBetas]),
568
+ usesJsonResponseTool: jsonResponseTool != null,
569
+ toolNameMapping,
570
+ };
571
+ }
572
+
573
+ private async getHeaders({
574
+ betas,
575
+ headers,
576
+ }: {
577
+ betas: Set<string>;
578
+ headers: Record<string, string | undefined> | undefined;
579
+ }) {
580
+ return combineHeaders(
581
+ await resolve(this.config.headers),
582
+ headers,
583
+ betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {},
584
+ );
585
+ }
586
+
587
+ private async getBetasFromHeaders(
588
+ requestHeaders: Record<string, string | undefined> | undefined,
589
+ ) {
590
+ const configHeaders = await resolve(this.config.headers);
591
+
592
+ const configBetaHeader = configHeaders['anthropic-beta'] ?? '';
593
+ const requestBetaHeader = requestHeaders?.['anthropic-beta'] ?? '';
594
+
595
+ return new Set(
596
+ [
597
+ ...configBetaHeader.toLowerCase().split(','),
598
+ ...requestBetaHeader.toLowerCase().split(','),
599
+ ]
600
+ .map(beta => beta.trim())
601
+ .filter(beta => beta !== ''),
602
+ );
603
+ }
604
+
605
+ private buildRequestUrl(isStreaming: boolean): string {
606
+ return (
607
+ this.config.buildRequestUrl?.(this.config.baseURL, isStreaming) ??
608
+ `${this.config.baseURL}/messages`
609
+ );
610
+ }
611
+
612
+ private transformRequestBody(args: Record<string, any>): Record<string, any> {
613
+ return this.config.transformRequestBody?.(args) ?? args;
614
+ }
615
+
616
+ private extractCitationDocuments(prompt: LanguageModelV3Prompt): Array<{
617
+ title: string;
618
+ filename?: string;
619
+ mediaType: string;
620
+ }> {
621
+ const isCitationPart = (part: {
622
+ type: string;
623
+ mediaType?: string;
624
+ providerOptions?: { anthropic?: { citations?: { enabled?: boolean } } };
625
+ }) => {
626
+ if (part.type !== 'file') {
627
+ return false;
628
+ }
629
+
630
+ if (
631
+ part.mediaType !== 'application/pdf' &&
632
+ part.mediaType !== 'text/plain'
633
+ ) {
634
+ return false;
635
+ }
636
+
637
+ const anthropic = part.providerOptions?.anthropic;
638
+ const citationsConfig = anthropic?.citations as
639
+ | { enabled?: boolean }
640
+ | undefined;
641
+ return citationsConfig?.enabled ?? false;
642
+ };
643
+
644
+ return prompt
645
+ .filter(message => message.role === 'user')
646
+ .flatMap(message => message.content)
647
+ .filter(isCitationPart)
648
+ .map(part => {
649
+ // TypeScript knows this is a file part due to our filter
650
+ const filePart = part as Extract<typeof part, { type: 'file' }>;
651
+ return {
652
+ title: filePart.filename ?? 'Untitled Document',
653
+ filename: filePart.filename,
654
+ mediaType: filePart.mediaType,
655
+ };
656
+ });
657
+ }
658
+
659
+ async doGenerate(
660
+ options: LanguageModelV3CallOptions,
661
+ ): Promise<LanguageModelV3GenerateResult> {
662
+ const { args, warnings, betas, usesJsonResponseTool, toolNameMapping } =
663
+ await this.getArgs({
664
+ ...options,
665
+ stream: false,
666
+ userSuppliedBetas: await this.getBetasFromHeaders(options.headers),
667
+ });
668
+
669
+ // Extract citation documents for response processing
670
+ const citationDocuments = [
671
+ ...this.extractCitationDocuments(options.prompt),
672
+ ];
673
+
674
+ const {
675
+ responseHeaders,
676
+ value: response,
677
+ rawValue: rawResponse,
678
+ } = await postJsonToApi({
679
+ url: this.buildRequestUrl(false),
680
+ headers: await this.getHeaders({ betas, headers: options.headers }),
681
+ body: this.transformRequestBody(args),
682
+ failedResponseHandler: anthropicFailedResponseHandler,
683
+ successfulResponseHandler: createJsonResponseHandler(
684
+ anthropicMessagesResponseSchema,
685
+ ),
686
+ abortSignal: options.abortSignal,
687
+ fetch: this.config.fetch,
688
+ });
689
+
690
+ const content: Array<LanguageModelV3Content> = [];
691
+ const mcpToolCalls: Record<string, LanguageModelV3ToolCall> = {};
692
+ const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name
693
+ let isJsonResponseFromTool = false;
694
+
695
+ // map response content to content array
696
+ for (const part of response.content) {
697
+ switch (part.type) {
698
+ case 'text': {
699
+ if (!usesJsonResponseTool) {
700
+ content.push({ type: 'text', text: part.text });
701
+
702
+ // Process citations if present
703
+ if (part.citations) {
704
+ for (const citation of part.citations) {
705
+ const source = createCitationSource(
706
+ citation,
707
+ citationDocuments,
708
+ this.generateId,
709
+ );
710
+
711
+ if (source) {
712
+ content.push(source);
713
+ }
714
+ }
715
+ }
716
+ }
717
+ break;
718
+ }
719
+ case 'thinking': {
720
+ content.push({
721
+ type: 'reasoning',
722
+ text: part.thinking,
723
+ providerMetadata: {
724
+ anthropic: {
725
+ signature: part.signature,
726
+ } satisfies AnthropicReasoningMetadata,
727
+ },
728
+ });
729
+ break;
730
+ }
731
+ case 'redacted_thinking': {
732
+ content.push({
733
+ type: 'reasoning',
734
+ text: '',
735
+ providerMetadata: {
736
+ anthropic: {
737
+ redactedData: part.data,
738
+ } satisfies AnthropicReasoningMetadata,
739
+ },
740
+ });
741
+ break;
742
+ }
743
+ case 'tool_use': {
744
+ const isJsonResponseTool =
745
+ usesJsonResponseTool && part.name === 'json';
746
+
747
+ if (isJsonResponseTool) {
748
+ isJsonResponseFromTool = true;
749
+
750
+ // when a json response tool is used, the tool call becomes the text:
751
+ content.push({
752
+ type: 'text',
753
+ text: JSON.stringify(part.input),
754
+ });
755
+ } else {
756
+ const caller = part.caller;
757
+ const callerInfo = caller
758
+ ? {
759
+ type: caller.type,
760
+ toolId: 'tool_id' in caller ? caller.tool_id : undefined,
761
+ }
762
+ : undefined;
763
+
764
+ content.push({
765
+ type: 'tool-call',
766
+ toolCallId: part.id,
767
+ toolName: part.name,
768
+ input: JSON.stringify(part.input),
769
+ ...(callerInfo && {
770
+ providerMetadata: {
771
+ anthropic: {
772
+ caller: callerInfo,
773
+ },
774
+ },
775
+ }),
776
+ });
777
+ }
778
+
779
+ break;
780
+ }
781
+ case 'server_tool_use': {
782
+ // code execution 20250825 needs mapping:
783
+ if (
784
+ part.name === 'text_editor_code_execution' ||
785
+ part.name === 'bash_code_execution'
786
+ ) {
787
+ content.push({
788
+ type: 'tool-call',
789
+ toolCallId: part.id,
790
+ toolName: toolNameMapping.toCustomToolName('code_execution'),
791
+ input: JSON.stringify({ type: part.name, ...part.input }),
792
+ providerExecuted: true,
793
+ });
794
+ } else if (
795
+ part.name === 'web_search' ||
796
+ part.name === 'code_execution' ||
797
+ part.name === 'web_fetch'
798
+ ) {
799
+ // For code_execution, inject 'programmatic-tool-call' type when input has { code } format
800
+ const inputToSerialize =
801
+ part.name === 'code_execution' &&
802
+ part.input != null &&
803
+ typeof part.input === 'object' &&
804
+ 'code' in part.input &&
805
+ !('type' in part.input)
806
+ ? { type: 'programmatic-tool-call', ...part.input }
807
+ : part.input;
808
+
809
+ content.push({
810
+ type: 'tool-call',
811
+ toolCallId: part.id,
812
+ toolName: toolNameMapping.toCustomToolName(part.name),
813
+ input: JSON.stringify(inputToSerialize),
814
+ providerExecuted: true,
815
+ });
816
+ } else if (
817
+ part.name === 'tool_search_tool_regex' ||
818
+ part.name === 'tool_search_tool_bm25'
819
+ ) {
820
+ serverToolCalls[part.id] = part.name;
821
+ content.push({
822
+ type: 'tool-call',
823
+ toolCallId: part.id,
824
+ toolName: toolNameMapping.toCustomToolName(part.name),
825
+ input: JSON.stringify(part.input),
826
+ providerExecuted: true,
827
+ });
828
+ }
829
+
830
+ break;
831
+ }
832
+ case 'mcp_tool_use': {
833
+ mcpToolCalls[part.id] = {
834
+ type: 'tool-call',
835
+ toolCallId: part.id,
836
+ toolName: part.name,
837
+ input: JSON.stringify(part.input),
838
+ providerExecuted: true,
839
+ dynamic: true,
840
+ providerMetadata: {
841
+ anthropic: {
842
+ type: 'mcp-tool-use',
843
+ serverName: part.server_name,
844
+ },
845
+ },
846
+ };
847
+ content.push(mcpToolCalls[part.id]);
848
+ break;
849
+ }
850
+ case 'mcp_tool_result': {
851
+ content.push({
852
+ type: 'tool-result',
853
+ toolCallId: part.tool_use_id,
854
+ toolName: mcpToolCalls[part.tool_use_id].toolName,
855
+ isError: part.is_error,
856
+ result: part.content,
857
+ dynamic: true,
858
+ providerMetadata: mcpToolCalls[part.tool_use_id].providerMetadata,
859
+ });
860
+ break;
861
+ }
862
+ case 'web_fetch_tool_result': {
863
+ if (part.content.type === 'web_fetch_result') {
864
+ citationDocuments.push({
865
+ title: part.content.content.title ?? part.content.url,
866
+ mediaType: part.content.content.source.media_type,
867
+ });
868
+ content.push({
869
+ type: 'tool-result',
870
+ toolCallId: part.tool_use_id,
871
+ toolName: toolNameMapping.toCustomToolName('web_fetch'),
872
+ result: {
873
+ type: 'web_fetch_result',
874
+ url: part.content.url,
875
+ retrievedAt: part.content.retrieved_at,
876
+ content: {
877
+ type: part.content.content.type,
878
+ title: part.content.content.title,
879
+ citations: part.content.content.citations,
880
+ source: {
881
+ type: part.content.content.source.type,
882
+ mediaType: part.content.content.source.media_type,
883
+ data: part.content.content.source.data,
884
+ },
885
+ },
886
+ },
887
+ });
888
+ } else if (part.content.type === 'web_fetch_tool_result_error') {
889
+ content.push({
890
+ type: 'tool-result',
891
+ toolCallId: part.tool_use_id,
892
+ toolName: toolNameMapping.toCustomToolName('web_fetch'),
893
+ isError: true,
894
+ result: {
895
+ type: 'web_fetch_tool_result_error',
896
+ errorCode: part.content.error_code,
897
+ },
898
+ });
899
+ }
900
+ break;
901
+ }
902
+ case 'web_search_tool_result': {
903
+ if (Array.isArray(part.content)) {
904
+ content.push({
905
+ type: 'tool-result',
906
+ toolCallId: part.tool_use_id,
907
+ toolName: toolNameMapping.toCustomToolName('web_search'),
908
+ result: part.content.map(result => ({
909
+ url: result.url,
910
+ title: result.title,
911
+ pageAge: result.page_age ?? null,
912
+ encryptedContent: result.encrypted_content,
913
+ type: result.type,
914
+ })),
915
+ });
916
+
917
+ for (const result of part.content) {
918
+ content.push({
919
+ type: 'source',
920
+ sourceType: 'url',
921
+ id: this.generateId(),
922
+ url: result.url,
923
+ title: result.title,
924
+ providerMetadata: {
925
+ anthropic: {
926
+ pageAge: result.page_age ?? null,
927
+ },
928
+ },
929
+ });
930
+ }
931
+ } else {
932
+ content.push({
933
+ type: 'tool-result',
934
+ toolCallId: part.tool_use_id,
935
+ toolName: toolNameMapping.toCustomToolName('web_search'),
936
+ isError: true,
937
+ result: {
938
+ type: 'web_search_tool_result_error',
939
+ errorCode: part.content.error_code,
940
+ },
941
+ });
942
+ }
943
+ break;
944
+ }
945
+
946
+ // code execution 20250522:
947
+ case 'code_execution_tool_result': {
948
+ if (part.content.type === 'code_execution_result') {
949
+ content.push({
950
+ type: 'tool-result',
951
+ toolCallId: part.tool_use_id,
952
+ toolName: toolNameMapping.toCustomToolName('code_execution'),
953
+ result: {
954
+ type: part.content.type,
955
+ stdout: part.content.stdout,
956
+ stderr: part.content.stderr,
957
+ return_code: part.content.return_code,
958
+ content: part.content.content ?? [],
959
+ },
960
+ });
961
+ } else if (part.content.type === 'code_execution_tool_result_error') {
962
+ content.push({
963
+ type: 'tool-result',
964
+ toolCallId: part.tool_use_id,
965
+ toolName: toolNameMapping.toCustomToolName('code_execution'),
966
+ isError: true,
967
+ result: {
968
+ type: 'code_execution_tool_result_error',
969
+ errorCode: part.content.error_code,
970
+ },
971
+ });
972
+ }
973
+ break;
974
+ }
975
+
976
+ // code execution 20250825:
977
+ case 'bash_code_execution_tool_result':
978
+ case 'text_editor_code_execution_tool_result': {
979
+ content.push({
980
+ type: 'tool-result',
981
+ toolCallId: part.tool_use_id,
982
+ toolName: toolNameMapping.toCustomToolName('code_execution'),
983
+ result: part.content,
984
+ });
985
+ break;
986
+ }
987
+
988
+ // tool search tool results:
989
+ case 'tool_search_tool_result': {
990
+ const providerToolName =
991
+ serverToolCalls[part.tool_use_id] ?? 'tool_search_tool_regex';
992
+ if (part.content.type === 'tool_search_tool_search_result') {
993
+ content.push({
994
+ type: 'tool-result',
995
+ toolCallId: part.tool_use_id,
996
+ toolName: toolNameMapping.toCustomToolName(providerToolName),
997
+ result: part.content.tool_references.map(ref => ({
998
+ type: ref.type,
999
+ toolName: ref.tool_name,
1000
+ })),
1001
+ });
1002
+ } else {
1003
+ content.push({
1004
+ type: 'tool-result',
1005
+ toolCallId: part.tool_use_id,
1006
+ toolName: toolNameMapping.toCustomToolName(providerToolName),
1007
+ isError: true,
1008
+ result: {
1009
+ type: 'tool_search_tool_result_error',
1010
+ errorCode: part.content.error_code,
1011
+ },
1012
+ });
1013
+ }
1014
+ break;
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ return {
1020
+ content,
1021
+ finishReason: {
1022
+ unified: mapAnthropicStopReason({
1023
+ finishReason: response.stop_reason,
1024
+ isJsonResponseFromTool,
1025
+ }),
1026
+ raw: response.stop_reason ?? undefined,
1027
+ },
1028
+ usage: convertAnthropicMessagesUsage(response.usage),
1029
+ request: { body: args },
1030
+ response: {
1031
+ id: response.id ?? undefined,
1032
+ modelId: response.model ?? undefined,
1033
+ headers: responseHeaders,
1034
+ body: rawResponse,
1035
+ },
1036
+ warnings,
1037
+ providerMetadata: {
1038
+ anthropic: {
1039
+ usage: response.usage as JSONObject,
1040
+ cacheCreationInputTokens:
1041
+ response.usage.cache_creation_input_tokens ?? null,
1042
+ stopSequence: response.stop_sequence ?? null,
1043
+ container: response.container
1044
+ ? {
1045
+ expiresAt: response.container.expires_at,
1046
+ id: response.container.id,
1047
+ skills:
1048
+ response.container.skills?.map(skill => ({
1049
+ type: skill.type,
1050
+ skillId: skill.skill_id,
1051
+ version: skill.version,
1052
+ })) ?? null,
1053
+ }
1054
+ : null,
1055
+ contextManagement:
1056
+ mapAnthropicResponseContextManagement(
1057
+ response.context_management,
1058
+ ) ?? null,
1059
+ } satisfies AnthropicMessageMetadata,
1060
+ },
1061
+ };
1062
+ }
1063
+
1064
+ async doStream(
1065
+ options: LanguageModelV3CallOptions,
1066
+ ): Promise<LanguageModelV3StreamResult> {
1067
+ const {
1068
+ args: body,
1069
+ warnings,
1070
+ betas,
1071
+ usesJsonResponseTool,
1072
+ toolNameMapping,
1073
+ } = await this.getArgs({
1074
+ ...options,
1075
+ stream: true,
1076
+ userSuppliedBetas: await this.getBetasFromHeaders(options.headers),
1077
+ });
1078
+
1079
+ // Extract citation documents for response processing
1080
+ const citationDocuments = [
1081
+ ...this.extractCitationDocuments(options.prompt),
1082
+ ];
1083
+
1084
+ const url = this.buildRequestUrl(true);
1085
+ const { responseHeaders, value: response } = await postJsonToApi({
1086
+ url,
1087
+ headers: await this.getHeaders({ betas, headers: options.headers }),
1088
+ body: this.transformRequestBody(body),
1089
+ failedResponseHandler: anthropicFailedResponseHandler,
1090
+ successfulResponseHandler: createEventSourceResponseHandler(
1091
+ anthropicMessagesChunkSchema,
1092
+ ),
1093
+ abortSignal: options.abortSignal,
1094
+ fetch: this.config.fetch,
1095
+ });
1096
+
1097
+ let finishReason: LanguageModelV3FinishReason = {
1098
+ unified: 'other',
1099
+ raw: undefined,
1100
+ };
1101
+ const usage: AnthropicMessagesUsage = {
1102
+ input_tokens: 0,
1103
+ output_tokens: 0,
1104
+ cache_creation_input_tokens: 0,
1105
+ cache_read_input_tokens: 0,
1106
+ };
1107
+
1108
+ const contentBlocks: Record<
1109
+ number,
1110
+ | {
1111
+ type: 'tool-call';
1112
+ toolCallId: string;
1113
+ toolName: string;
1114
+ input: string;
1115
+ providerExecuted?: boolean;
1116
+ firstDelta: boolean;
1117
+ providerToolName?: string;
1118
+ caller?: {
1119
+ type: 'code_execution_20250825' | 'direct';
1120
+ toolId?: string;
1121
+ };
1122
+ }
1123
+ | { type: 'text' | 'reasoning' }
1124
+ > = {};
1125
+ const mcpToolCalls: Record<string, LanguageModelV3ToolCall> = {};
1126
+ const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name
1127
+
1128
+ let contextManagement:
1129
+ | AnthropicMessageMetadata['contextManagement']
1130
+ | null = null;
1131
+ let rawUsage: JSONObject | undefined = undefined;
1132
+ let cacheCreationInputTokens: number | null = null;
1133
+ let stopSequence: string | null = null;
1134
+ let container: AnthropicMessageMetadata['container'] | null = null;
1135
+ let isJsonResponseFromTool = false;
1136
+
1137
+ let blockType:
1138
+ | 'text'
1139
+ | 'thinking'
1140
+ | 'tool_use'
1141
+ | 'redacted_thinking'
1142
+ | 'server_tool_use'
1143
+ | 'web_fetch_tool_result'
1144
+ | 'web_search_tool_result'
1145
+ | 'code_execution_tool_result'
1146
+ | 'text_editor_code_execution_tool_result'
1147
+ | 'bash_code_execution_tool_result'
1148
+ | 'tool_search_tool_result'
1149
+ | 'mcp_tool_use'
1150
+ | 'mcp_tool_result'
1151
+ | undefined = undefined;
1152
+
1153
+ const generateId = this.generateId;
1154
+
1155
+ const transformedStream = response.pipeThrough(
1156
+ new TransformStream<
1157
+ ParseResult<InferSchema<typeof anthropicMessagesChunkSchema>>,
1158
+ LanguageModelV3StreamPart
1159
+ >({
1160
+ start(controller) {
1161
+ controller.enqueue({ type: 'stream-start', warnings });
1162
+ },
1163
+
1164
+ transform(chunk, controller) {
1165
+ if (options.includeRawChunks) {
1166
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
1167
+ }
1168
+
1169
+ if (!chunk.success) {
1170
+ controller.enqueue({ type: 'error', error: chunk.error });
1171
+ return;
1172
+ }
1173
+
1174
+ const value = chunk.value;
1175
+
1176
+ switch (value.type) {
1177
+ case 'ping': {
1178
+ return; // ignored
1179
+ }
1180
+
1181
+ case 'content_block_start': {
1182
+ const part = value.content_block;
1183
+ const contentBlockType = part.type;
1184
+ blockType = contentBlockType;
1185
+
1186
+ switch (contentBlockType) {
1187
+ case 'text': {
1188
+ // when a json response tool is used, the tool call is returned as text,
1189
+ // so we ignore the text content:
1190
+ if (usesJsonResponseTool) {
1191
+ return;
1192
+ }
1193
+
1194
+ contentBlocks[value.index] = { type: 'text' };
1195
+ controller.enqueue({
1196
+ type: 'text-start',
1197
+ id: String(value.index),
1198
+ });
1199
+ return;
1200
+ }
1201
+
1202
+ case 'thinking': {
1203
+ contentBlocks[value.index] = { type: 'reasoning' };
1204
+ controller.enqueue({
1205
+ type: 'reasoning-start',
1206
+ id: String(value.index),
1207
+ });
1208
+ return;
1209
+ }
1210
+
1211
+ case 'redacted_thinking': {
1212
+ contentBlocks[value.index] = { type: 'reasoning' };
1213
+ controller.enqueue({
1214
+ type: 'reasoning-start',
1215
+ id: String(value.index),
1216
+ providerMetadata: {
1217
+ anthropic: {
1218
+ redactedData: part.data,
1219
+ } satisfies AnthropicReasoningMetadata,
1220
+ },
1221
+ });
1222
+ return;
1223
+ }
1224
+
1225
+ case 'tool_use': {
1226
+ const isJsonResponseTool =
1227
+ usesJsonResponseTool && part.name === 'json';
1228
+
1229
+ if (isJsonResponseTool) {
1230
+ isJsonResponseFromTool = true;
1231
+
1232
+ contentBlocks[value.index] = { type: 'text' };
1233
+
1234
+ controller.enqueue({
1235
+ type: 'text-start',
1236
+ id: String(value.index),
1237
+ });
1238
+ } else {
1239
+ // Extract caller info for type-safe access
1240
+ const caller = part.caller;
1241
+ const callerInfo = caller
1242
+ ? {
1243
+ type: caller.type,
1244
+ toolId:
1245
+ 'tool_id' in caller ? caller.tool_id : undefined,
1246
+ }
1247
+ : undefined;
1248
+
1249
+ // Programmatic tool calling: for deferred tool calls from code_execution,
1250
+ // input may be present directly in content_block_start.
1251
+ // Only use if non-empty (empty {} means input comes via deltas)
1252
+ const hasNonEmptyInput =
1253
+ part.input && Object.keys(part.input).length > 0;
1254
+ const initialInput = hasNonEmptyInput
1255
+ ? JSON.stringify(part.input)
1256
+ : '';
1257
+
1258
+ contentBlocks[value.index] = {
1259
+ type: 'tool-call',
1260
+ toolCallId: part.id,
1261
+ toolName: part.name,
1262
+ input: initialInput,
1263
+ firstDelta: initialInput.length === 0,
1264
+ ...(callerInfo && { caller: callerInfo }),
1265
+ };
1266
+
1267
+ controller.enqueue({
1268
+ type: 'tool-input-start',
1269
+ id: part.id,
1270
+ toolName: part.name,
1271
+ });
1272
+ }
1273
+ return;
1274
+ }
1275
+
1276
+ case 'server_tool_use': {
1277
+ if (
1278
+ [
1279
+ 'web_fetch',
1280
+ 'web_search',
1281
+ // code execution 20250825:
1282
+ 'code_execution',
1283
+ // code execution 20250825 text editor:
1284
+ 'text_editor_code_execution',
1285
+ // code execution 20250825 bash:
1286
+ 'bash_code_execution',
1287
+ ].includes(part.name)
1288
+ ) {
1289
+ // map tool names for the code execution 20250825 tool:
1290
+ const providerToolName =
1291
+ part.name === 'text_editor_code_execution' ||
1292
+ part.name === 'bash_code_execution'
1293
+ ? 'code_execution'
1294
+ : part.name;
1295
+
1296
+ const customToolName =
1297
+ toolNameMapping.toCustomToolName(providerToolName);
1298
+
1299
+ contentBlocks[value.index] = {
1300
+ type: 'tool-call',
1301
+ toolCallId: part.id,
1302
+ toolName: customToolName,
1303
+ input: '',
1304
+ providerExecuted: true,
1305
+ firstDelta: true,
1306
+ providerToolName: part.name,
1307
+ };
1308
+
1309
+ controller.enqueue({
1310
+ type: 'tool-input-start',
1311
+ id: part.id,
1312
+ toolName: customToolName,
1313
+ providerExecuted: true,
1314
+ });
1315
+ } else if (
1316
+ part.name === 'tool_search_tool_regex' ||
1317
+ part.name === 'tool_search_tool_bm25'
1318
+ ) {
1319
+ serverToolCalls[part.id] = part.name;
1320
+ const customToolName = toolNameMapping.toCustomToolName(
1321
+ part.name,
1322
+ );
1323
+
1324
+ contentBlocks[value.index] = {
1325
+ type: 'tool-call',
1326
+ toolCallId: part.id,
1327
+ toolName: customToolName,
1328
+ input: '',
1329
+ providerExecuted: true,
1330
+ firstDelta: true,
1331
+ providerToolName: part.name,
1332
+ };
1333
+
1334
+ controller.enqueue({
1335
+ type: 'tool-input-start',
1336
+ id: part.id,
1337
+ toolName: customToolName,
1338
+ providerExecuted: true,
1339
+ });
1340
+ }
1341
+
1342
+ return;
1343
+ }
1344
+
1345
+ case 'web_fetch_tool_result': {
1346
+ if (part.content.type === 'web_fetch_result') {
1347
+ citationDocuments.push({
1348
+ title: part.content.content.title ?? part.content.url,
1349
+ mediaType: part.content.content.source.media_type,
1350
+ });
1351
+ controller.enqueue({
1352
+ type: 'tool-result',
1353
+ toolCallId: part.tool_use_id,
1354
+ toolName: toolNameMapping.toCustomToolName('web_fetch'),
1355
+ result: {
1356
+ type: 'web_fetch_result',
1357
+ url: part.content.url,
1358
+ retrievedAt: part.content.retrieved_at,
1359
+ content: {
1360
+ type: part.content.content.type,
1361
+ title: part.content.content.title,
1362
+ citations: part.content.content.citations,
1363
+ source: {
1364
+ type: part.content.content.source.type,
1365
+ mediaType: part.content.content.source.media_type,
1366
+ data: part.content.content.source.data,
1367
+ },
1368
+ },
1369
+ },
1370
+ });
1371
+ } else if (
1372
+ part.content.type === 'web_fetch_tool_result_error'
1373
+ ) {
1374
+ controller.enqueue({
1375
+ type: 'tool-result',
1376
+ toolCallId: part.tool_use_id,
1377
+ toolName: toolNameMapping.toCustomToolName('web_fetch'),
1378
+ isError: true,
1379
+ result: {
1380
+ type: 'web_fetch_tool_result_error',
1381
+ errorCode: part.content.error_code,
1382
+ },
1383
+ });
1384
+ }
1385
+
1386
+ return;
1387
+ }
1388
+
1389
+ case 'web_search_tool_result': {
1390
+ if (Array.isArray(part.content)) {
1391
+ controller.enqueue({
1392
+ type: 'tool-result',
1393
+ toolCallId: part.tool_use_id,
1394
+ toolName: toolNameMapping.toCustomToolName('web_search'),
1395
+ result: part.content.map(result => ({
1396
+ url: result.url,
1397
+ title: result.title,
1398
+ pageAge: result.page_age ?? null,
1399
+ encryptedContent: result.encrypted_content,
1400
+ type: result.type,
1401
+ })),
1402
+ });
1403
+
1404
+ for (const result of part.content) {
1405
+ controller.enqueue({
1406
+ type: 'source',
1407
+ sourceType: 'url',
1408
+ id: generateId(),
1409
+ url: result.url,
1410
+ title: result.title,
1411
+ providerMetadata: {
1412
+ anthropic: {
1413
+ pageAge: result.page_age ?? null,
1414
+ },
1415
+ },
1416
+ });
1417
+ }
1418
+ } else {
1419
+ controller.enqueue({
1420
+ type: 'tool-result',
1421
+ toolCallId: part.tool_use_id,
1422
+ toolName: toolNameMapping.toCustomToolName('web_search'),
1423
+ isError: true,
1424
+ result: {
1425
+ type: 'web_search_tool_result_error',
1426
+ errorCode: part.content.error_code,
1427
+ },
1428
+ });
1429
+ }
1430
+ return;
1431
+ }
1432
+
1433
+ // code execution 20250522:
1434
+ case 'code_execution_tool_result': {
1435
+ if (part.content.type === 'code_execution_result') {
1436
+ controller.enqueue({
1437
+ type: 'tool-result',
1438
+ toolCallId: part.tool_use_id,
1439
+ toolName:
1440
+ toolNameMapping.toCustomToolName('code_execution'),
1441
+ result: {
1442
+ type: part.content.type,
1443
+ stdout: part.content.stdout,
1444
+ stderr: part.content.stderr,
1445
+ return_code: part.content.return_code,
1446
+ content: part.content.content ?? [],
1447
+ },
1448
+ });
1449
+ } else if (
1450
+ part.content.type === 'code_execution_tool_result_error'
1451
+ ) {
1452
+ controller.enqueue({
1453
+ type: 'tool-result',
1454
+ toolCallId: part.tool_use_id,
1455
+ toolName:
1456
+ toolNameMapping.toCustomToolName('code_execution'),
1457
+ isError: true,
1458
+ result: {
1459
+ type: 'code_execution_tool_result_error',
1460
+ errorCode: part.content.error_code,
1461
+ },
1462
+ });
1463
+ }
1464
+
1465
+ return;
1466
+ }
1467
+
1468
+ // code execution 20250825:
1469
+ case 'bash_code_execution_tool_result':
1470
+ case 'text_editor_code_execution_tool_result': {
1471
+ controller.enqueue({
1472
+ type: 'tool-result',
1473
+ toolCallId: part.tool_use_id,
1474
+ toolName:
1475
+ toolNameMapping.toCustomToolName('code_execution'),
1476
+ result: part.content,
1477
+ });
1478
+ return;
1479
+ }
1480
+
1481
+ // tool search tool results:
1482
+ case 'tool_search_tool_result': {
1483
+ const providerToolName =
1484
+ serverToolCalls[part.tool_use_id] ??
1485
+ 'tool_search_tool_regex';
1486
+ if (part.content.type === 'tool_search_tool_search_result') {
1487
+ controller.enqueue({
1488
+ type: 'tool-result',
1489
+ toolCallId: part.tool_use_id,
1490
+ toolName:
1491
+ toolNameMapping.toCustomToolName(providerToolName),
1492
+ result: part.content.tool_references.map(ref => ({
1493
+ type: ref.type,
1494
+ toolName: ref.tool_name,
1495
+ })),
1496
+ });
1497
+ } else {
1498
+ controller.enqueue({
1499
+ type: 'tool-result',
1500
+ toolCallId: part.tool_use_id,
1501
+ toolName:
1502
+ toolNameMapping.toCustomToolName(providerToolName),
1503
+ isError: true,
1504
+ result: {
1505
+ type: 'tool_search_tool_result_error',
1506
+ errorCode: part.content.error_code,
1507
+ },
1508
+ });
1509
+ }
1510
+ return;
1511
+ }
1512
+
1513
+ case 'mcp_tool_use': {
1514
+ mcpToolCalls[part.id] = {
1515
+ type: 'tool-call',
1516
+ toolCallId: part.id,
1517
+ toolName: part.name,
1518
+ input: JSON.stringify(part.input),
1519
+ providerExecuted: true,
1520
+ dynamic: true,
1521
+ providerMetadata: {
1522
+ anthropic: {
1523
+ type: 'mcp-tool-use',
1524
+ serverName: part.server_name,
1525
+ },
1526
+ },
1527
+ };
1528
+ controller.enqueue(mcpToolCalls[part.id]);
1529
+ return;
1530
+ }
1531
+
1532
+ case 'mcp_tool_result': {
1533
+ controller.enqueue({
1534
+ type: 'tool-result',
1535
+ toolCallId: part.tool_use_id,
1536
+ toolName: mcpToolCalls[part.tool_use_id].toolName,
1537
+ isError: part.is_error,
1538
+ result: part.content,
1539
+ dynamic: true,
1540
+ providerMetadata:
1541
+ mcpToolCalls[part.tool_use_id].providerMetadata,
1542
+ });
1543
+ return;
1544
+ }
1545
+
1546
+ default: {
1547
+ const _exhaustiveCheck: never = contentBlockType;
1548
+ throw new Error(
1549
+ `Unsupported content block type: ${_exhaustiveCheck}`,
1550
+ );
1551
+ }
1552
+ }
1553
+ }
1554
+
1555
+ case 'content_block_stop': {
1556
+ // when finishing a tool call block, send the full tool call:
1557
+ if (contentBlocks[value.index] != null) {
1558
+ const contentBlock = contentBlocks[value.index];
1559
+
1560
+ switch (contentBlock.type) {
1561
+ case 'text': {
1562
+ controller.enqueue({
1563
+ type: 'text-end',
1564
+ id: String(value.index),
1565
+ });
1566
+ break;
1567
+ }
1568
+
1569
+ case 'reasoning': {
1570
+ controller.enqueue({
1571
+ type: 'reasoning-end',
1572
+ id: String(value.index),
1573
+ });
1574
+ break;
1575
+ }
1576
+
1577
+ case 'tool-call':
1578
+ // when a json response tool is used, the tool call is returned as text,
1579
+ // so we ignore the tool call content:
1580
+ const isJsonResponseTool =
1581
+ usesJsonResponseTool && contentBlock.toolName === 'json';
1582
+
1583
+ if (!isJsonResponseTool) {
1584
+ controller.enqueue({
1585
+ type: 'tool-input-end',
1586
+ id: contentBlock.toolCallId,
1587
+ });
1588
+
1589
+ // For code_execution, inject 'programmatic-tool-call' type
1590
+ // when input has { code } format (programmatic tool calling)
1591
+ let finalInput =
1592
+ contentBlock.input === '' ? '{}' : contentBlock.input;
1593
+ if (contentBlock.providerToolName === 'code_execution') {
1594
+ try {
1595
+ const parsed = JSON.parse(finalInput);
1596
+ if (
1597
+ parsed != null &&
1598
+ typeof parsed === 'object' &&
1599
+ 'code' in parsed &&
1600
+ !('type' in parsed)
1601
+ ) {
1602
+ finalInput = JSON.stringify({
1603
+ type: 'programmatic-tool-call',
1604
+ ...parsed,
1605
+ });
1606
+ }
1607
+ } catch {
1608
+ // ignore parse errors, use original input
1609
+ }
1610
+ }
1611
+
1612
+ controller.enqueue({
1613
+ type: 'tool-call',
1614
+ toolCallId: contentBlock.toolCallId,
1615
+ toolName: contentBlock.toolName,
1616
+ input: finalInput,
1617
+ providerExecuted: contentBlock.providerExecuted,
1618
+ ...(contentBlock.caller && {
1619
+ providerMetadata: {
1620
+ anthropic: {
1621
+ caller: contentBlock.caller,
1622
+ },
1623
+ },
1624
+ }),
1625
+ });
1626
+ }
1627
+ break;
1628
+ }
1629
+
1630
+ delete contentBlocks[value.index];
1631
+ }
1632
+
1633
+ blockType = undefined; // reset block type
1634
+
1635
+ return;
1636
+ }
1637
+
1638
+ case 'content_block_delta': {
1639
+ const deltaType = value.delta.type;
1640
+
1641
+ switch (deltaType) {
1642
+ case 'text_delta': {
1643
+ // when a json response tool is used, the tool call is returned as text,
1644
+ // so we ignore the text content:
1645
+ if (usesJsonResponseTool) {
1646
+ return; // excluding the text-start will also exclude the text-end
1647
+ }
1648
+
1649
+ controller.enqueue({
1650
+ type: 'text-delta',
1651
+ id: String(value.index),
1652
+ delta: value.delta.text,
1653
+ });
1654
+
1655
+ return;
1656
+ }
1657
+
1658
+ case 'thinking_delta': {
1659
+ controller.enqueue({
1660
+ type: 'reasoning-delta',
1661
+ id: String(value.index),
1662
+ delta: value.delta.thinking,
1663
+ });
1664
+
1665
+ return;
1666
+ }
1667
+
1668
+ case 'signature_delta': {
1669
+ // signature are only supported on thinking blocks:
1670
+ if (blockType === 'thinking') {
1671
+ controller.enqueue({
1672
+ type: 'reasoning-delta',
1673
+ id: String(value.index),
1674
+ delta: '',
1675
+ providerMetadata: {
1676
+ anthropic: {
1677
+ signature: value.delta.signature,
1678
+ } satisfies AnthropicReasoningMetadata,
1679
+ },
1680
+ });
1681
+ }
1682
+
1683
+ return;
1684
+ }
1685
+
1686
+ case 'input_json_delta': {
1687
+ const contentBlock = contentBlocks[value.index];
1688
+ let delta = value.delta.partial_json;
1689
+
1690
+ // skip empty deltas to enable replacing the first character
1691
+ // in the code execution 20250825 tool.
1692
+ if (delta.length === 0) {
1693
+ return;
1694
+ }
1695
+
1696
+ if (isJsonResponseFromTool) {
1697
+ if (contentBlock?.type !== 'text') {
1698
+ return; // exclude reasoning
1699
+ }
1700
+
1701
+ controller.enqueue({
1702
+ type: 'text-delta',
1703
+ id: String(value.index),
1704
+ delta,
1705
+ });
1706
+ } else {
1707
+ if (contentBlock?.type !== 'tool-call') {
1708
+ return;
1709
+ }
1710
+
1711
+ // for the code execution 20250825, we need to add
1712
+ // the type to the delta and change the tool name.
1713
+ if (
1714
+ contentBlock.firstDelta &&
1715
+ (contentBlock.providerToolName ===
1716
+ 'bash_code_execution' ||
1717
+ contentBlock.providerToolName ===
1718
+ 'text_editor_code_execution')
1719
+ ) {
1720
+ delta = `{"type": "${contentBlock.providerToolName}",${delta.substring(1)}`;
1721
+ }
1722
+
1723
+ controller.enqueue({
1724
+ type: 'tool-input-delta',
1725
+ id: contentBlock.toolCallId,
1726
+ delta,
1727
+ });
1728
+
1729
+ contentBlock.input += delta;
1730
+ contentBlock.firstDelta = false;
1731
+ }
1732
+
1733
+ return;
1734
+ }
1735
+
1736
+ case 'citations_delta': {
1737
+ const citation = value.delta.citation;
1738
+ const source = createCitationSource(
1739
+ citation,
1740
+ citationDocuments,
1741
+ generateId,
1742
+ );
1743
+
1744
+ if (source) {
1745
+ controller.enqueue(source);
1746
+ }
1747
+
1748
+ return;
1749
+ }
1750
+
1751
+ default: {
1752
+ const _exhaustiveCheck: never = deltaType;
1753
+ throw new Error(
1754
+ `Unsupported delta type: ${_exhaustiveCheck}`,
1755
+ );
1756
+ }
1757
+ }
1758
+ }
1759
+
1760
+ case 'message_start': {
1761
+ usage.input_tokens = value.message.usage.input_tokens;
1762
+ usage.cache_read_input_tokens =
1763
+ value.message.usage.cache_read_input_tokens ?? 0;
1764
+ usage.cache_creation_input_tokens =
1765
+ value.message.usage.cache_creation_input_tokens ?? 0;
1766
+
1767
+ rawUsage = {
1768
+ ...(value.message.usage as JSONObject),
1769
+ };
1770
+
1771
+ cacheCreationInputTokens =
1772
+ value.message.usage.cache_creation_input_tokens ?? null;
1773
+
1774
+ if (value.message.container != null) {
1775
+ container = {
1776
+ expiresAt: value.message.container.expires_at,
1777
+ id: value.message.container.id,
1778
+ skills: null,
1779
+ };
1780
+ }
1781
+
1782
+ if (value.message.stop_reason != null) {
1783
+ finishReason = {
1784
+ unified: mapAnthropicStopReason({
1785
+ finishReason: value.message.stop_reason,
1786
+ isJsonResponseFromTool,
1787
+ }),
1788
+ raw: value.message.stop_reason,
1789
+ };
1790
+ }
1791
+
1792
+ controller.enqueue({
1793
+ type: 'response-metadata',
1794
+ id: value.message.id ?? undefined,
1795
+ modelId: value.message.model ?? undefined,
1796
+ });
1797
+
1798
+ // Programmatic tool calling: process pre-populated content blocks
1799
+ // (for deferred tool calls, content may be in message_start)
1800
+ if (value.message.content != null) {
1801
+ for (
1802
+ let contentIndex = 0;
1803
+ contentIndex < value.message.content.length;
1804
+ contentIndex++
1805
+ ) {
1806
+ const part = value.message.content[contentIndex];
1807
+ if (part.type === 'tool_use') {
1808
+ const caller = part.caller;
1809
+ const callerInfo = caller
1810
+ ? {
1811
+ type: caller.type,
1812
+ toolId:
1813
+ 'tool_id' in caller ? caller.tool_id : undefined,
1814
+ }
1815
+ : undefined;
1816
+
1817
+ controller.enqueue({
1818
+ type: 'tool-input-start',
1819
+ id: part.id,
1820
+ toolName: part.name,
1821
+ });
1822
+
1823
+ const inputStr = JSON.stringify(part.input ?? {});
1824
+ controller.enqueue({
1825
+ type: 'tool-input-delta',
1826
+ id: part.id,
1827
+ delta: inputStr,
1828
+ });
1829
+
1830
+ controller.enqueue({
1831
+ type: 'tool-input-end',
1832
+ id: part.id,
1833
+ });
1834
+
1835
+ controller.enqueue({
1836
+ type: 'tool-call',
1837
+ toolCallId: part.id,
1838
+ toolName: part.name,
1839
+ input: inputStr,
1840
+ ...(callerInfo && {
1841
+ providerMetadata: {
1842
+ anthropic: {
1843
+ caller: callerInfo,
1844
+ },
1845
+ },
1846
+ }),
1847
+ });
1848
+ }
1849
+ }
1850
+ }
1851
+
1852
+ return;
1853
+ }
1854
+
1855
+ case 'message_delta': {
1856
+ if (
1857
+ value.usage.input_tokens != null &&
1858
+ usage.input_tokens !== value.usage.input_tokens
1859
+ ) {
1860
+ usage.input_tokens = value.usage.input_tokens;
1861
+ }
1862
+ usage.output_tokens = value.usage.output_tokens;
1863
+
1864
+ finishReason = {
1865
+ unified: mapAnthropicStopReason({
1866
+ finishReason: value.delta.stop_reason,
1867
+ isJsonResponseFromTool,
1868
+ }),
1869
+ raw: value.delta.stop_reason ?? undefined,
1870
+ };
1871
+
1872
+ stopSequence = value.delta.stop_sequence ?? null;
1873
+ container =
1874
+ value.delta.container != null
1875
+ ? {
1876
+ expiresAt: value.delta.container.expires_at,
1877
+ id: value.delta.container.id,
1878
+ skills:
1879
+ value.delta.container.skills?.map(skill => ({
1880
+ type: skill.type,
1881
+ skillId: skill.skill_id,
1882
+ version: skill.version,
1883
+ })) ?? null,
1884
+ }
1885
+ : null;
1886
+
1887
+ if (value.delta.context_management) {
1888
+ contextManagement = mapAnthropicResponseContextManagement(
1889
+ value.delta.context_management,
1890
+ );
1891
+ }
1892
+
1893
+ rawUsage = {
1894
+ ...rawUsage,
1895
+ ...(value.usage as JSONObject),
1896
+ };
1897
+
1898
+ return;
1899
+ }
1900
+
1901
+ case 'message_stop': {
1902
+ controller.enqueue({
1903
+ type: 'finish',
1904
+ finishReason,
1905
+ usage: convertAnthropicMessagesUsage(usage),
1906
+ providerMetadata: {
1907
+ anthropic: {
1908
+ usage: (rawUsage as JSONObject) ?? null,
1909
+ cacheCreationInputTokens,
1910
+ stopSequence,
1911
+ container,
1912
+ contextManagement,
1913
+ } satisfies AnthropicMessageMetadata,
1914
+ },
1915
+ });
1916
+ return;
1917
+ }
1918
+
1919
+ case 'error': {
1920
+ controller.enqueue({ type: 'error', error: value.error });
1921
+ return;
1922
+ }
1923
+
1924
+ default: {
1925
+ const _exhaustiveCheck: never = value;
1926
+ throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`);
1927
+ }
1928
+ }
1929
+ },
1930
+ }),
1931
+ );
1932
+
1933
+ // The first chunk needs to be pulled immediately to check if it is an error
1934
+ const [streamForFirstChunk, streamForConsumer] = transformedStream.tee();
1935
+
1936
+ const firstChunkReader = streamForFirstChunk.getReader();
1937
+ try {
1938
+ await firstChunkReader.read(); // streamStart comes first, ignored
1939
+
1940
+ let result = await firstChunkReader.read();
1941
+
1942
+ // when raw chunks are enabled, the first chunk is a raw chunk, so we need to read the next chunk
1943
+ if (result.value?.type === 'raw') {
1944
+ result = await firstChunkReader.read();
1945
+ }
1946
+
1947
+ // The Anthropic API returns 200 responses when there are overloaded errors.
1948
+ // We handle the case where the first chunk is an error here and transform
1949
+ // it into an APICallError.
1950
+ if (result.value?.type === 'error') {
1951
+ const error = result.value.error as { message: string; type: string };
1952
+
1953
+ throw new APICallError({
1954
+ message: error.message,
1955
+ url,
1956
+ requestBodyValues: body,
1957
+ statusCode: error.type === 'overloaded_error' ? 529 : 500,
1958
+ responseHeaders,
1959
+ responseBody: JSON.stringify(error),
1960
+ isRetryable: error.type === 'overloaded_error',
1961
+ });
1962
+ }
1963
+ } finally {
1964
+ firstChunkReader.cancel().catch(() => {});
1965
+ firstChunkReader.releaseLock();
1966
+ }
1967
+
1968
+ return {
1969
+ stream: streamForConsumer,
1970
+ request: { body },
1971
+ response: { headers: responseHeaders },
1972
+ };
1973
+ }
1974
+ }
1975
+
1976
+ /**
1977
+ * Returns the capabilities of a Claude model that are used for defaults and feature selection.
1978
+ *
1979
+ * @see https://docs.claude.com/en/docs/about-claude/models/overview#model-comparison-table
1980
+ * @see https://platform.claude.com/docs/en/build-with-claude/structured-outputs
1981
+ */
1982
+ function getModelCapabilities(modelId: string): {
1983
+ maxOutputTokens: number;
1984
+ supportsStructuredOutput: boolean;
1985
+ isKnownModel: boolean;
1986
+ } {
1987
+ if (
1988
+ modelId.includes('claude-sonnet-4-5') ||
1989
+ modelId.includes('claude-opus-4-5') ||
1990
+ modelId.includes('claude-haiku-4-5')
1991
+ ) {
1992
+ return {
1993
+ maxOutputTokens: 64000,
1994
+ supportsStructuredOutput: true,
1995
+ isKnownModel: true,
1996
+ };
1997
+ } else if (modelId.includes('claude-opus-4-1')) {
1998
+ return {
1999
+ maxOutputTokens: 32000,
2000
+ supportsStructuredOutput: true,
2001
+ isKnownModel: true,
2002
+ };
2003
+ } else if (
2004
+ modelId.includes('claude-sonnet-4-') ||
2005
+ modelId.includes('claude-3-7-sonnet')
2006
+ ) {
2007
+ return {
2008
+ maxOutputTokens: 64000,
2009
+ supportsStructuredOutput: false,
2010
+ isKnownModel: true,
2011
+ };
2012
+ } else if (modelId.includes('claude-opus-4-')) {
2013
+ return {
2014
+ maxOutputTokens: 32000,
2015
+ supportsStructuredOutput: false,
2016
+ isKnownModel: true,
2017
+ };
2018
+ } else if (modelId.includes('claude-3-5-haiku')) {
2019
+ return {
2020
+ maxOutputTokens: 8192,
2021
+ supportsStructuredOutput: false,
2022
+ isKnownModel: true,
2023
+ };
2024
+ } else if (modelId.includes('claude-3-haiku')) {
2025
+ return {
2026
+ maxOutputTokens: 4096,
2027
+ supportsStructuredOutput: false,
2028
+ isKnownModel: true,
2029
+ };
2030
+ } else {
2031
+ return {
2032
+ maxOutputTokens: 4096,
2033
+ supportsStructuredOutput: false,
2034
+ isKnownModel: false,
2035
+ };
2036
+ }
2037
+ }
2038
+
2039
+ function mapAnthropicResponseContextManagement(
2040
+ contextManagement: AnthropicResponseContextManagement | null | undefined,
2041
+ ): AnthropicMessageMetadata['contextManagement'] | null {
2042
+ return contextManagement
2043
+ ? {
2044
+ appliedEdits: contextManagement.applied_edits
2045
+ .map(edit => {
2046
+ const strategy = edit.type;
2047
+
2048
+ switch (strategy) {
2049
+ case 'clear_tool_uses_20250919':
2050
+ return {
2051
+ type: edit.type,
2052
+ clearedToolUses: edit.cleared_tool_uses,
2053
+ clearedInputTokens: edit.cleared_input_tokens,
2054
+ };
2055
+
2056
+ case 'clear_thinking_20251015':
2057
+ return {
2058
+ type: edit.type,
2059
+ clearedThinkingTurns: edit.cleared_thinking_turns,
2060
+ clearedInputTokens: edit.cleared_input_tokens,
2061
+ };
2062
+ }
2063
+ })
2064
+ .filter(edit => edit !== undefined),
2065
+ }
2066
+ : null;
2067
+ }