@ai-sdk/anthropic 3.0.18 → 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 (72) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/package.json +3 -2
  5. package/src/__fixtures__/anthropic-code-execution-20250825.1.chunks.txt +248 -0
  6. package/src/__fixtures__/anthropic-code-execution-20250825.1.json +70 -0
  7. package/src/__fixtures__/anthropic-code-execution-20250825.2.chunks.txt +984 -0
  8. package/src/__fixtures__/anthropic-code-execution-20250825.2.json +111 -0
  9. package/src/__fixtures__/anthropic-code-execution-20250825.pptx-skill.chunks.txt +691 -0
  10. package/src/__fixtures__/anthropic-code-execution-20250825.pptx-skill.json +1801 -0
  11. package/src/__fixtures__/anthropic-json-other-tool.1.chunks.txt +13 -0
  12. package/src/__fixtures__/anthropic-json-other-tool.1.json +26 -0
  13. package/src/__fixtures__/anthropic-json-output-format.1.chunks.txt +120 -0
  14. package/src/__fixtures__/anthropic-json-output-format.1.json +25 -0
  15. package/src/__fixtures__/anthropic-json-tool.1.chunks.txt +9 -0
  16. package/src/__fixtures__/anthropic-json-tool.1.json +37 -0
  17. package/src/__fixtures__/anthropic-json-tool.2.chunks.txt +14 -0
  18. package/src/__fixtures__/anthropic-mcp.1.chunks.txt +17 -0
  19. package/src/__fixtures__/anthropic-mcp.1.json +39 -0
  20. package/src/__fixtures__/anthropic-memory-20250818.1.json +28 -0
  21. package/src/__fixtures__/anthropic-message-delta-input-tokens.chunks.txt +8 -0
  22. package/src/__fixtures__/anthropic-programmatic-tool-calling.1.chunks.txt +278 -0
  23. package/src/__fixtures__/anthropic-programmatic-tool-calling.1.json +106 -0
  24. package/src/__fixtures__/anthropic-tool-no-args.chunks.txt +13 -0
  25. package/src/__fixtures__/anthropic-tool-no-args.json +31 -0
  26. package/src/__fixtures__/anthropic-tool-search-bm25.1.chunks.txt +47 -0
  27. package/src/__fixtures__/anthropic-tool-search-bm25.1.json +67 -0
  28. package/src/__fixtures__/anthropic-tool-search-regex.1.chunks.txt +51 -0
  29. package/src/__fixtures__/anthropic-tool-search-regex.1.json +65 -0
  30. package/src/__fixtures__/anthropic-web-fetch-tool.1.chunks.txt +64 -0
  31. package/src/__fixtures__/anthropic-web-fetch-tool.1.json +54 -0
  32. package/src/__fixtures__/anthropic-web-fetch-tool.2.json +56 -0
  33. package/src/__fixtures__/anthropic-web-fetch-tool.error.json +46 -0
  34. package/src/__fixtures__/anthropic-web-search-tool.1.chunks.txt +120 -0
  35. package/src/__fixtures__/anthropic-web-search-tool.1.json +181 -0
  36. package/src/__snapshots__/anthropic-messages-language-model.test.ts.snap +16719 -0
  37. package/src/anthropic-error.test.ts +42 -0
  38. package/src/anthropic-error.ts +26 -0
  39. package/src/anthropic-message-metadata.ts +105 -0
  40. package/src/anthropic-messages-api.ts +1188 -0
  41. package/src/anthropic-messages-language-model.test.ts +7170 -0
  42. package/src/anthropic-messages-language-model.ts +2067 -0
  43. package/src/anthropic-messages-options.ts +213 -0
  44. package/src/anthropic-prepare-tools.test.ts +1219 -0
  45. package/src/anthropic-prepare-tools.ts +341 -0
  46. package/src/anthropic-provider.test.ts +162 -0
  47. package/src/anthropic-provider.ts +152 -0
  48. package/src/anthropic-tools.ts +182 -0
  49. package/src/convert-anthropic-messages-usage.ts +32 -0
  50. package/src/convert-to-anthropic-messages-prompt.test.ts +2902 -0
  51. package/src/convert-to-anthropic-messages-prompt.ts +1050 -0
  52. package/src/forward-anthropic-container-id-from-last-step.ts +38 -0
  53. package/src/get-cache-control.ts +63 -0
  54. package/src/index.ts +10 -0
  55. package/src/internal/index.ts +4 -0
  56. package/src/map-anthropic-stop-reason.ts +28 -0
  57. package/src/tool/bash_20241022.ts +33 -0
  58. package/src/tool/bash_20250124.ts +33 -0
  59. package/src/tool/code-execution_20250522.ts +61 -0
  60. package/src/tool/code-execution_20250825.ts +281 -0
  61. package/src/tool/computer_20241022.ts +87 -0
  62. package/src/tool/computer_20250124.ts +130 -0
  63. package/src/tool/memory_20250818.ts +62 -0
  64. package/src/tool/text-editor_20241022.ts +63 -0
  65. package/src/tool/text-editor_20250124.ts +63 -0
  66. package/src/tool/text-editor_20250429.ts +64 -0
  67. package/src/tool/text-editor_20250728.ts +80 -0
  68. package/src/tool/tool-search-bm25_20251119.ts +98 -0
  69. package/src/tool/tool-search-regex_20251119.ts +110 -0
  70. package/src/tool/web-fetch-20250910.ts +145 -0
  71. package/src/tool/web-search_20250305.ts +136 -0
  72. package/src/version.ts +6 -0
@@ -0,0 +1,2902 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SharedV3Warning } from '@ai-sdk/provider';
3
+ import { createToolNameMapping } from '@ai-sdk/provider-utils';
4
+ import { convertToAnthropicMessagesPrompt } from './convert-to-anthropic-messages-prompt';
5
+ import { CacheControlValidator } from './get-cache-control';
6
+
7
+ // Create a default identity tool name mapping for tests (no tools = no mapping)
8
+ const defaultToolNameMapping = createToolNameMapping({
9
+ tools: [],
10
+ providerToolNames: {},
11
+ });
12
+
13
+ describe('system messages', () => {
14
+ it('should convert a single system message into an anthropic system message', async () => {
15
+ const result = await convertToAnthropicMessagesPrompt({
16
+ prompt: [{ role: 'system', content: 'This is a system message' }],
17
+ sendReasoning: true,
18
+ warnings: [],
19
+ toolNameMapping: defaultToolNameMapping,
20
+ });
21
+
22
+ expect(result).toEqual({
23
+ prompt: {
24
+ messages: [],
25
+ system: [{ type: 'text', text: 'This is a system message' }],
26
+ },
27
+ betas: new Set(),
28
+ });
29
+ });
30
+
31
+ it('should convert multiple system messages into an anthropic system message', async () => {
32
+ const result = await convertToAnthropicMessagesPrompt({
33
+ prompt: [
34
+ { role: 'system', content: 'This is a system message' },
35
+ { role: 'system', content: 'This is another system message' },
36
+ ],
37
+ sendReasoning: true,
38
+ warnings: [],
39
+ toolNameMapping: defaultToolNameMapping,
40
+ });
41
+
42
+ expect(result).toEqual({
43
+ prompt: {
44
+ messages: [],
45
+ system: [
46
+ { type: 'text', text: 'This is a system message' },
47
+ { type: 'text', text: 'This is another system message' },
48
+ ],
49
+ },
50
+ betas: new Set(),
51
+ });
52
+ });
53
+ });
54
+
55
+ describe('user messages', () => {
56
+ it('should add image parts for UInt8Array images', async () => {
57
+ const result = await convertToAnthropicMessagesPrompt({
58
+ prompt: [
59
+ {
60
+ role: 'user',
61
+ content: [
62
+ {
63
+ type: 'file',
64
+ data: 'AAECAw==',
65
+ mediaType: 'image/png',
66
+ },
67
+ ],
68
+ },
69
+ ],
70
+ sendReasoning: true,
71
+ warnings: [],
72
+ toolNameMapping: defaultToolNameMapping,
73
+ });
74
+
75
+ expect(result).toEqual({
76
+ prompt: {
77
+ messages: [
78
+ {
79
+ role: 'user',
80
+ content: [
81
+ {
82
+ type: 'image',
83
+ source: {
84
+ data: 'AAECAw==',
85
+ media_type: 'image/png',
86
+ type: 'base64',
87
+ },
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ system: undefined,
93
+ },
94
+ betas: new Set(),
95
+ });
96
+ });
97
+
98
+ it('should add image parts for URL images', async () => {
99
+ const result = await convertToAnthropicMessagesPrompt({
100
+ prompt: [
101
+ {
102
+ role: 'user',
103
+ content: [
104
+ {
105
+ type: 'file',
106
+ data: new URL('https://example.com/image.png'),
107
+ mediaType: 'image/*',
108
+ },
109
+ ],
110
+ },
111
+ ],
112
+ sendReasoning: true,
113
+ warnings: [],
114
+ toolNameMapping: defaultToolNameMapping,
115
+ });
116
+
117
+ expect(result).toEqual({
118
+ prompt: {
119
+ messages: [
120
+ {
121
+ role: 'user',
122
+ content: [
123
+ {
124
+ type: 'image',
125
+ source: {
126
+ type: 'url',
127
+ url: 'https://example.com/image.png',
128
+ },
129
+ },
130
+ ],
131
+ },
132
+ ],
133
+ system: undefined,
134
+ },
135
+ betas: new Set(),
136
+ });
137
+ });
138
+
139
+ it('should treat URL strings in image file data as URLs, not base64)', async () => {
140
+ const result = await convertToAnthropicMessagesPrompt({
141
+ prompt: [
142
+ {
143
+ role: 'user',
144
+ content: [
145
+ {
146
+ type: 'file',
147
+ data: 'https://example.com/image.png', // String URL (as if after JSON deserialization)
148
+ mediaType: 'image/png',
149
+ },
150
+ ],
151
+ },
152
+ ],
153
+ sendReasoning: true,
154
+ warnings: [],
155
+ toolNameMapping: defaultToolNameMapping,
156
+ });
157
+
158
+ expect(result).toEqual({
159
+ prompt: {
160
+ messages: [
161
+ {
162
+ role: 'user',
163
+ content: [
164
+ {
165
+ type: 'image',
166
+ source: {
167
+ type: 'url',
168
+ url: 'https://example.com/image.png',
169
+ },
170
+ },
171
+ ],
172
+ },
173
+ ],
174
+ system: undefined,
175
+ },
176
+ betas: new Set(),
177
+ });
178
+ });
179
+
180
+ it('should treat URL strings in PDF file data as URLs, not base64)', async () => {
181
+ const result = await convertToAnthropicMessagesPrompt({
182
+ prompt: [
183
+ {
184
+ role: 'user',
185
+ content: [
186
+ {
187
+ type: 'file',
188
+ data: 'https://example.com/document.pdf',
189
+ mediaType: 'application/pdf',
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ sendReasoning: true,
195
+ warnings: [],
196
+ toolNameMapping: defaultToolNameMapping,
197
+ });
198
+
199
+ expect(result).toEqual({
200
+ prompt: {
201
+ messages: [
202
+ {
203
+ role: 'user',
204
+ content: [
205
+ {
206
+ type: 'document',
207
+ source: {
208
+ type: 'url',
209
+ url: 'https://example.com/document.pdf',
210
+ },
211
+ },
212
+ ],
213
+ },
214
+ ],
215
+ system: undefined,
216
+ },
217
+ betas: new Set(['pdfs-2024-09-25']),
218
+ });
219
+ });
220
+
221
+ it('should add PDF file parts for base64 PDFs', async () => {
222
+ const result = await convertToAnthropicMessagesPrompt({
223
+ prompt: [
224
+ {
225
+ role: 'user',
226
+ content: [
227
+ {
228
+ type: 'file',
229
+ data: 'base64PDFdata',
230
+ mediaType: 'application/pdf',
231
+ },
232
+ ],
233
+ },
234
+ ],
235
+ sendReasoning: true,
236
+ warnings: [],
237
+ toolNameMapping: defaultToolNameMapping,
238
+ });
239
+
240
+ expect(result).toEqual({
241
+ prompt: {
242
+ messages: [
243
+ {
244
+ role: 'user',
245
+ content: [
246
+ {
247
+ type: 'document',
248
+ source: {
249
+ type: 'base64',
250
+ media_type: 'application/pdf',
251
+ data: 'base64PDFdata',
252
+ },
253
+ },
254
+ ],
255
+ },
256
+ ],
257
+ system: undefined,
258
+ },
259
+ betas: new Set(['pdfs-2024-09-25']),
260
+ });
261
+ });
262
+
263
+ it('should add PDF file parts for URL PDFs', async () => {
264
+ const result = await convertToAnthropicMessagesPrompt({
265
+ prompt: [
266
+ {
267
+ role: 'user',
268
+ content: [
269
+ {
270
+ type: 'file',
271
+ data: new URL('https://example.com/document.pdf'),
272
+ mediaType: 'application/pdf',
273
+ },
274
+ ],
275
+ },
276
+ ],
277
+ sendReasoning: true,
278
+ warnings: [],
279
+ toolNameMapping: defaultToolNameMapping,
280
+ });
281
+
282
+ expect(result).toEqual({
283
+ prompt: {
284
+ messages: [
285
+ {
286
+ role: 'user',
287
+ content: [
288
+ {
289
+ type: 'document',
290
+ source: {
291
+ type: 'url',
292
+ url: 'https://example.com/document.pdf',
293
+ },
294
+ },
295
+ ],
296
+ },
297
+ ],
298
+ system: undefined,
299
+ },
300
+ betas: new Set(['pdfs-2024-09-25']),
301
+ });
302
+ });
303
+
304
+ it('should add text file parts for text/plain documents', async () => {
305
+ const result = await convertToAnthropicMessagesPrompt({
306
+ prompt: [
307
+ {
308
+ role: 'user',
309
+ content: [
310
+ {
311
+ type: 'file',
312
+ data: Buffer.from('sample text content', 'utf-8').toString(
313
+ 'base64',
314
+ ),
315
+ mediaType: 'text/plain',
316
+ filename: 'sample.txt',
317
+ },
318
+ ],
319
+ },
320
+ ],
321
+ sendReasoning: true,
322
+ warnings: [],
323
+ toolNameMapping: defaultToolNameMapping,
324
+ });
325
+
326
+ expect(result).toEqual({
327
+ prompt: {
328
+ messages: [
329
+ {
330
+ role: 'user',
331
+ content: [
332
+ {
333
+ type: 'document',
334
+ source: {
335
+ type: 'text',
336
+ media_type: 'text/plain',
337
+ data: 'sample text content',
338
+ },
339
+ title: 'sample.txt',
340
+ cache_control: undefined,
341
+ },
342
+ ],
343
+ },
344
+ ],
345
+ system: undefined,
346
+ },
347
+ betas: new Set(),
348
+ });
349
+ });
350
+
351
+ it('should throw error for unsupported file types', async () => {
352
+ await expect(
353
+ convertToAnthropicMessagesPrompt({
354
+ prompt: [
355
+ {
356
+ role: 'user',
357
+ content: [
358
+ {
359
+ type: 'file',
360
+ data: 'base64data',
361
+ mediaType: 'video/mp4',
362
+ },
363
+ ],
364
+ },
365
+ ],
366
+ sendReasoning: true,
367
+ warnings: [],
368
+ toolNameMapping: defaultToolNameMapping,
369
+ }),
370
+ ).rejects.toThrow('media type: video/mp4');
371
+ });
372
+ });
373
+
374
+ describe('tool messages', () => {
375
+ it('should convert a single tool result into an anthropic user message', async () => {
376
+ const result = await convertToAnthropicMessagesPrompt({
377
+ prompt: [
378
+ {
379
+ role: 'tool',
380
+ content: [
381
+ {
382
+ type: 'tool-result',
383
+ toolName: 'tool-1',
384
+ toolCallId: 'tool-call-1',
385
+ output: {
386
+ type: 'json',
387
+ value: { test: 'This is a tool message' },
388
+ },
389
+ },
390
+ ],
391
+ },
392
+ ],
393
+ sendReasoning: true,
394
+ warnings: [],
395
+ toolNameMapping: defaultToolNameMapping,
396
+ });
397
+
398
+ expect(result).toEqual({
399
+ prompt: {
400
+ messages: [
401
+ {
402
+ role: 'user',
403
+ content: [
404
+ {
405
+ type: 'tool_result',
406
+ tool_use_id: 'tool-call-1',
407
+ is_error: undefined,
408
+ content: JSON.stringify({ test: 'This is a tool message' }),
409
+ },
410
+ ],
411
+ },
412
+ ],
413
+ system: undefined,
414
+ },
415
+ betas: new Set(),
416
+ });
417
+ });
418
+
419
+ it('should convert multiple tool results into an anthropic user message', async () => {
420
+ const result = await convertToAnthropicMessagesPrompt({
421
+ prompt: [
422
+ {
423
+ role: 'tool',
424
+ content: [
425
+ {
426
+ type: 'tool-result',
427
+ toolName: 'tool-1',
428
+ toolCallId: 'tool-call-1',
429
+ output: {
430
+ type: 'json',
431
+ value: { test: 'This is a tool message' },
432
+ },
433
+ },
434
+ {
435
+ type: 'tool-result',
436
+ toolName: 'tool-2',
437
+ toolCallId: 'tool-call-2',
438
+ output: { type: 'json', value: { something: 'else' } },
439
+ },
440
+ ],
441
+ },
442
+ ],
443
+ sendReasoning: true,
444
+ warnings: [],
445
+ toolNameMapping: defaultToolNameMapping,
446
+ });
447
+
448
+ expect(result).toEqual({
449
+ prompt: {
450
+ messages: [
451
+ {
452
+ role: 'user',
453
+ content: [
454
+ {
455
+ type: 'tool_result',
456
+ tool_use_id: 'tool-call-1',
457
+ is_error: undefined,
458
+ content: JSON.stringify({ test: 'This is a tool message' }),
459
+ },
460
+ {
461
+ type: 'tool_result',
462
+ tool_use_id: 'tool-call-2',
463
+ is_error: undefined,
464
+ content: JSON.stringify({ something: 'else' }),
465
+ },
466
+ ],
467
+ },
468
+ ],
469
+ system: undefined,
470
+ },
471
+ betas: new Set(),
472
+ });
473
+ });
474
+
475
+ it('should combine user and tool messages', async () => {
476
+ const result = await convertToAnthropicMessagesPrompt({
477
+ prompt: [
478
+ {
479
+ role: 'tool',
480
+ content: [
481
+ {
482
+ type: 'tool-result',
483
+ toolName: 'tool-1',
484
+ toolCallId: 'tool-call-1',
485
+ output: {
486
+ type: 'json',
487
+ value: { test: 'This is a tool message' },
488
+ },
489
+ },
490
+ ],
491
+ },
492
+ {
493
+ role: 'user',
494
+ content: [{ type: 'text', text: 'This is a user message' }],
495
+ },
496
+ ],
497
+ sendReasoning: true,
498
+ warnings: [],
499
+ toolNameMapping: defaultToolNameMapping,
500
+ });
501
+
502
+ expect(result).toEqual({
503
+ prompt: {
504
+ messages: [
505
+ {
506
+ role: 'user',
507
+ content: [
508
+ {
509
+ type: 'tool_result',
510
+ tool_use_id: 'tool-call-1',
511
+ is_error: undefined,
512
+ content: JSON.stringify({ test: 'This is a tool message' }),
513
+ },
514
+ { type: 'text', text: 'This is a user message' },
515
+ ],
516
+ },
517
+ ],
518
+ system: undefined,
519
+ },
520
+ betas: new Set(),
521
+ });
522
+ });
523
+
524
+ it('should handle tool result with content parts', async () => {
525
+ const result = await convertToAnthropicMessagesPrompt({
526
+ prompt: [
527
+ {
528
+ role: 'tool',
529
+ content: [
530
+ {
531
+ type: 'tool-result',
532
+ toolName: 'image-generator',
533
+ toolCallId: 'image-gen-1',
534
+ output: {
535
+ type: 'content',
536
+ value: [
537
+ {
538
+ type: 'text',
539
+ text: 'Image generated successfully',
540
+ },
541
+ {
542
+ type: 'image-data',
543
+ data: 'AAECAw==',
544
+ mediaType: 'image/png',
545
+ },
546
+ ],
547
+ },
548
+ },
549
+ ],
550
+ },
551
+ ],
552
+ sendReasoning: true,
553
+ warnings: [],
554
+ toolNameMapping: defaultToolNameMapping,
555
+ });
556
+
557
+ expect(result).toMatchInlineSnapshot(`
558
+ {
559
+ "betas": Set {},
560
+ "prompt": {
561
+ "messages": [
562
+ {
563
+ "content": [
564
+ {
565
+ "cache_control": undefined,
566
+ "content": [
567
+ {
568
+ "text": "Image generated successfully",
569
+ "type": "text",
570
+ },
571
+ {
572
+ "source": {
573
+ "data": "AAECAw==",
574
+ "media_type": "image/png",
575
+ "type": "base64",
576
+ },
577
+ "type": "image",
578
+ },
579
+ ],
580
+ "is_error": undefined,
581
+ "tool_use_id": "image-gen-1",
582
+ "type": "tool_result",
583
+ },
584
+ ],
585
+ "role": "user",
586
+ },
587
+ ],
588
+ "system": undefined,
589
+ },
590
+ }
591
+ `);
592
+ });
593
+
594
+ it('should handle tool result with PDF content', async () => {
595
+ const result = await convertToAnthropicMessagesPrompt({
596
+ prompt: [
597
+ {
598
+ role: 'tool',
599
+ content: [
600
+ {
601
+ type: 'tool-result',
602
+ toolName: 'pdf-generator',
603
+ toolCallId: 'pdf-gen-1',
604
+ output: {
605
+ type: 'content',
606
+ value: [
607
+ {
608
+ type: 'text',
609
+ text: 'PDF generated successfully',
610
+ },
611
+ {
612
+ type: 'file-data',
613
+ data: 'JVBERi0xLjQKJeLjz9MKNCAwIG9iago=', // Sample PDF base64
614
+ mediaType: 'application/pdf',
615
+ },
616
+ ],
617
+ },
618
+ },
619
+ ],
620
+ },
621
+ ],
622
+ sendReasoning: true,
623
+ warnings: [],
624
+ toolNameMapping: defaultToolNameMapping,
625
+ });
626
+
627
+ expect(result).toMatchInlineSnapshot(`
628
+ {
629
+ "betas": Set {
630
+ "pdfs-2024-09-25",
631
+ },
632
+ "prompt": {
633
+ "messages": [
634
+ {
635
+ "content": [
636
+ {
637
+ "cache_control": undefined,
638
+ "content": [
639
+ {
640
+ "text": "PDF generated successfully",
641
+ "type": "text",
642
+ },
643
+ {
644
+ "source": {
645
+ "data": "JVBERi0xLjQKJeLjz9MKNCAwIG9iago=",
646
+ "media_type": "application/pdf",
647
+ "type": "base64",
648
+ },
649
+ "type": "document",
650
+ },
651
+ ],
652
+ "is_error": undefined,
653
+ "tool_use_id": "pdf-gen-1",
654
+ "type": "tool_result",
655
+ },
656
+ ],
657
+ "role": "user",
658
+ },
659
+ ],
660
+ "system": undefined,
661
+ },
662
+ }
663
+ `);
664
+ });
665
+ it('should handle tool result with url-based PDF content', async () => {
666
+ const result = await convertToAnthropicMessagesPrompt({
667
+ prompt: [
668
+ {
669
+ role: 'tool',
670
+ content: [
671
+ {
672
+ type: 'tool-result',
673
+ toolName: 'get-pdf',
674
+ toolCallId: 'get-pdf-1',
675
+ output: {
676
+ type: 'content',
677
+ value: [
678
+ {
679
+ type: 'file-url',
680
+ url: 'https://example.com/document.pdf',
681
+ },
682
+ ],
683
+ },
684
+ },
685
+ ],
686
+ },
687
+ ],
688
+ sendReasoning: true,
689
+ warnings: [],
690
+ toolNameMapping: defaultToolNameMapping,
691
+ });
692
+
693
+ expect(result).toMatchInlineSnapshot(`
694
+ {
695
+ "betas": Set {},
696
+ "prompt": {
697
+ "messages": [
698
+ {
699
+ "content": [
700
+ {
701
+ "cache_control": undefined,
702
+ "content": [
703
+ {
704
+ "source": {
705
+ "type": "url",
706
+ "url": "https://example.com/document.pdf",
707
+ },
708
+ "type": "document",
709
+ },
710
+ ],
711
+ "is_error": undefined,
712
+ "tool_use_id": "get-pdf-1",
713
+ "type": "tool_result",
714
+ },
715
+ ],
716
+ "role": "user",
717
+ },
718
+ ],
719
+ "system": undefined,
720
+ },
721
+ }
722
+ `);
723
+ });
724
+
725
+ it('should handle tool result with url-based image content', async () => {
726
+ const result = await convertToAnthropicMessagesPrompt({
727
+ prompt: [
728
+ {
729
+ role: 'tool',
730
+ content: [
731
+ {
732
+ type: 'tool-result',
733
+ toolName: 'image-generator',
734
+ toolCallId: 'image-gen-1',
735
+ output: {
736
+ type: 'content',
737
+ value: [
738
+ {
739
+ type: 'image-url',
740
+ url: 'https://example.com/image.png',
741
+ },
742
+ ],
743
+ },
744
+ },
745
+ ],
746
+ },
747
+ ],
748
+ sendReasoning: true,
749
+ warnings: [],
750
+ toolNameMapping: defaultToolNameMapping,
751
+ });
752
+
753
+ expect(result).toMatchInlineSnapshot(`
754
+ {
755
+ "betas": Set {},
756
+ "prompt": {
757
+ "messages": [
758
+ {
759
+ "content": [
760
+ {
761
+ "cache_control": undefined,
762
+ "content": [
763
+ {
764
+ "source": {
765
+ "type": "url",
766
+ "url": "https://example.com/image.png",
767
+ },
768
+ "type": "image",
769
+ },
770
+ ],
771
+ "is_error": undefined,
772
+ "tool_use_id": "image-gen-1",
773
+ "type": "tool_result",
774
+ },
775
+ ],
776
+ "role": "user",
777
+ },
778
+ ],
779
+ "system": undefined,
780
+ },
781
+ }
782
+ `);
783
+ });
784
+ });
785
+
786
+ describe('assistant messages', () => {
787
+ it('should remove trailing whitespace from last assistant message when there is no further user message', async () => {
788
+ const result = await convertToAnthropicMessagesPrompt({
789
+ prompt: [
790
+ {
791
+ role: 'user',
792
+ content: [{ type: 'text', text: 'user content' }],
793
+ },
794
+ {
795
+ role: 'assistant',
796
+ content: [{ type: 'text', text: 'assistant content ' }],
797
+ },
798
+ ],
799
+ sendReasoning: true,
800
+ warnings: [],
801
+ toolNameMapping: defaultToolNameMapping,
802
+ });
803
+
804
+ expect(result).toEqual({
805
+ prompt: {
806
+ messages: [
807
+ {
808
+ role: 'user',
809
+ content: [{ type: 'text', text: 'user content' }],
810
+ },
811
+ {
812
+ role: 'assistant',
813
+ content: [{ type: 'text', text: 'assistant content' }],
814
+ },
815
+ ],
816
+ },
817
+ betas: new Set(),
818
+ });
819
+ });
820
+
821
+ it('should remove trailing whitespace from last assistant message with multi-part content when there is no further user message', async () => {
822
+ const result = await convertToAnthropicMessagesPrompt({
823
+ prompt: [
824
+ {
825
+ role: 'user',
826
+ content: [{ type: 'text', text: 'user content' }],
827
+ },
828
+ {
829
+ role: 'assistant',
830
+ content: [
831
+ { type: 'text', text: 'assistant ' },
832
+ { type: 'text', text: 'content ' },
833
+ ],
834
+ },
835
+ ],
836
+ sendReasoning: true,
837
+ warnings: [],
838
+ toolNameMapping: defaultToolNameMapping,
839
+ });
840
+
841
+ expect(result).toEqual({
842
+ prompt: {
843
+ messages: [
844
+ {
845
+ role: 'user',
846
+ content: [{ type: 'text', text: 'user content' }],
847
+ },
848
+ {
849
+ role: 'assistant',
850
+ content: [
851
+ { type: 'text', text: 'assistant ' },
852
+ { type: 'text', text: 'content' },
853
+ ],
854
+ },
855
+ ],
856
+ },
857
+ betas: new Set(),
858
+ });
859
+ });
860
+
861
+ it('should keep trailing whitespace from assistant message when there is a further user message', async () => {
862
+ const result = await convertToAnthropicMessagesPrompt({
863
+ prompt: [
864
+ {
865
+ role: 'user',
866
+ content: [{ type: 'text', text: 'user content' }],
867
+ },
868
+ {
869
+ role: 'assistant',
870
+ content: [{ type: 'text', text: 'assistant content ' }],
871
+ },
872
+ {
873
+ role: 'user',
874
+ content: [{ type: 'text', text: 'user content 2' }],
875
+ },
876
+ ],
877
+ sendReasoning: true,
878
+ warnings: [],
879
+ toolNameMapping: defaultToolNameMapping,
880
+ });
881
+
882
+ expect(result).toEqual({
883
+ prompt: {
884
+ messages: [
885
+ {
886
+ role: 'user',
887
+ content: [{ type: 'text', text: 'user content' }],
888
+ },
889
+ {
890
+ role: 'assistant',
891
+ content: [{ type: 'text', text: 'assistant content ' }],
892
+ },
893
+ {
894
+ role: 'user',
895
+ content: [{ type: 'text', text: 'user content 2' }],
896
+ },
897
+ ],
898
+ },
899
+ betas: new Set(),
900
+ });
901
+ });
902
+
903
+ it('should combine multiple sequential assistant messages into a single message', async () => {
904
+ const result = await convertToAnthropicMessagesPrompt({
905
+ prompt: [
906
+ { role: 'user', content: [{ type: 'text', text: 'Hi!' }] },
907
+ { role: 'assistant', content: [{ type: 'text', text: 'Hello' }] },
908
+ { role: 'assistant', content: [{ type: 'text', text: 'World' }] },
909
+ { role: 'assistant', content: [{ type: 'text', text: '!' }] },
910
+ ],
911
+ sendReasoning: true,
912
+ warnings: [],
913
+ toolNameMapping: defaultToolNameMapping,
914
+ });
915
+
916
+ expect(result).toEqual({
917
+ prompt: {
918
+ messages: [
919
+ { role: 'user', content: [{ type: 'text', text: 'Hi!' }] },
920
+ {
921
+ role: 'assistant',
922
+ content: [
923
+ { type: 'text', text: 'Hello' },
924
+ { type: 'text', text: 'World' },
925
+ { type: 'text', text: '!' },
926
+ ],
927
+ },
928
+ ],
929
+ },
930
+ betas: new Set(),
931
+ });
932
+ });
933
+
934
+ it('should convert assistant message reasoning parts with signature into thinking parts when sendReasoning is true', async () => {
935
+ const warnings: SharedV3Warning[] = [];
936
+ const result = await convertToAnthropicMessagesPrompt({
937
+ prompt: [
938
+ {
939
+ role: 'assistant',
940
+ content: [
941
+ {
942
+ type: 'reasoning',
943
+ text: 'I need to count the number of "r"s in the word "strawberry".',
944
+ providerOptions: {
945
+ anthropic: {
946
+ signature: 'test-signature',
947
+ },
948
+ },
949
+ },
950
+ {
951
+ type: 'text',
952
+ text: 'The word "strawberry" has 2 "r"s.',
953
+ },
954
+ ],
955
+ },
956
+ ],
957
+ sendReasoning: true,
958
+ warnings,
959
+ toolNameMapping: defaultToolNameMapping,
960
+ });
961
+
962
+ expect(result).toEqual({
963
+ prompt: {
964
+ messages: [
965
+ {
966
+ role: 'assistant',
967
+ content: [
968
+ {
969
+ type: 'thinking',
970
+ thinking:
971
+ 'I need to count the number of "r"s in the word "strawberry".',
972
+ signature: 'test-signature',
973
+ },
974
+ {
975
+ type: 'text',
976
+ text: 'The word "strawberry" has 2 "r"s.',
977
+ },
978
+ ],
979
+ },
980
+ ],
981
+ },
982
+ betas: new Set(),
983
+ });
984
+ expect(warnings).toEqual([]);
985
+ });
986
+
987
+ it('should ignore reasoning parts without signature into thinking parts when sendReasoning is true', async () => {
988
+ const warnings: SharedV3Warning[] = [];
989
+ const result = await convertToAnthropicMessagesPrompt({
990
+ prompt: [
991
+ {
992
+ role: 'assistant',
993
+ content: [
994
+ {
995
+ type: 'reasoning',
996
+ text: 'I need to count the number of "r"s in the word "strawberry".',
997
+ },
998
+ {
999
+ type: 'text',
1000
+ text: 'The word "strawberry" has 2 "r"s.',
1001
+ },
1002
+ ],
1003
+ },
1004
+ ],
1005
+ sendReasoning: true,
1006
+ warnings,
1007
+ toolNameMapping: defaultToolNameMapping,
1008
+ });
1009
+
1010
+ expect(result).toMatchInlineSnapshot(`
1011
+ {
1012
+ "betas": Set {},
1013
+ "prompt": {
1014
+ "messages": [
1015
+ {
1016
+ "content": [
1017
+ {
1018
+ "cache_control": undefined,
1019
+ "text": "The word "strawberry" has 2 "r"s.",
1020
+ "type": "text",
1021
+ },
1022
+ ],
1023
+ "role": "assistant",
1024
+ },
1025
+ ],
1026
+ "system": undefined,
1027
+ },
1028
+ }
1029
+ `);
1030
+ expect(warnings).toMatchInlineSnapshot(`
1031
+ [
1032
+ {
1033
+ "message": "unsupported reasoning metadata",
1034
+ "type": "other",
1035
+ },
1036
+ ]
1037
+ `);
1038
+ });
1039
+
1040
+ it('should omit assistant message reasoning parts with signature when sendReasoning is false', async () => {
1041
+ const warnings: SharedV3Warning[] = [];
1042
+ const result = await convertToAnthropicMessagesPrompt({
1043
+ prompt: [
1044
+ {
1045
+ role: 'assistant',
1046
+ content: [
1047
+ {
1048
+ type: 'reasoning',
1049
+ text: 'I need to count the number of "r"s in the word "strawberry".',
1050
+ providerOptions: {
1051
+ anthropic: {
1052
+ signature: 'test-signature',
1053
+ },
1054
+ },
1055
+ },
1056
+ {
1057
+ type: 'text',
1058
+ text: 'The word "strawberry" has 2 "r"s.',
1059
+ },
1060
+ ],
1061
+ },
1062
+ ],
1063
+ sendReasoning: false,
1064
+ warnings,
1065
+ toolNameMapping: defaultToolNameMapping,
1066
+ });
1067
+
1068
+ expect(result).toEqual({
1069
+ prompt: {
1070
+ messages: [
1071
+ {
1072
+ role: 'assistant',
1073
+ content: [
1074
+ {
1075
+ type: 'text',
1076
+ text: 'The word "strawberry" has 2 "r"s.',
1077
+ },
1078
+ ],
1079
+ },
1080
+ ],
1081
+ },
1082
+ betas: new Set(),
1083
+ });
1084
+ expect(warnings).toEqual([
1085
+ {
1086
+ type: 'other',
1087
+ message: 'sending reasoning content is disabled for this model',
1088
+ },
1089
+ ]);
1090
+ });
1091
+
1092
+ it('should omit reasoning parts without signature when sendReasoning is false', async () => {
1093
+ const warnings: SharedV3Warning[] = [];
1094
+ const result = await convertToAnthropicMessagesPrompt({
1095
+ prompt: [
1096
+ {
1097
+ role: 'assistant',
1098
+ content: [
1099
+ {
1100
+ type: 'reasoning',
1101
+ text: 'I need to count the number of "r"s in the word "strawberry".',
1102
+ },
1103
+ {
1104
+ type: 'text',
1105
+ text: 'The word "strawberry" has 2 "r"s.',
1106
+ },
1107
+ ],
1108
+ },
1109
+ ],
1110
+ sendReasoning: false,
1111
+ warnings,
1112
+ toolNameMapping: defaultToolNameMapping,
1113
+ });
1114
+
1115
+ expect(result).toEqual({
1116
+ prompt: {
1117
+ messages: [
1118
+ {
1119
+ role: 'assistant',
1120
+ content: [
1121
+ {
1122
+ type: 'text',
1123
+ text: 'The word "strawberry" has 2 "r"s.',
1124
+ },
1125
+ ],
1126
+ },
1127
+ ],
1128
+ },
1129
+ betas: new Set(),
1130
+ });
1131
+ expect(warnings).toEqual([
1132
+ {
1133
+ type: 'other',
1134
+ message: 'sending reasoning content is disabled for this model',
1135
+ },
1136
+ ]);
1137
+ });
1138
+
1139
+ it('should convert anthropic web_search tool call and result parts', async () => {
1140
+ const warnings: SharedV3Warning[] = [];
1141
+ const result = await convertToAnthropicMessagesPrompt({
1142
+ prompt: [
1143
+ {
1144
+ role: 'assistant',
1145
+ content: [
1146
+ {
1147
+ input: {
1148
+ query: 'San Francisco major news events June 22 2025',
1149
+ },
1150
+ providerExecuted: true,
1151
+ toolCallId: 'srvtoolu_011cNtbtzFARKPcAcp7w4nh9',
1152
+ toolName: 'web_search',
1153
+ type: 'tool-call',
1154
+ },
1155
+ {
1156
+ output: {
1157
+ type: 'json',
1158
+ value: [
1159
+ {
1160
+ url: 'https://patch.com/california/san-francisco/calendar',
1161
+ title: 'San Francisco Calendar',
1162
+ pageAge: null,
1163
+ encryptedContent: 'encrypted-content',
1164
+ type: 'web_search_result',
1165
+ },
1166
+ ],
1167
+ },
1168
+ toolCallId: 'srvtoolu_011cNtbtzFARKPcAcp7w4nh9',
1169
+ toolName: 'web_search',
1170
+ type: 'tool-result',
1171
+ },
1172
+ ],
1173
+ },
1174
+ ],
1175
+ sendReasoning: false,
1176
+ warnings,
1177
+ toolNameMapping: defaultToolNameMapping,
1178
+ });
1179
+
1180
+ expect(result).toMatchInlineSnapshot(`
1181
+ {
1182
+ "betas": Set {},
1183
+ "prompt": {
1184
+ "messages": [
1185
+ {
1186
+ "content": [
1187
+ {
1188
+ "cache_control": undefined,
1189
+ "id": "srvtoolu_011cNtbtzFARKPcAcp7w4nh9",
1190
+ "input": {
1191
+ "query": "San Francisco major news events June 22 2025",
1192
+ },
1193
+ "name": "web_search",
1194
+ "type": "server_tool_use",
1195
+ },
1196
+ {
1197
+ "cache_control": undefined,
1198
+ "content": [
1199
+ {
1200
+ "encrypted_content": "encrypted-content",
1201
+ "page_age": null,
1202
+ "title": "San Francisco Calendar",
1203
+ "type": "web_search_result",
1204
+ "url": "https://patch.com/california/san-francisco/calendar",
1205
+ },
1206
+ ],
1207
+ "tool_use_id": "srvtoolu_011cNtbtzFARKPcAcp7w4nh9",
1208
+ "type": "web_search_tool_result",
1209
+ },
1210
+ ],
1211
+ "role": "assistant",
1212
+ },
1213
+ ],
1214
+ "system": undefined,
1215
+ },
1216
+ }
1217
+ `);
1218
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1219
+ });
1220
+
1221
+ it('should convert anthropic web_fetch tool call and result parts', async () => {
1222
+ const warnings: SharedV3Warning[] = [];
1223
+ const result = await convertToAnthropicMessagesPrompt({
1224
+ prompt: [
1225
+ {
1226
+ role: 'assistant',
1227
+ content: [
1228
+ {
1229
+ input: {
1230
+ url: 'https://raw.githubusercontent.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf',
1231
+ },
1232
+ providerExecuted: true,
1233
+ toolCallId: 'srvtoolu_011cNtbtzFARKPcAcp7w4nh9',
1234
+ toolName: 'web_fetch',
1235
+ type: 'tool-call',
1236
+ },
1237
+ {
1238
+ output: {
1239
+ type: 'json',
1240
+ value: {
1241
+ type: 'web_fetch_result',
1242
+ url: 'https://raw.githubusercontent.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf',
1243
+ retrievedAt: '2025-01-01T00:00:00.000Z',
1244
+ content: {
1245
+ type: 'document',
1246
+ title: 'AI.pdf',
1247
+ citations: { enabled: true },
1248
+ source: {
1249
+ type: 'text',
1250
+ mediaType: 'text/plain',
1251
+ data: 'The PDF says about AI.',
1252
+ },
1253
+ },
1254
+ },
1255
+ },
1256
+ toolCallId: 'srvtoolu_011cNtbtzFARKPcAcp7w4nh9',
1257
+ toolName: 'web_fetch',
1258
+ type: 'tool-result',
1259
+ },
1260
+ ],
1261
+ },
1262
+ ],
1263
+ sendReasoning: false,
1264
+ warnings,
1265
+ toolNameMapping: defaultToolNameMapping,
1266
+ });
1267
+
1268
+ expect(result).toMatchInlineSnapshot(`
1269
+ {
1270
+ "betas": Set {},
1271
+ "prompt": {
1272
+ "messages": [
1273
+ {
1274
+ "content": [
1275
+ {
1276
+ "cache_control": undefined,
1277
+ "id": "srvtoolu_011cNtbtzFARKPcAcp7w4nh9",
1278
+ "input": {
1279
+ "url": "https://raw.githubusercontent.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf",
1280
+ },
1281
+ "name": "web_fetch",
1282
+ "type": "server_tool_use",
1283
+ },
1284
+ {
1285
+ "cache_control": undefined,
1286
+ "content": {
1287
+ "content": {
1288
+ "citations": {
1289
+ "enabled": true,
1290
+ },
1291
+ "source": {
1292
+ "data": "The PDF says about AI.",
1293
+ "media_type": "text/plain",
1294
+ "type": "text",
1295
+ },
1296
+ "title": "AI.pdf",
1297
+ "type": "document",
1298
+ },
1299
+ "retrieved_at": "2025-01-01T00:00:00.000Z",
1300
+ "type": "web_fetch_result",
1301
+ "url": "https://raw.githubusercontent.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf",
1302
+ },
1303
+ "tool_use_id": "srvtoolu_011cNtbtzFARKPcAcp7w4nh9",
1304
+ "type": "web_fetch_tool_result",
1305
+ },
1306
+ ],
1307
+ "role": "assistant",
1308
+ },
1309
+ ],
1310
+ "system": undefined,
1311
+ },
1312
+ }
1313
+ `);
1314
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1315
+ });
1316
+
1317
+ it('should convert anthropic web_fetch tool call with error result', async () => {
1318
+ const warnings: SharedV3Warning[] = [];
1319
+ const result = await convertToAnthropicMessagesPrompt({
1320
+ prompt: [
1321
+ {
1322
+ role: 'assistant',
1323
+ content: [
1324
+ {
1325
+ input: {
1326
+ url: 'https://httpbin.org/status/500',
1327
+ },
1328
+ providerExecuted: true,
1329
+ toolCallId: 'srvtoolu_016yTvwN6L1sDdjdPUzPbZRV',
1330
+ toolName: 'web_fetch',
1331
+ type: 'tool-call',
1332
+ },
1333
+ {
1334
+ output: {
1335
+ type: 'error-json',
1336
+ value: JSON.stringify({
1337
+ type: 'web_fetch_tool_result_error',
1338
+ errorCode: 'url_not_accessible',
1339
+ }),
1340
+ },
1341
+ toolCallId: 'srvtoolu_016yTvwN6L1sDdjdPUzPbZRV',
1342
+ toolName: 'web_fetch',
1343
+ type: 'tool-result',
1344
+ },
1345
+ ],
1346
+ },
1347
+ ],
1348
+ sendReasoning: false,
1349
+ warnings,
1350
+ toolNameMapping: defaultToolNameMapping,
1351
+ });
1352
+
1353
+ expect(result).toMatchInlineSnapshot(`
1354
+ {
1355
+ "betas": Set {},
1356
+ "prompt": {
1357
+ "messages": [
1358
+ {
1359
+ "content": [
1360
+ {
1361
+ "cache_control": undefined,
1362
+ "id": "srvtoolu_016yTvwN6L1sDdjdPUzPbZRV",
1363
+ "input": {
1364
+ "url": "https://httpbin.org/status/500",
1365
+ },
1366
+ "name": "web_fetch",
1367
+ "type": "server_tool_use",
1368
+ },
1369
+ {
1370
+ "cache_control": undefined,
1371
+ "content": {
1372
+ "error_code": "url_not_accessible",
1373
+ "type": "web_fetch_tool_result_error",
1374
+ },
1375
+ "tool_use_id": "srvtoolu_016yTvwN6L1sDdjdPUzPbZRV",
1376
+ "type": "web_fetch_tool_result",
1377
+ },
1378
+ ],
1379
+ "role": "assistant",
1380
+ },
1381
+ ],
1382
+ "system": undefined,
1383
+ },
1384
+ }
1385
+ `);
1386
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1387
+ });
1388
+
1389
+ it('should convert anthropic web_fetch tool call with error result as object', async () => {
1390
+ const warnings: SharedV3Warning[] = [];
1391
+ const result = await convertToAnthropicMessagesPrompt({
1392
+ prompt: [
1393
+ {
1394
+ role: 'assistant',
1395
+ content: [
1396
+ {
1397
+ input: {
1398
+ url: 'https://www.fotball.no/fotballdata/turnering/hjem/?fiksId=193156',
1399
+ },
1400
+ providerExecuted: true,
1401
+ toolCallId: 'srvtoolu_01JteKo9VRHDKZ1rdMXywnwD',
1402
+ toolName: 'web_fetch',
1403
+ type: 'tool-call',
1404
+ },
1405
+ {
1406
+ output: {
1407
+ type: 'error-json',
1408
+ value: {
1409
+ type: 'web_fetch_tool_result_error',
1410
+ errorCode: 'url_not_allowed',
1411
+ },
1412
+ },
1413
+ toolCallId: 'srvtoolu_01JteKo9VRHDKZ1rdMXywnwD',
1414
+ toolName: 'web_fetch',
1415
+ type: 'tool-result',
1416
+ },
1417
+ ],
1418
+ },
1419
+ ],
1420
+ sendReasoning: false,
1421
+ warnings,
1422
+ toolNameMapping: defaultToolNameMapping,
1423
+ });
1424
+
1425
+ expect(result).toMatchInlineSnapshot(`
1426
+ {
1427
+ "betas": Set {},
1428
+ "prompt": {
1429
+ "messages": [
1430
+ {
1431
+ "content": [
1432
+ {
1433
+ "cache_control": undefined,
1434
+ "id": "srvtoolu_01JteKo9VRHDKZ1rdMXywnwD",
1435
+ "input": {
1436
+ "url": "https://www.fotball.no/fotballdata/turnering/hjem/?fiksId=193156",
1437
+ },
1438
+ "name": "web_fetch",
1439
+ "type": "server_tool_use",
1440
+ },
1441
+ {
1442
+ "cache_control": undefined,
1443
+ "content": {
1444
+ "error_code": "url_not_allowed",
1445
+ "type": "web_fetch_tool_result_error",
1446
+ },
1447
+ "tool_use_id": "srvtoolu_01JteKo9VRHDKZ1rdMXywnwD",
1448
+ "type": "web_fetch_tool_result",
1449
+ },
1450
+ ],
1451
+ "role": "assistant",
1452
+ },
1453
+ ],
1454
+ "system": undefined,
1455
+ },
1456
+ }
1457
+ `);
1458
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1459
+ });
1460
+
1461
+ it('should convert anthropic web_fetch tool call with error result as malformed string', async () => {
1462
+ const warnings: SharedV3Warning[] = [];
1463
+ const result = await convertToAnthropicMessagesPrompt({
1464
+ prompt: [
1465
+ {
1466
+ role: 'assistant',
1467
+ content: [
1468
+ {
1469
+ input: {
1470
+ url: 'https://example.com',
1471
+ },
1472
+ providerExecuted: true,
1473
+ toolCallId: 'srvtoolu_test123',
1474
+ toolName: 'web_fetch',
1475
+ type: 'tool-call',
1476
+ },
1477
+ {
1478
+ output: {
1479
+ type: 'error-json',
1480
+ value: 'not valid json at all',
1481
+ },
1482
+ toolCallId: 'srvtoolu_test123',
1483
+ toolName: 'web_fetch',
1484
+ type: 'tool-result',
1485
+ },
1486
+ ],
1487
+ },
1488
+ ],
1489
+ sendReasoning: false,
1490
+ warnings,
1491
+ toolNameMapping: defaultToolNameMapping,
1492
+ });
1493
+
1494
+ expect(result.prompt.messages[0].content[1]).toMatchInlineSnapshot(`
1495
+ {
1496
+ "cache_control": undefined,
1497
+ "content": {
1498
+ "error_code": "unknown",
1499
+ "type": "web_fetch_tool_result_error",
1500
+ },
1501
+ "tool_use_id": "srvtoolu_test123",
1502
+ "type": "web_fetch_tool_result",
1503
+ }
1504
+ `);
1505
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1506
+ });
1507
+
1508
+ it('should convert anthropic tool_search_tool_regex tool call and result parts', async () => {
1509
+ const warnings: SharedV3Warning[] = [];
1510
+ const result = await convertToAnthropicMessagesPrompt({
1511
+ prompt: [
1512
+ {
1513
+ role: 'assistant',
1514
+ content: [
1515
+ {
1516
+ input: {
1517
+ pattern: 'weather|forecast',
1518
+ limit: 10,
1519
+ },
1520
+ providerExecuted: true,
1521
+ toolCallId: 'srvtoolu_01SACvPAnp6ucMJsstB5qb3f',
1522
+ toolName: 'tool_search_tool_regex',
1523
+ type: 'tool-call',
1524
+ },
1525
+ {
1526
+ output: {
1527
+ type: 'json',
1528
+ value: [
1529
+ {
1530
+ type: 'tool_reference',
1531
+ toolName: 'get_weather',
1532
+ },
1533
+ ],
1534
+ },
1535
+ toolCallId: 'srvtoolu_01SACvPAnp6ucMJsstB5qb3f',
1536
+ toolName: 'tool_search_tool_regex',
1537
+ type: 'tool-result',
1538
+ },
1539
+ ],
1540
+ },
1541
+ ],
1542
+ sendReasoning: false,
1543
+ warnings,
1544
+ toolNameMapping: defaultToolNameMapping,
1545
+ });
1546
+
1547
+ expect(result).toMatchInlineSnapshot(`
1548
+ {
1549
+ "betas": Set {},
1550
+ "prompt": {
1551
+ "messages": [
1552
+ {
1553
+ "content": [
1554
+ {
1555
+ "cache_control": undefined,
1556
+ "id": "srvtoolu_01SACvPAnp6ucMJsstB5qb3f",
1557
+ "input": {
1558
+ "limit": 10,
1559
+ "pattern": "weather|forecast",
1560
+ },
1561
+ "name": "tool_search_tool_regex",
1562
+ "type": "server_tool_use",
1563
+ },
1564
+ {
1565
+ "cache_control": undefined,
1566
+ "content": {
1567
+ "tool_references": [
1568
+ {
1569
+ "tool_name": "get_weather",
1570
+ "type": "tool_reference",
1571
+ },
1572
+ ],
1573
+ "type": "tool_search_tool_search_result",
1574
+ },
1575
+ "tool_use_id": "srvtoolu_01SACvPAnp6ucMJsstB5qb3f",
1576
+ "type": "tool_search_tool_result",
1577
+ },
1578
+ ],
1579
+ "role": "assistant",
1580
+ },
1581
+ ],
1582
+ "system": undefined,
1583
+ },
1584
+ }
1585
+ `);
1586
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1587
+ });
1588
+
1589
+ describe('code_execution 20250522', () => {
1590
+ it('should convert anthropic code_execution tool call and result parts', async () => {
1591
+ const warnings: SharedV3Warning[] = [];
1592
+ const result = await convertToAnthropicMessagesPrompt({
1593
+ prompt: [
1594
+ {
1595
+ role: 'assistant',
1596
+ content: [
1597
+ {
1598
+ input: {
1599
+ code: 'print("Hello, world!")',
1600
+ },
1601
+ providerExecuted: true,
1602
+ toolCallId: 'srvtoolu_01XyZ1234567890',
1603
+ toolName: 'code_execution',
1604
+ type: 'tool-call',
1605
+ },
1606
+ {
1607
+ output: {
1608
+ type: 'json',
1609
+ value: {
1610
+ type: 'code_execution_result',
1611
+ stdout: 'Hello, world!',
1612
+ stderr: '',
1613
+ return_code: 0,
1614
+ },
1615
+ },
1616
+ toolCallId: 'srvtoolu_01XyZ1234567890',
1617
+ toolName: 'code_execution',
1618
+ type: 'tool-result',
1619
+ },
1620
+ ],
1621
+ },
1622
+ ],
1623
+ sendReasoning: false,
1624
+ warnings,
1625
+ toolNameMapping: defaultToolNameMapping,
1626
+ });
1627
+
1628
+ expect(result).toMatchInlineSnapshot(`
1629
+ {
1630
+ "betas": Set {},
1631
+ "prompt": {
1632
+ "messages": [
1633
+ {
1634
+ "content": [
1635
+ {
1636
+ "cache_control": undefined,
1637
+ "id": "srvtoolu_01XyZ1234567890",
1638
+ "input": {
1639
+ "code": "print("Hello, world!")",
1640
+ },
1641
+ "name": "code_execution",
1642
+ "type": "server_tool_use",
1643
+ },
1644
+ {
1645
+ "cache_control": undefined,
1646
+ "content": {
1647
+ "content": [],
1648
+ "return_code": 0,
1649
+ "stderr": "",
1650
+ "stdout": "Hello, world!",
1651
+ "type": "code_execution_result",
1652
+ },
1653
+ "tool_use_id": "srvtoolu_01XyZ1234567890",
1654
+ "type": "code_execution_tool_result",
1655
+ },
1656
+ ],
1657
+ "role": "assistant",
1658
+ },
1659
+ ],
1660
+ "system": undefined,
1661
+ },
1662
+ }
1663
+ `);
1664
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1665
+ });
1666
+ });
1667
+
1668
+ describe('code_execution 20250825', () => {
1669
+ it('should convert anthropic code_execution tool call and result parts', async () => {
1670
+ const warnings: SharedV3Warning[] = [];
1671
+ const result = await convertToAnthropicMessagesPrompt({
1672
+ prompt: [
1673
+ {
1674
+ role: 'assistant',
1675
+ content: [
1676
+ {
1677
+ type: 'tool-call',
1678
+ toolCallId: 'srvtoolu_01Hq9rR6fZwwDGHkTYRafn7k',
1679
+ toolName: 'code_execution',
1680
+ input: {
1681
+ type: 'text_editor_code_execution',
1682
+ command: 'create',
1683
+ path: '/tmp/fibonacci.py',
1684
+ file_text: 'def..',
1685
+ },
1686
+ providerExecuted: true,
1687
+ },
1688
+ {
1689
+ type: 'tool-result',
1690
+ toolCallId: 'srvtoolu_01Hq9rR6fZwwDGHkTYRafn7k',
1691
+ toolName: 'code_execution',
1692
+ output: {
1693
+ type: 'json',
1694
+ value: {
1695
+ type: 'text_editor_code_execution_create_result',
1696
+ is_file_update: false,
1697
+ },
1698
+ },
1699
+ },
1700
+ {
1701
+ type: 'tool-call',
1702
+ toolCallId: 'srvtoolu_0193G3ttnkiTfZASwHQSKc2V',
1703
+ toolName: 'code_execution',
1704
+ input: {
1705
+ type: 'bash_code_execution',
1706
+ command: 'python /tmp/fibonacci.py',
1707
+ },
1708
+ providerExecuted: true,
1709
+ },
1710
+ {
1711
+ type: 'tool-result',
1712
+ toolCallId: 'srvtoolu_0193G3ttnkiTfZASwHQSKc2V',
1713
+ toolName: 'code_execution',
1714
+ output: {
1715
+ type: 'json',
1716
+ value: {
1717
+ type: 'bash_code_execution_result',
1718
+ content: [],
1719
+ stdout: 'The 10th Fibonacci number is: 34\n',
1720
+ stderr: '',
1721
+ return_code: 0,
1722
+ },
1723
+ },
1724
+ },
1725
+ ],
1726
+ },
1727
+ ],
1728
+ sendReasoning: false,
1729
+ warnings,
1730
+ toolNameMapping: defaultToolNameMapping,
1731
+ });
1732
+
1733
+ expect(result).toMatchInlineSnapshot(`
1734
+ {
1735
+ "betas": Set {},
1736
+ "prompt": {
1737
+ "messages": [
1738
+ {
1739
+ "content": [
1740
+ {
1741
+ "cache_control": undefined,
1742
+ "id": "srvtoolu_01Hq9rR6fZwwDGHkTYRafn7k",
1743
+ "input": {
1744
+ "command": "create",
1745
+ "file_text": "def..",
1746
+ "path": "/tmp/fibonacci.py",
1747
+ "type": "text_editor_code_execution",
1748
+ },
1749
+ "name": "text_editor_code_execution",
1750
+ "type": "server_tool_use",
1751
+ },
1752
+ {
1753
+ "cache_control": undefined,
1754
+ "content": {
1755
+ "is_file_update": false,
1756
+ "type": "text_editor_code_execution_create_result",
1757
+ },
1758
+ "tool_use_id": "srvtoolu_01Hq9rR6fZwwDGHkTYRafn7k",
1759
+ "type": "text_editor_code_execution_tool_result",
1760
+ },
1761
+ {
1762
+ "cache_control": undefined,
1763
+ "id": "srvtoolu_0193G3ttnkiTfZASwHQSKc2V",
1764
+ "input": {
1765
+ "command": "python /tmp/fibonacci.py",
1766
+ "type": "bash_code_execution",
1767
+ },
1768
+ "name": "bash_code_execution",
1769
+ "type": "server_tool_use",
1770
+ },
1771
+ {
1772
+ "cache_control": undefined,
1773
+ "content": {
1774
+ "content": [],
1775
+ "return_code": 0,
1776
+ "stderr": "",
1777
+ "stdout": "The 10th Fibonacci number is: 34
1778
+ ",
1779
+ "type": "bash_code_execution_result",
1780
+ },
1781
+ "tool_use_id": "srvtoolu_0193G3ttnkiTfZASwHQSKc2V",
1782
+ "type": "bash_code_execution_tool_result",
1783
+ },
1784
+ ],
1785
+ "role": "assistant",
1786
+ },
1787
+ ],
1788
+ "system": undefined,
1789
+ },
1790
+ }
1791
+ `);
1792
+ expect(warnings).toMatchInlineSnapshot(`[]`);
1793
+ });
1794
+ });
1795
+
1796
+ describe('mcp tool use', () => {
1797
+ it('should convert anthropic mcp tool use parts', async () => {
1798
+ const warnings: SharedV3Warning[] = [];
1799
+ const result = await convertToAnthropicMessagesPrompt({
1800
+ prompt: [
1801
+ {
1802
+ role: 'assistant',
1803
+ content: [
1804
+ {
1805
+ type: 'tool-call',
1806
+ toolCallId: 'mcptoolu_01HXPYHs79HH36fBbKHysCrp',
1807
+ toolName: 'echo',
1808
+ input: {},
1809
+ providerExecuted: true,
1810
+ providerOptions: {
1811
+ anthropic: { type: 'mcp-tool-use', serverName: 'echo' },
1812
+ },
1813
+ },
1814
+ {
1815
+ type: 'tool-result',
1816
+ toolCallId: 'mcptoolu_01HXPYHs79HH36fBbKHysCrp',
1817
+ toolName: 'echo',
1818
+ output: {
1819
+ type: 'json',
1820
+ value: [{ type: 'text', text: 'Tool echo: hello world' }],
1821
+ },
1822
+ providerOptions: undefined,
1823
+ },
1824
+ {
1825
+ type: 'text',
1826
+ text: 'The echo tool responded back with "hello world" - it simply echoed the message I sent to it!',
1827
+ providerOptions: undefined,
1828
+ },
1829
+ ],
1830
+ providerOptions: undefined,
1831
+ },
1832
+ ],
1833
+ sendReasoning: false,
1834
+ warnings,
1835
+ toolNameMapping: defaultToolNameMapping,
1836
+ });
1837
+
1838
+ expect(result).toMatchInlineSnapshot(`
1839
+ {
1840
+ "betas": Set {},
1841
+ "prompt": {
1842
+ "messages": [
1843
+ {
1844
+ "content": [
1845
+ {
1846
+ "cache_control": undefined,
1847
+ "id": "mcptoolu_01HXPYHs79HH36fBbKHysCrp",
1848
+ "input": {},
1849
+ "name": "echo",
1850
+ "server_name": "echo",
1851
+ "type": "mcp_tool_use",
1852
+ },
1853
+ {
1854
+ "cache_control": undefined,
1855
+ "content": [
1856
+ {
1857
+ "text": "Tool echo: hello world",
1858
+ "type": "text",
1859
+ },
1860
+ ],
1861
+ "is_error": false,
1862
+ "tool_use_id": "mcptoolu_01HXPYHs79HH36fBbKHysCrp",
1863
+ "type": "mcp_tool_result",
1864
+ },
1865
+ {
1866
+ "cache_control": undefined,
1867
+ "text": "The echo tool responded back with "hello world" - it simply echoed the message I sent to it!",
1868
+ "type": "text",
1869
+ },
1870
+ ],
1871
+ "role": "assistant",
1872
+ },
1873
+ ],
1874
+ "system": undefined,
1875
+ },
1876
+ }
1877
+ `);
1878
+ expect(warnings).toMatchInlineSnapshot(`
1879
+ [
1880
+ {
1881
+ "message": "provider executed tool result for tool echo is not supported",
1882
+ "type": "other",
1883
+ },
1884
+ ]
1885
+ `);
1886
+ });
1887
+ });
1888
+ });
1889
+
1890
+ describe('cache control', () => {
1891
+ describe('system message', () => {
1892
+ it('should set cache_control on system message with message cache control', async () => {
1893
+ const result = await convertToAnthropicMessagesPrompt({
1894
+ prompt: [
1895
+ {
1896
+ role: 'system',
1897
+ content: 'system message',
1898
+ providerOptions: {
1899
+ anthropic: { cacheControl: { type: 'ephemeral' } },
1900
+ },
1901
+ },
1902
+ ],
1903
+ sendReasoning: true,
1904
+ warnings: [],
1905
+ toolNameMapping: defaultToolNameMapping,
1906
+ });
1907
+
1908
+ expect(result).toEqual({
1909
+ prompt: {
1910
+ messages: [],
1911
+ system: [
1912
+ {
1913
+ type: 'text',
1914
+ text: 'system message',
1915
+ cache_control: { type: 'ephemeral' },
1916
+ },
1917
+ ],
1918
+ },
1919
+ betas: new Set(),
1920
+ });
1921
+ });
1922
+ });
1923
+
1924
+ describe('user message', () => {
1925
+ it('should set cache_control on user message part with part cache control', async () => {
1926
+ const result = await convertToAnthropicMessagesPrompt({
1927
+ prompt: [
1928
+ {
1929
+ role: 'user',
1930
+ content: [
1931
+ {
1932
+ type: 'text',
1933
+ text: 'test',
1934
+ providerOptions: {
1935
+ anthropic: {
1936
+ cacheControl: { type: 'ephemeral' },
1937
+ },
1938
+ },
1939
+ },
1940
+ ],
1941
+ },
1942
+ ],
1943
+ sendReasoning: true,
1944
+ warnings: [],
1945
+ toolNameMapping: defaultToolNameMapping,
1946
+ });
1947
+
1948
+ expect(result).toEqual({
1949
+ prompt: {
1950
+ messages: [
1951
+ {
1952
+ role: 'user',
1953
+ content: [
1954
+ {
1955
+ type: 'text',
1956
+ text: 'test',
1957
+ cache_control: { type: 'ephemeral' },
1958
+ },
1959
+ ],
1960
+ },
1961
+ ],
1962
+ },
1963
+ betas: new Set(),
1964
+ });
1965
+ });
1966
+
1967
+ it('should set cache_control on last user message part with message cache control', async () => {
1968
+ const result = await convertToAnthropicMessagesPrompt({
1969
+ prompt: [
1970
+ {
1971
+ role: 'user',
1972
+ content: [
1973
+ { type: 'text', text: 'part1' },
1974
+ { type: 'text', text: 'part2' },
1975
+ ],
1976
+ providerOptions: {
1977
+ anthropic: {
1978
+ cacheControl: { type: 'ephemeral' },
1979
+ },
1980
+ },
1981
+ },
1982
+ ],
1983
+ sendReasoning: true,
1984
+ warnings: [],
1985
+ toolNameMapping: defaultToolNameMapping,
1986
+ });
1987
+
1988
+ expect(result).toEqual({
1989
+ prompt: {
1990
+ messages: [
1991
+ {
1992
+ role: 'user',
1993
+ content: [
1994
+ {
1995
+ type: 'text',
1996
+ text: 'part1',
1997
+ cache_control: undefined,
1998
+ },
1999
+ {
2000
+ type: 'text',
2001
+ text: 'part2',
2002
+ cache_control: { type: 'ephemeral' },
2003
+ },
2004
+ ],
2005
+ },
2006
+ ],
2007
+ },
2008
+ betas: new Set(),
2009
+ });
2010
+ });
2011
+ });
2012
+
2013
+ describe('assistant message', () => {
2014
+ it('should set cache_control on assistant message text part with part cache control', async () => {
2015
+ const result = await convertToAnthropicMessagesPrompt({
2016
+ prompt: [
2017
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2018
+ {
2019
+ role: 'assistant',
2020
+ content: [
2021
+ {
2022
+ type: 'text',
2023
+ text: 'test',
2024
+ providerOptions: {
2025
+ anthropic: {
2026
+ cacheControl: { type: 'ephemeral' },
2027
+ },
2028
+ },
2029
+ },
2030
+ ],
2031
+ },
2032
+ ],
2033
+ sendReasoning: true,
2034
+ warnings: [],
2035
+ toolNameMapping: defaultToolNameMapping,
2036
+ });
2037
+
2038
+ expect(result).toEqual({
2039
+ prompt: {
2040
+ messages: [
2041
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2042
+ {
2043
+ role: 'assistant',
2044
+ content: [
2045
+ {
2046
+ type: 'text',
2047
+ text: 'test',
2048
+ cache_control: { type: 'ephemeral' },
2049
+ },
2050
+ ],
2051
+ },
2052
+ ],
2053
+ },
2054
+ betas: new Set(),
2055
+ });
2056
+ });
2057
+
2058
+ it('should set cache_control on assistant tool call part with part cache control', async () => {
2059
+ const result = await convertToAnthropicMessagesPrompt({
2060
+ prompt: [
2061
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2062
+ {
2063
+ role: 'assistant',
2064
+ content: [
2065
+ {
2066
+ type: 'tool-call',
2067
+ toolCallId: 'test-id',
2068
+ toolName: 'test-tool',
2069
+ input: { some: 'arg' },
2070
+ providerOptions: {
2071
+ anthropic: {
2072
+ cacheControl: { type: 'ephemeral' },
2073
+ },
2074
+ },
2075
+ },
2076
+ ],
2077
+ },
2078
+ ],
2079
+ sendReasoning: true,
2080
+ warnings: [],
2081
+ toolNameMapping: defaultToolNameMapping,
2082
+ });
2083
+
2084
+ expect(result).toEqual({
2085
+ prompt: {
2086
+ messages: [
2087
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2088
+ {
2089
+ role: 'assistant',
2090
+ content: [
2091
+ {
2092
+ type: 'tool_use',
2093
+ name: 'test-tool',
2094
+ id: 'test-id',
2095
+ input: { some: 'arg' },
2096
+ cache_control: { type: 'ephemeral' },
2097
+ },
2098
+ ],
2099
+ },
2100
+ ],
2101
+ },
2102
+ betas: new Set(),
2103
+ });
2104
+ });
2105
+
2106
+ it('should set cache_control on last assistant message part with message cache control', async () => {
2107
+ const result = await convertToAnthropicMessagesPrompt({
2108
+ prompt: [
2109
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2110
+ {
2111
+ role: 'assistant',
2112
+ content: [
2113
+ { type: 'text', text: 'part1' },
2114
+ { type: 'text', text: 'part2' },
2115
+ ],
2116
+ providerOptions: {
2117
+ anthropic: {
2118
+ cacheControl: { type: 'ephemeral' },
2119
+ },
2120
+ },
2121
+ },
2122
+ ],
2123
+ sendReasoning: true,
2124
+ warnings: [],
2125
+ toolNameMapping: defaultToolNameMapping,
2126
+ });
2127
+
2128
+ expect(result).toEqual({
2129
+ prompt: {
2130
+ messages: [
2131
+ { role: 'user', content: [{ type: 'text', text: 'user-content' }] },
2132
+ {
2133
+ role: 'assistant',
2134
+ content: [
2135
+ {
2136
+ type: 'text',
2137
+ text: 'part1',
2138
+ cache_control: undefined,
2139
+ },
2140
+ {
2141
+ type: 'text',
2142
+ text: 'part2',
2143
+ cache_control: { type: 'ephemeral' },
2144
+ },
2145
+ ],
2146
+ },
2147
+ ],
2148
+ },
2149
+ betas: new Set(),
2150
+ });
2151
+ });
2152
+ });
2153
+
2154
+ describe('tool message', () => {
2155
+ it('should set cache_control on tool result message part with part cache control', async () => {
2156
+ const result = await convertToAnthropicMessagesPrompt({
2157
+ prompt: [
2158
+ {
2159
+ role: 'tool',
2160
+ content: [
2161
+ {
2162
+ type: 'tool-result',
2163
+ toolName: 'test',
2164
+ toolCallId: 'test',
2165
+ output: { type: 'json', value: { test: 'test' } },
2166
+ providerOptions: {
2167
+ anthropic: {
2168
+ cacheControl: { type: 'ephemeral' },
2169
+ },
2170
+ },
2171
+ },
2172
+ ],
2173
+ },
2174
+ ],
2175
+ sendReasoning: true,
2176
+ warnings: [],
2177
+ toolNameMapping: defaultToolNameMapping,
2178
+ });
2179
+
2180
+ expect(result).toEqual({
2181
+ prompt: {
2182
+ messages: [
2183
+ {
2184
+ role: 'user',
2185
+ content: [
2186
+ {
2187
+ type: 'tool_result',
2188
+ content: '{"test":"test"}',
2189
+ is_error: undefined,
2190
+ tool_use_id: 'test',
2191
+ cache_control: { type: 'ephemeral' },
2192
+ },
2193
+ ],
2194
+ },
2195
+ ],
2196
+ },
2197
+ betas: new Set(),
2198
+ });
2199
+ });
2200
+
2201
+ it('should set cache_control on last tool result message part with message cache control', async () => {
2202
+ const result = await convertToAnthropicMessagesPrompt({
2203
+ prompt: [
2204
+ {
2205
+ role: 'tool',
2206
+ content: [
2207
+ {
2208
+ type: 'tool-result',
2209
+ toolName: 'test',
2210
+ toolCallId: 'part1',
2211
+ output: { type: 'json', value: { test: 'part1' } },
2212
+ },
2213
+ {
2214
+ type: 'tool-result',
2215
+ toolName: 'test',
2216
+ toolCallId: 'part2',
2217
+ output: { type: 'json', value: { test: 'part2' } },
2218
+ },
2219
+ ],
2220
+ providerOptions: {
2221
+ anthropic: {
2222
+ cacheControl: { type: 'ephemeral' },
2223
+ },
2224
+ },
2225
+ },
2226
+ ],
2227
+ sendReasoning: true,
2228
+ warnings: [],
2229
+ toolNameMapping: defaultToolNameMapping,
2230
+ });
2231
+
2232
+ expect(result).toEqual({
2233
+ prompt: {
2234
+ messages: [
2235
+ {
2236
+ role: 'user',
2237
+ content: [
2238
+ {
2239
+ type: 'tool_result',
2240
+ tool_use_id: 'part1',
2241
+ content: '{"test":"part1"}',
2242
+ is_error: undefined,
2243
+ cache_control: undefined,
2244
+ },
2245
+ {
2246
+ type: 'tool_result',
2247
+ tool_use_id: 'part2',
2248
+ content: '{"test":"part2"}',
2249
+ is_error: undefined,
2250
+ cache_control: { type: 'ephemeral' },
2251
+ },
2252
+ ],
2253
+ },
2254
+ ],
2255
+ },
2256
+ betas: new Set(),
2257
+ });
2258
+ });
2259
+ });
2260
+
2261
+ describe('cache control validation', () => {
2262
+ it('should reject cache_control on thinking blocks', async () => {
2263
+ const warnings: SharedV3Warning[] = [];
2264
+ const cacheControlValidator = new CacheControlValidator();
2265
+ const result = await convertToAnthropicMessagesPrompt({
2266
+ prompt: [
2267
+ {
2268
+ role: 'assistant',
2269
+ content: [
2270
+ {
2271
+ type: 'reasoning',
2272
+ text: 'thinking content',
2273
+ providerOptions: {
2274
+ anthropic: {
2275
+ signature: 'test-sig',
2276
+ cacheControl: { type: 'ephemeral' },
2277
+ },
2278
+ },
2279
+ },
2280
+ ],
2281
+ },
2282
+ ],
2283
+ sendReasoning: true,
2284
+ warnings,
2285
+ cacheControlValidator,
2286
+ toolNameMapping: defaultToolNameMapping,
2287
+ });
2288
+
2289
+ expect(result).toEqual({
2290
+ prompt: {
2291
+ messages: [
2292
+ {
2293
+ role: 'assistant',
2294
+ content: [
2295
+ {
2296
+ type: 'thinking',
2297
+ thinking: 'thinking content',
2298
+ signature: 'test-sig',
2299
+ },
2300
+ ],
2301
+ },
2302
+ ],
2303
+ },
2304
+ betas: new Set(),
2305
+ });
2306
+
2307
+ expect(cacheControlValidator.getWarnings()).toMatchInlineSnapshot(`
2308
+ [
2309
+ {
2310
+ "details": "cache_control cannot be set on thinking block. It will be ignored.",
2311
+ "feature": "cache_control on non-cacheable context",
2312
+ "type": "unsupported",
2313
+ },
2314
+ ]
2315
+ `);
2316
+ });
2317
+
2318
+ it('should reject cache_control on redacted thinking blocks', async () => {
2319
+ const warnings: SharedV3Warning[] = [];
2320
+ const cacheControlValidator = new CacheControlValidator();
2321
+ const result = await convertToAnthropicMessagesPrompt({
2322
+ prompt: [
2323
+ {
2324
+ role: 'assistant',
2325
+ content: [
2326
+ {
2327
+ type: 'reasoning',
2328
+ text: 'redacted',
2329
+ providerOptions: {
2330
+ anthropic: {
2331
+ redactedData: 'abc123',
2332
+ cacheControl: { type: 'ephemeral' },
2333
+ },
2334
+ },
2335
+ },
2336
+ ],
2337
+ },
2338
+ ],
2339
+ sendReasoning: true,
2340
+ warnings,
2341
+ cacheControlValidator,
2342
+ toolNameMapping: defaultToolNameMapping,
2343
+ });
2344
+
2345
+ expect(result.prompt.messages[0].content[0]).not.toHaveProperty(
2346
+ 'cache_control',
2347
+ );
2348
+
2349
+ expect(cacheControlValidator.getWarnings()).toMatchInlineSnapshot(`
2350
+ [
2351
+ {
2352
+ "details": "cache_control cannot be set on redacted thinking block. It will be ignored.",
2353
+ "feature": "cache_control on non-cacheable context",
2354
+ "type": "unsupported",
2355
+ },
2356
+ ]
2357
+ `);
2358
+ });
2359
+ });
2360
+
2361
+ it('should limit cache breakpoints to 4', async () => {
2362
+ const warnings: SharedV3Warning[] = [];
2363
+ const cacheControlValidator = new CacheControlValidator();
2364
+ const result = await convertToAnthropicMessagesPrompt({
2365
+ prompt: [
2366
+ {
2367
+ role: 'system',
2368
+ content: 'system 1',
2369
+ providerOptions: {
2370
+ anthropic: { cacheControl: { type: 'ephemeral' } },
2371
+ },
2372
+ },
2373
+ {
2374
+ role: 'system',
2375
+ content: 'system 2',
2376
+ providerOptions: {
2377
+ anthropic: { cacheControl: { type: 'ephemeral' } },
2378
+ },
2379
+ },
2380
+ {
2381
+ role: 'user',
2382
+ content: [
2383
+ {
2384
+ type: 'text',
2385
+ text: 'user 1',
2386
+ providerOptions: {
2387
+ anthropic: { cacheControl: { type: 'ephemeral' } },
2388
+ },
2389
+ },
2390
+ ],
2391
+ },
2392
+ {
2393
+ role: 'assistant',
2394
+ content: [
2395
+ {
2396
+ type: 'text',
2397
+ text: 'assistant 1',
2398
+ providerOptions: {
2399
+ anthropic: { cacheControl: { type: 'ephemeral' } },
2400
+ },
2401
+ },
2402
+ ],
2403
+ },
2404
+ {
2405
+ role: 'user',
2406
+ content: [
2407
+ {
2408
+ type: 'text',
2409
+ text: 'user 2 (should be rejected)',
2410
+ providerOptions: {
2411
+ anthropic: { cacheControl: { type: 'ephemeral' } },
2412
+ },
2413
+ },
2414
+ ],
2415
+ },
2416
+ ],
2417
+ sendReasoning: true,
2418
+ warnings,
2419
+ cacheControlValidator,
2420
+ toolNameMapping: defaultToolNameMapping,
2421
+ });
2422
+
2423
+ // First 4 should have cache_control
2424
+ expect(result.prompt.system?.[0].cache_control).toEqual({
2425
+ type: 'ephemeral',
2426
+ });
2427
+ expect(result.prompt.system?.[1].cache_control).toEqual({
2428
+ type: 'ephemeral',
2429
+ });
2430
+ expect(result.prompt.messages[0].content[0].cache_control).toEqual({
2431
+ type: 'ephemeral',
2432
+ });
2433
+ expect(result.prompt.messages[1].content[0].cache_control).toEqual({
2434
+ type: 'ephemeral',
2435
+ });
2436
+
2437
+ // 5th should be rejected
2438
+ expect(result.prompt.messages[2].content[0].cache_control).toBeUndefined();
2439
+
2440
+ // Should have warning about exceeding limit
2441
+ expect(cacheControlValidator.getWarnings()).toMatchInlineSnapshot(`
2442
+ [
2443
+ {
2444
+ "details": "Maximum 4 cache breakpoints exceeded (found 5). This breakpoint will be ignored.",
2445
+ "feature": "cacheControl breakpoint limit",
2446
+ "type": "unsupported",
2447
+ },
2448
+ ]
2449
+ `);
2450
+ });
2451
+ });
2452
+
2453
+ describe('citations', () => {
2454
+ it('should not include citations by default', async () => {
2455
+ const result = await convertToAnthropicMessagesPrompt({
2456
+ prompt: [
2457
+ {
2458
+ role: 'user',
2459
+ content: [
2460
+ {
2461
+ type: 'file',
2462
+ data: 'base64PDFdata',
2463
+ mediaType: 'application/pdf',
2464
+ },
2465
+ ],
2466
+ },
2467
+ ],
2468
+ sendReasoning: true,
2469
+ warnings: [],
2470
+ toolNameMapping: defaultToolNameMapping,
2471
+ });
2472
+
2473
+ expect(result).toMatchInlineSnapshot(`
2474
+ {
2475
+ "betas": Set {
2476
+ "pdfs-2024-09-25",
2477
+ },
2478
+ "prompt": {
2479
+ "messages": [
2480
+ {
2481
+ "content": [
2482
+ {
2483
+ "cache_control": undefined,
2484
+ "source": {
2485
+ "data": "base64PDFdata",
2486
+ "media_type": "application/pdf",
2487
+ "type": "base64",
2488
+ },
2489
+ "title": undefined,
2490
+ "type": "document",
2491
+ },
2492
+ ],
2493
+ "role": "user",
2494
+ },
2495
+ ],
2496
+ "system": undefined,
2497
+ },
2498
+ }
2499
+ `);
2500
+ });
2501
+
2502
+ it('should include citations when enabled on file part', async () => {
2503
+ const result = await convertToAnthropicMessagesPrompt({
2504
+ prompt: [
2505
+ {
2506
+ role: 'user',
2507
+ content: [
2508
+ {
2509
+ type: 'file',
2510
+ data: 'base64PDFdata',
2511
+ mediaType: 'application/pdf',
2512
+ providerOptions: {
2513
+ anthropic: {
2514
+ citations: { enabled: true },
2515
+ },
2516
+ },
2517
+ },
2518
+ ],
2519
+ },
2520
+ ],
2521
+ sendReasoning: true,
2522
+ warnings: [],
2523
+ toolNameMapping: defaultToolNameMapping,
2524
+ });
2525
+
2526
+ expect(result).toMatchInlineSnapshot(`
2527
+ {
2528
+ "betas": Set {
2529
+ "pdfs-2024-09-25",
2530
+ },
2531
+ "prompt": {
2532
+ "messages": [
2533
+ {
2534
+ "content": [
2535
+ {
2536
+ "cache_control": undefined,
2537
+ "citations": {
2538
+ "enabled": true,
2539
+ },
2540
+ "source": {
2541
+ "data": "base64PDFdata",
2542
+ "media_type": "application/pdf",
2543
+ "type": "base64",
2544
+ },
2545
+ "title": undefined,
2546
+ "type": "document",
2547
+ },
2548
+ ],
2549
+ "role": "user",
2550
+ },
2551
+ ],
2552
+ "system": undefined,
2553
+ },
2554
+ }
2555
+ `);
2556
+ });
2557
+
2558
+ it('should include custom title and context when provided', async () => {
2559
+ const result = await convertToAnthropicMessagesPrompt({
2560
+ prompt: [
2561
+ {
2562
+ role: 'user',
2563
+ content: [
2564
+ {
2565
+ type: 'file',
2566
+ data: 'base64PDFdata',
2567
+ mediaType: 'application/pdf',
2568
+ filename: 'original-name.pdf',
2569
+ providerOptions: {
2570
+ anthropic: {
2571
+ title: 'Custom Document Title',
2572
+ context: 'This is metadata about the document',
2573
+ citations: { enabled: true },
2574
+ },
2575
+ },
2576
+ },
2577
+ ],
2578
+ },
2579
+ ],
2580
+ sendReasoning: true,
2581
+ warnings: [],
2582
+ toolNameMapping: defaultToolNameMapping,
2583
+ });
2584
+
2585
+ expect(result).toMatchInlineSnapshot(`
2586
+ {
2587
+ "betas": Set {
2588
+ "pdfs-2024-09-25",
2589
+ },
2590
+ "prompt": {
2591
+ "messages": [
2592
+ {
2593
+ "content": [
2594
+ {
2595
+ "cache_control": undefined,
2596
+ "citations": {
2597
+ "enabled": true,
2598
+ },
2599
+ "context": "This is metadata about the document",
2600
+ "source": {
2601
+ "data": "base64PDFdata",
2602
+ "media_type": "application/pdf",
2603
+ "type": "base64",
2604
+ },
2605
+ "title": "Custom Document Title",
2606
+ "type": "document",
2607
+ },
2608
+ ],
2609
+ "role": "user",
2610
+ },
2611
+ ],
2612
+ "system": undefined,
2613
+ },
2614
+ }
2615
+ `);
2616
+ });
2617
+
2618
+ it('should handle multiple documents with consistent citation settings', async () => {
2619
+ const result = await convertToAnthropicMessagesPrompt({
2620
+ prompt: [
2621
+ {
2622
+ role: 'user',
2623
+ content: [
2624
+ {
2625
+ type: 'file',
2626
+ data: 'base64PDFdata1',
2627
+ mediaType: 'application/pdf',
2628
+ filename: 'doc1.pdf',
2629
+ providerOptions: {
2630
+ anthropic: {
2631
+ citations: { enabled: true },
2632
+ title: 'Custom Title 1',
2633
+ },
2634
+ },
2635
+ },
2636
+ {
2637
+ type: 'file',
2638
+ data: 'base64PDFdata2',
2639
+ mediaType: 'application/pdf',
2640
+ filename: 'doc2.pdf',
2641
+ providerOptions: {
2642
+ anthropic: {
2643
+ citations: { enabled: true },
2644
+ title: 'Custom Title 2',
2645
+ context: 'Additional context for document 2',
2646
+ },
2647
+ },
2648
+ },
2649
+ {
2650
+ type: 'text',
2651
+ text: 'Analyze both documents',
2652
+ },
2653
+ ],
2654
+ },
2655
+ ],
2656
+ sendReasoning: true,
2657
+ warnings: [],
2658
+ toolNameMapping: defaultToolNameMapping,
2659
+ });
2660
+
2661
+ expect(result).toMatchInlineSnapshot(`
2662
+ {
2663
+ "betas": Set {
2664
+ "pdfs-2024-09-25",
2665
+ },
2666
+ "prompt": {
2667
+ "messages": [
2668
+ {
2669
+ "content": [
2670
+ {
2671
+ "cache_control": undefined,
2672
+ "citations": {
2673
+ "enabled": true,
2674
+ },
2675
+ "source": {
2676
+ "data": "base64PDFdata1",
2677
+ "media_type": "application/pdf",
2678
+ "type": "base64",
2679
+ },
2680
+ "title": "Custom Title 1",
2681
+ "type": "document",
2682
+ },
2683
+ {
2684
+ "cache_control": undefined,
2685
+ "citations": {
2686
+ "enabled": true,
2687
+ },
2688
+ "context": "Additional context for document 2",
2689
+ "source": {
2690
+ "data": "base64PDFdata2",
2691
+ "media_type": "application/pdf",
2692
+ "type": "base64",
2693
+ },
2694
+ "title": "Custom Title 2",
2695
+ "type": "document",
2696
+ },
2697
+ {
2698
+ "cache_control": undefined,
2699
+ "text": "Analyze both documents",
2700
+ "type": "text",
2701
+ },
2702
+ ],
2703
+ "role": "user",
2704
+ },
2705
+ ],
2706
+ "system": undefined,
2707
+ },
2708
+ }
2709
+ `);
2710
+ });
2711
+
2712
+ describe('message sequences', () => {
2713
+ it('should convert user-assistant-tool-assistant-user message sequence with multiple tool calls', async () => {
2714
+ const result = await convertToAnthropicMessagesPrompt({
2715
+ prompt: [
2716
+ {
2717
+ role: 'user',
2718
+ content: [
2719
+ { type: 'text', text: 'weather for berlin, london and paris' },
2720
+ ],
2721
+ },
2722
+ {
2723
+ role: 'assistant',
2724
+ content: [
2725
+ {
2726
+ type: 'text',
2727
+ text: 'I will use the weather tool to get the weather for berlin, london and paris',
2728
+ },
2729
+ {
2730
+ type: 'tool-call',
2731
+ toolName: 'weather',
2732
+ toolCallId: 'weather-call-1',
2733
+ input: { location: 'berlin' },
2734
+ },
2735
+ {
2736
+ type: 'tool-call',
2737
+ toolName: 'weather',
2738
+ toolCallId: 'weather-call-2',
2739
+ input: { location: 'london' },
2740
+ },
2741
+ {
2742
+ type: 'tool-call',
2743
+ toolName: 'weather',
2744
+ toolCallId: 'weather-call-3',
2745
+ input: { location: 'paris' },
2746
+ },
2747
+ ],
2748
+ },
2749
+ {
2750
+ role: 'tool',
2751
+ content: [
2752
+ {
2753
+ type: 'tool-result',
2754
+ toolName: 'weather',
2755
+ toolCallId: 'weather-call-1',
2756
+ output: {
2757
+ type: 'json',
2758
+ value: { weather: 'sunny' },
2759
+ },
2760
+ },
2761
+ {
2762
+ type: 'tool-result',
2763
+ toolName: 'weather',
2764
+ toolCallId: 'weather-call-2',
2765
+ output: {
2766
+ type: 'json',
2767
+ value: { weather: 'cloudy' },
2768
+ },
2769
+ },
2770
+ {
2771
+ type: 'tool-result',
2772
+ toolName: 'weather',
2773
+ toolCallId: 'weather-call-3',
2774
+ output: {
2775
+ type: 'json',
2776
+ value: { weather: 'rainy' },
2777
+ },
2778
+ },
2779
+ ],
2780
+ },
2781
+ {
2782
+ role: 'assistant',
2783
+ content: [
2784
+ {
2785
+ type: 'text',
2786
+ text: 'The weather for berlin is sunny, the weather for london is cloudy, and the weather for paris is rainy',
2787
+ },
2788
+ ],
2789
+ },
2790
+ {
2791
+ role: 'user',
2792
+ content: [{ type: 'text', text: 'and for new york?' }],
2793
+ },
2794
+ ],
2795
+ sendReasoning: true,
2796
+ warnings: [],
2797
+ toolNameMapping: defaultToolNameMapping,
2798
+ });
2799
+
2800
+ expect(result.prompt).toMatchInlineSnapshot(`
2801
+ {
2802
+ "messages": [
2803
+ {
2804
+ "content": [
2805
+ {
2806
+ "cache_control": undefined,
2807
+ "text": "weather for berlin, london and paris",
2808
+ "type": "text",
2809
+ },
2810
+ ],
2811
+ "role": "user",
2812
+ },
2813
+ {
2814
+ "content": [
2815
+ {
2816
+ "cache_control": undefined,
2817
+ "text": "I will use the weather tool to get the weather for berlin, london and paris",
2818
+ "type": "text",
2819
+ },
2820
+ {
2821
+ "cache_control": undefined,
2822
+ "id": "weather-call-1",
2823
+ "input": {
2824
+ "location": "berlin",
2825
+ },
2826
+ "name": "weather",
2827
+ "type": "tool_use",
2828
+ },
2829
+ {
2830
+ "cache_control": undefined,
2831
+ "id": "weather-call-2",
2832
+ "input": {
2833
+ "location": "london",
2834
+ },
2835
+ "name": "weather",
2836
+ "type": "tool_use",
2837
+ },
2838
+ {
2839
+ "cache_control": undefined,
2840
+ "id": "weather-call-3",
2841
+ "input": {
2842
+ "location": "paris",
2843
+ },
2844
+ "name": "weather",
2845
+ "type": "tool_use",
2846
+ },
2847
+ ],
2848
+ "role": "assistant",
2849
+ },
2850
+ {
2851
+ "content": [
2852
+ {
2853
+ "cache_control": undefined,
2854
+ "content": "{"weather":"sunny"}",
2855
+ "is_error": undefined,
2856
+ "tool_use_id": "weather-call-1",
2857
+ "type": "tool_result",
2858
+ },
2859
+ {
2860
+ "cache_control": undefined,
2861
+ "content": "{"weather":"cloudy"}",
2862
+ "is_error": undefined,
2863
+ "tool_use_id": "weather-call-2",
2864
+ "type": "tool_result",
2865
+ },
2866
+ {
2867
+ "cache_control": undefined,
2868
+ "content": "{"weather":"rainy"}",
2869
+ "is_error": undefined,
2870
+ "tool_use_id": "weather-call-3",
2871
+ "type": "tool_result",
2872
+ },
2873
+ ],
2874
+ "role": "user",
2875
+ },
2876
+ {
2877
+ "content": [
2878
+ {
2879
+ "cache_control": undefined,
2880
+ "text": "The weather for berlin is sunny, the weather for london is cloudy, and the weather for paris is rainy",
2881
+ "type": "text",
2882
+ },
2883
+ ],
2884
+ "role": "assistant",
2885
+ },
2886
+ {
2887
+ "content": [
2888
+ {
2889
+ "cache_control": undefined,
2890
+ "text": "and for new york?",
2891
+ "type": "text",
2892
+ },
2893
+ ],
2894
+ "role": "user",
2895
+ },
2896
+ ],
2897
+ "system": undefined,
2898
+ }
2899
+ `);
2900
+ });
2901
+ });
2902
+ });