@ai-sdk/anthropic 4.0.44 → 4.0.46

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.
@@ -17,6 +17,7 @@ import {
17
17
  createJsonLinesResponseHandler,
18
18
  createJsonResponseHandler,
19
19
  getFromApi,
20
+ isRecord,
20
21
  lazySchema,
21
22
  normalizeBatchRequestCounts,
22
23
  normalizeHeaders,
@@ -39,6 +40,7 @@ import {
39
40
  import { anthropicFailedResponseHandler } from './anthropic-error';
40
41
  import {
41
42
  AnthropicLanguageModel,
43
+ createCitationSource,
42
44
  type AnthropicLanguageModelConfig,
43
45
  } from './anthropic-language-model';
44
46
  import {
@@ -78,6 +80,8 @@ const anthropicBatchResponseSchema = lazySchema(() =>
78
80
  created_at: z.string(),
79
81
  expires_at: z.string(),
80
82
  archived_at: z.string().nullish(),
83
+ cancel_initiated_at: z.string().nullish(),
84
+ ended_at: z.string().nullish(),
81
85
  results_url: z.string().nullish(),
82
86
  }),
83
87
  ),
@@ -85,29 +89,55 @@ const anthropicBatchResponseSchema = lazySchema(() =>
85
89
 
86
90
  type AnthropicBatchResponse = InferSchema<typeof anthropicBatchResponseSchema>;
87
91
 
88
- const anthropicBatchResultLineSchema = lazySchema(() =>
92
+ const knownAnthropicBatchContentTypes = new Set([
93
+ 'advisor_tool_result',
94
+ 'bash_code_execution_tool_result',
95
+ 'code_execution_tool_result',
96
+ 'compaction',
97
+ 'container_upload',
98
+ 'fallback',
99
+ 'mcp_tool_result',
100
+ 'mcp_tool_use',
101
+ 'redacted_thinking',
102
+ 'server_tool_use',
103
+ 'text',
104
+ 'text_editor_code_execution_tool_result',
105
+ 'thinking',
106
+ 'tool_search_tool_result',
107
+ 'tool_use',
108
+ 'web_fetch_tool_result',
109
+ 'web_search_tool_result',
110
+ ]);
111
+
112
+ const anthropicBatchResultSchema = lazySchema(() =>
89
113
  zodSchema(
90
- z.object({
91
- custom_id: z.string(),
92
- result: z.discriminatedUnion('type', [
93
- z.object({
94
- type: z.literal('succeeded'),
95
- message: z.unknown(),
96
- }),
97
- z.object({
98
- type: z.literal('errored'),
114
+ z.discriminatedUnion('type', [
115
+ z.object({
116
+ type: z.literal('succeeded'),
117
+ message: z.unknown(),
118
+ }),
119
+ z.object({
120
+ type: z.literal('errored'),
121
+ error: z.object({
122
+ type: z.literal('error'),
99
123
  error: z.object({
100
- type: z.literal('error'),
101
- error: z.object({
102
- type: z.string(),
103
- message: z.string(),
104
- }),
105
- request_id: z.string().nullish(),
124
+ type: z.string(),
125
+ message: z.string(),
106
126
  }),
127
+ request_id: z.string().nullish(),
107
128
  }),
108
- z.object({ type: z.literal('canceled') }),
109
- z.object({ type: z.literal('expired') }),
110
- ]),
129
+ }),
130
+ z.object({ type: z.literal('canceled') }),
131
+ z.object({ type: z.literal('expired') }),
132
+ ]),
133
+ ),
134
+ );
135
+
136
+ const anthropicBatchResultLineSchema = lazySchema(() =>
137
+ zodSchema(
138
+ z.object({
139
+ custom_id: z.string(),
140
+ result: z.unknown(),
111
141
  }),
112
142
  ),
113
143
  );
@@ -196,6 +226,29 @@ export class AnthropicMessagesBatchLanguageModel
196
226
  stream: false,
197
227
  userSuppliedBetas: new Set(explicitBatchBetas),
198
228
  });
229
+ if (prepared.usesJsonResponseTool) {
230
+ throw new UnsupportedFunctionalityError({
231
+ functionality: 'batch responseFormat JSON-tool fallback',
232
+ message:
233
+ `Anthropic Message Batches cannot decode the JSON-tool structured-output fallback ` +
234
+ `(request "${request.id}") because batch results are retrieved independently of the start call. ` +
235
+ `Use a model that supports native output_format structured outputs.`,
236
+ });
237
+ }
238
+ const aliasedProviderTool = request.options.tools?.find(
239
+ tool =>
240
+ tool.type === 'provider' &&
241
+ prepared.toolNameMapping.toProviderToolName(tool.name) !== tool.name,
242
+ );
243
+ if (aliasedProviderTool != null) {
244
+ throw new UnsupportedFunctionalityError({
245
+ functionality: 'aliased provider tool names in batches',
246
+ message:
247
+ `Anthropic Message Batches cannot restore the custom provider-tool name ` +
248
+ `"${aliasedProviderTool.name}" when results are retrieved independently of the start call ` +
249
+ `(request "${request.id}"). Use the provider's canonical tool name.`,
250
+ });
251
+ }
199
252
  const body = this.transformRequestBody(prepared.args, prepared.betas);
200
253
  validateAnthropicBatchBody({
201
254
  body,
@@ -303,7 +356,7 @@ export class AnthropicMessagesBatchLanguageModel
303
356
  lines: AsyncIterable<AnthropicBatchResultLine>,
304
357
  ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
305
358
  for await (const line of lines) {
306
- yield await convertAnthropicBatchResult(line);
359
+ yield await convertAnthropicBatchResult(line, this.generateId);
307
360
  }
308
361
  }
309
362
 
@@ -438,6 +491,15 @@ function convertAnthropicBatchStatus(
438
491
  ...(requestCounts != null ? { requestCounts } : {}),
439
492
  createdAt: batch.created_at,
440
493
  expiresAt: batch.expires_at,
494
+ providerMetadata: {
495
+ anthropic: {
496
+ archivedAt: batch.archived_at ?? null,
497
+ cancelInitiatedAt: batch.cancel_initiated_at ?? null,
498
+ endedAt: batch.ended_at ?? null,
499
+ requestCounts: batch.request_counts,
500
+ resultsUrl: batch.results_url ?? null,
501
+ },
502
+ },
441
503
  };
442
504
  }
443
505
 
@@ -470,20 +532,32 @@ function convertAnthropicRequestCounts(
470
532
 
471
533
  async function convertAnthropicBatchResult(
472
534
  line: AnthropicBatchResultLine,
535
+ generateId: () => string,
473
536
  ): Promise<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
474
- switch (line.result.type) {
537
+ const resultValidation = await safeValidateTypes({
538
+ value: line.result,
539
+ schema: anthropicBatchResultSchema,
540
+ });
541
+
542
+ if (!resultValidation.success) {
543
+ return invalidAnthropicBatchResult(line.custom_id);
544
+ }
545
+
546
+ const result = resultValidation.value;
547
+
548
+ switch (result.type) {
475
549
  case 'canceled':
476
550
  return { id: line.custom_id, status: 'cancelled' };
477
551
  case 'expired':
478
552
  return { id: line.custom_id, status: 'expired' };
479
553
  case 'errored': {
480
- const requestId = line.result.error.request_id;
554
+ const requestId = result.error.request_id;
481
555
  return {
482
556
  id: line.custom_id,
483
557
  status: 'failed',
484
558
  error: {
485
- message: line.result.error.error.message,
486
- type: line.result.error.error.type,
559
+ message: result.error.error.message,
560
+ type: result.error.error.type,
487
561
  },
488
562
  ...(requestId != null
489
563
  ? {
@@ -495,69 +569,117 @@ async function convertAnthropicBatchResult(
495
569
  };
496
570
  }
497
571
  case 'succeeded': {
498
- const validation = await safeValidateTypes({
499
- value: line.result.message,
500
- schema: anthropicResponseSchema,
501
- });
502
-
503
- if (!validation.success) {
504
- return {
505
- id: line.custom_id,
506
- status: 'failed',
507
- error: {
508
- message: 'Anthropic returned an invalid Message batch result.',
509
- code: 'invalid_response',
510
- },
511
- };
512
- }
513
-
514
- const response = validation.value;
515
-
516
- const unsupportedPart = response.content.find(
517
- part => !supportedBatchContentTypes.has(part.type),
518
- );
572
+ const response = await parseAnthropicBatchResponse(result.message);
519
573
 
520
- if (unsupportedPart != null) {
521
- return {
522
- id: line.custom_id,
523
- status: 'failed',
524
- error: {
525
- message:
526
- `Anthropic returned a "${unsupportedPart.type}" content block, ` +
527
- 'but tool content is not supported in AI SDK text batches.',
528
- code: 'unsupported_content',
529
- },
530
- };
574
+ if (response == null) {
575
+ return invalidAnthropicBatchResult(line.custom_id);
531
576
  }
532
577
 
533
578
  return {
534
579
  id: line.custom_id,
535
580
  status: 'succeeded',
536
- result: convertAnthropicBatchResponse(response),
581
+ result: convertAnthropicBatchResponse(response, generateId),
537
582
  };
538
583
  }
539
584
  }
540
585
  }
541
586
 
542
- const supportedBatchContentTypes = new Set([
543
- 'text',
544
- 'thinking',
545
- 'redacted_thinking',
546
- 'compaction',
547
- // The normal Anthropic response conversion intentionally drops this marker;
548
- // the fallback hop remains available through usage.iterations metadata.
549
- 'fallback',
550
- ]);
587
+ function invalidAnthropicBatchResult(
588
+ id: string,
589
+ ): BatchV4ItemResult<LanguageModelV4GenerateResult> {
590
+ return {
591
+ id,
592
+ status: 'failed',
593
+ error: {
594
+ message: 'Anthropic returned an invalid Message batch result.',
595
+ code: 'invalid_response',
596
+ },
597
+ };
598
+ }
599
+
600
+ async function parseAnthropicBatchResponse(
601
+ message: unknown,
602
+ ): Promise<AnthropicResponse | undefined> {
603
+ const validation = await safeValidateTypes({
604
+ value: message,
605
+ schema: anthropicResponseSchema,
606
+ });
607
+
608
+ if (validation.success) {
609
+ return validation.value;
610
+ }
611
+
612
+ if (!isRecord(message) || !Array.isArray(message.content)) {
613
+ return undefined;
614
+ }
615
+
616
+ const content: AnthropicResponse['content'] = [];
617
+ for (const part of message.content) {
618
+ const partValidation = await safeValidateTypes({
619
+ value: { ...message, content: [part] },
620
+ schema: anthropicResponseSchema,
621
+ });
622
+
623
+ if (partValidation.success) {
624
+ const [validatedPart] = partValidation.value.content;
625
+ if (validatedPart != null) {
626
+ content.push(validatedPart);
627
+ }
628
+ } else if (
629
+ !isRecord(part) ||
630
+ typeof part.type !== 'string' ||
631
+ knownAnthropicBatchContentTypes.has(part.type)
632
+ ) {
633
+ return undefined;
634
+ }
635
+ }
636
+
637
+ const recovered = await safeValidateTypes({
638
+ value: { ...message, content },
639
+ schema: anthropicResponseSchema,
640
+ });
641
+
642
+ return recovered.success ? recovered.value : undefined;
643
+ }
551
644
 
552
645
  function convertAnthropicBatchResponse(
553
646
  response: AnthropicResponse,
647
+ generateId: () => string,
554
648
  ): LanguageModelV4GenerateResult {
555
649
  const content: LanguageModelV4GenerateResult['content'] = [];
650
+ const mcpToolCalls: Record<
651
+ string,
652
+ Extract<
653
+ LanguageModelV4GenerateResult['content'][number],
654
+ { type: 'tool-call' }
655
+ >
656
+ > = {};
657
+ const serverToolCalls: Record<string, string> = {};
556
658
 
557
659
  for (const part of response.content) {
558
660
  switch (part.type) {
559
661
  case 'text':
560
- content.push({ type: 'text', text: part.text });
662
+ const citations = part.citations;
663
+
664
+ content.push({
665
+ type: 'text',
666
+ text: part.text,
667
+ ...(citations != null &&
668
+ citations.length > 0 && {
669
+ providerMetadata: {
670
+ anthropic: { citations },
671
+ },
672
+ }),
673
+ });
674
+ for (const citation of part.citations ?? []) {
675
+ // Batch result retrieval does not include the original prompt's
676
+ // document ordering, so indexed document citations cannot be
677
+ // normalized safely. Preserve them above as provider metadata.
678
+ const source = createCitationSource(citation, [], generateId);
679
+ if (source != null) {
680
+ content.push(source);
681
+ }
682
+ }
561
683
  break;
562
684
  case 'thinking':
563
685
  content.push({
@@ -581,6 +703,13 @@ function convertAnthropicBatchResponse(
581
703
  },
582
704
  });
583
705
  break;
706
+ case 'container_upload':
707
+ content.push({
708
+ type: 'custom',
709
+ kind: 'anthropic.container_upload',
710
+ providerMetadata: { anthropic: { fileId: part.file_id } },
711
+ });
712
+ break;
584
713
  case 'compaction':
585
714
  content.push({
586
715
  type: 'text',
@@ -588,6 +717,249 @@ function convertAnthropicBatchResponse(
588
717
  providerMetadata: { anthropic: { type: 'compaction' } },
589
718
  });
590
719
  break;
720
+ case 'tool_use':
721
+ content.push({
722
+ type: 'tool-call',
723
+ toolCallId: part.id,
724
+ toolName: part.name,
725
+ input: JSON.stringify(part.input),
726
+ ...anthropicCallerMetadata(part.caller),
727
+ });
728
+ break;
729
+ case 'server_tool_use': {
730
+ const isCodeExecutionAlias =
731
+ part.name === 'bash_code_execution' ||
732
+ part.name === 'text_editor_code_execution';
733
+ const isCodeExecution =
734
+ isCodeExecutionAlias || part.name === 'code_execution';
735
+ const toolName = isCodeExecutionAlias ? 'code_execution' : part.name;
736
+ if (
737
+ part.name === 'tool_search_tool_bm25' ||
738
+ part.name === 'tool_search_tool_regex'
739
+ ) {
740
+ serverToolCalls[part.id] = part.name;
741
+ }
742
+ content.push({
743
+ type: 'tool-call',
744
+ toolCallId: part.id,
745
+ toolName,
746
+ input: JSON.stringify(
747
+ isCodeExecutionAlias
748
+ ? { type: part.name, ...(part.input ?? {}) }
749
+ : part.name === 'code_execution' &&
750
+ part.input != null &&
751
+ 'code' in part.input &&
752
+ !('type' in part.input)
753
+ ? { type: 'programmatic-tool-call', ...part.input }
754
+ : part.input,
755
+ ),
756
+ providerExecuted: true,
757
+ // Batch results are retrieved without the original request tools, so
758
+ // implicitly provisioned code execution must remain self-describing.
759
+ ...(isCodeExecution ? { dynamic: true } : {}),
760
+ ...anthropicCallerMetadata(part.caller),
761
+ });
762
+ break;
763
+ }
764
+ case 'mcp_tool_use': {
765
+ const toolCall = {
766
+ type: 'tool-call' as const,
767
+ toolCallId: part.id,
768
+ toolName: part.name,
769
+ input: JSON.stringify(part.input),
770
+ providerExecuted: true,
771
+ dynamic: true,
772
+ providerMetadata: {
773
+ anthropic: {
774
+ serverName: part.server_name,
775
+ type: 'mcp-tool-use',
776
+ },
777
+ },
778
+ };
779
+ mcpToolCalls[part.id] = toolCall;
780
+ content.push(toolCall);
781
+ break;
782
+ }
783
+ case 'mcp_tool_result': {
784
+ const toolCall = mcpToolCalls[part.tool_use_id];
785
+ content.push({
786
+ type: 'tool-result',
787
+ toolCallId: part.tool_use_id,
788
+ toolName: toolCall?.toolName ?? 'mcp',
789
+ isError: part.is_error,
790
+ result: part.content,
791
+ dynamic: true,
792
+ ...(toolCall?.providerMetadata != null && {
793
+ providerMetadata: toolCall.providerMetadata,
794
+ }),
795
+ });
796
+ break;
797
+ }
798
+ case 'web_fetch_tool_result':
799
+ content.push({
800
+ type: 'tool-result',
801
+ toolCallId: part.tool_use_id,
802
+ toolName: 'web_fetch',
803
+ ...(part.content.type === 'web_fetch_tool_result_error'
804
+ ? {
805
+ isError: true,
806
+ result: {
807
+ errorCode: part.content.error_code,
808
+ type: part.content.type,
809
+ },
810
+ }
811
+ : {
812
+ result: {
813
+ content: {
814
+ citations: part.content.content.citations,
815
+ source: {
816
+ data: part.content.content.source.data,
817
+ mediaType: part.content.content.source.media_type,
818
+ type: part.content.content.source.type,
819
+ },
820
+ title: part.content.content.title,
821
+ type: part.content.content.type,
822
+ },
823
+ retrievedAt: part.content.retrieved_at,
824
+ type: part.content.type,
825
+ url: part.content.url,
826
+ },
827
+ }),
828
+ ...anthropicCallerMetadata(part.caller),
829
+ });
830
+ break;
831
+ case 'web_search_tool_result':
832
+ content.push({
833
+ type: 'tool-result',
834
+ toolCallId: part.tool_use_id,
835
+ toolName: 'web_search',
836
+ ...(Array.isArray(part.content)
837
+ ? {
838
+ result: part.content.map(result => ({
839
+ encryptedContent: result.encrypted_content,
840
+ pageAge: result.page_age ?? null,
841
+ ...(result.title != null ? { title: result.title } : {}),
842
+ type: result.type,
843
+ url: result.url,
844
+ })),
845
+ }
846
+ : {
847
+ isError: true,
848
+ result: {
849
+ errorCode: part.content.error_code,
850
+ type: part.content.type,
851
+ },
852
+ }),
853
+ ...anthropicCallerMetadata(part.caller),
854
+ });
855
+ if (Array.isArray(part.content)) {
856
+ for (const result of part.content) {
857
+ content.push({
858
+ type: 'source',
859
+ sourceType: 'url',
860
+ id: generateId(),
861
+ url: result.url,
862
+ ...(result.title != null ? { title: result.title } : {}),
863
+ providerMetadata: {
864
+ anthropic: {
865
+ pageAge: result.page_age ?? null,
866
+ },
867
+ },
868
+ });
869
+ }
870
+ }
871
+ break;
872
+ case 'code_execution_tool_result':
873
+ content.push({
874
+ type: 'tool-result',
875
+ toolCallId: part.tool_use_id,
876
+ toolName: 'code_execution',
877
+ ...(part.content.type === 'code_execution_tool_result_error'
878
+ ? {
879
+ isError: true,
880
+ result: {
881
+ errorCode: part.content.error_code,
882
+ type: part.content.type,
883
+ },
884
+ }
885
+ : { result: part.content }),
886
+ });
887
+ break;
888
+ case 'bash_code_execution_tool_result':
889
+ case 'text_editor_code_execution_tool_result':
890
+ content.push({
891
+ type: 'tool-result',
892
+ toolCallId: part.tool_use_id,
893
+ toolName: 'code_execution',
894
+ result: part.content,
895
+ });
896
+ break;
897
+ case 'tool_search_tool_result': {
898
+ const toolName =
899
+ serverToolCalls[part.tool_use_id] ?? 'tool_search_tool_regex';
900
+ content.push({
901
+ type: 'tool-result',
902
+ toolCallId: part.tool_use_id,
903
+ toolName,
904
+ ...(part.content.type === 'tool_search_tool_result_error'
905
+ ? {
906
+ isError: true,
907
+ result: {
908
+ errorCode: part.content.error_code,
909
+ type: part.content.type,
910
+ },
911
+ }
912
+ : {
913
+ result: part.content.tool_references.map(reference => ({
914
+ toolName: reference.tool_name,
915
+ type: reference.type,
916
+ })),
917
+ }),
918
+ });
919
+ break;
920
+ }
921
+ case 'advisor_tool_result':
922
+ if (part.content.type === 'advisor_result') {
923
+ content.push({
924
+ type: 'tool-result',
925
+ toolCallId: part.tool_use_id,
926
+ toolName: 'advisor',
927
+ result: {
928
+ type: part.content.type,
929
+ text: part.content.text,
930
+ ...(part.content.stop_reason != null
931
+ ? { stopReason: part.content.stop_reason }
932
+ : {}),
933
+ },
934
+ });
935
+ } else if (part.content.type === 'advisor_redacted_result') {
936
+ content.push({
937
+ type: 'tool-result',
938
+ toolCallId: part.tool_use_id,
939
+ toolName: 'advisor',
940
+ result: {
941
+ type: part.content.type,
942
+ encryptedContent: part.content.encrypted_content,
943
+ ...(part.content.stop_reason != null
944
+ ? { stopReason: part.content.stop_reason }
945
+ : {}),
946
+ },
947
+ });
948
+ } else {
949
+ content.push({
950
+ type: 'tool-result',
951
+ toolCallId: part.tool_use_id,
952
+ toolName: 'advisor',
953
+ isError: true,
954
+ result: {
955
+ errorCode: part.content.error_code,
956
+ type: part.content.type,
957
+ },
958
+ });
959
+ }
960
+ break;
961
+ case 'fallback':
962
+ break;
591
963
  }
592
964
  }
593
965
 
@@ -611,6 +983,23 @@ function convertAnthropicBatchResponse(
611
983
  };
612
984
  }
613
985
 
986
+ function anthropicCallerMetadata(
987
+ caller: { type: string; tool_id?: string } | null | undefined,
988
+ ) {
989
+ return caller == null
990
+ ? {}
991
+ : {
992
+ providerMetadata: {
993
+ anthropic: {
994
+ caller: {
995
+ type: caller.type,
996
+ toolId: caller.tool_id,
997
+ },
998
+ },
999
+ },
1000
+ };
1001
+ }
1002
+
614
1003
  function convertAnthropicMessageMetadata(response: AnthropicResponse) {
615
1004
  const stopDetails = mapAnthropicStopDetails(response.stop_details);
616
1005