@ai-sdk/anthropic 4.0.78 → 4.0.79

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.
@@ -1420,6 +1420,40 @@ const result = await generateText({
1420
1420
  settings](https://console.anthropic.com/settings/privacy).
1421
1421
  </Note>
1422
1422
 
1423
+ #### Sources and Citations
1424
+
1425
+ `result.sources` and source stream events expose web-search results. Inline
1426
+ references are attached separately to generated text as `part.citations`, and
1427
+ to streamed text as `text-end.citations`. This shared citation representation
1428
+ includes the referenced URL or document and its supporting passage. If web-search
1429
+ results are unavailable, web citations also provide source references for
1430
+ compatibility. Indexed document references retain their existing document source
1431
+ parts when the original prompt supplies document information.
1432
+
1433
+ Raw citations remain available as
1434
+ `text.providerMetadata.anthropic.citations`, including on streamed `text-end`
1435
+ events. Import `AnthropicTextProviderMetadata` and `AnthropicCitation` for typed
1436
+ access. `AnthropicCitation` is a union of `web_search_result_location`,
1437
+ `search_result_location`, `page_location`, `char_location`, and
1438
+ `content_block_location`. Document page, character, and block locations refer to
1439
+ the source document, rather than the generated answer.
1440
+
1441
+ ```ts
1442
+ import type { AnthropicTextProviderMetadata } from '@ai-sdk/anthropic';
1443
+
1444
+ for (const part of result.content) {
1445
+ if (part.type === 'text') {
1446
+ console.log(part.citations);
1447
+ const metadata = part.providerMetadata as
1448
+ | AnthropicTextProviderMetadata
1449
+ | undefined;
1450
+ for (const citation of metadata?.anthropic.citations ?? []) {
1451
+ console.log(citation.type, citation.cited_text);
1452
+ }
1453
+ }
1454
+ }
1455
+ ```
1456
+
1423
1457
  #### Configuration Options
1424
1458
 
1425
1459
  Use `webSearch_20260318` for dynamic filtering and response inclusion control.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/anthropic",
3
- "version": "4.0.78",
3
+ "version": "4.0.79",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -35,8 +35,8 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@ai-sdk/provider": "4.0.26",
39
- "@ai-sdk/provider-utils": "5.0.58"
38
+ "@ai-sdk/provider": "4.0.27",
39
+ "@ai-sdk/provider-utils": "5.0.59"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -594,6 +594,7 @@ export type AnthropicSpeed = 'fast' | 'standard';
594
594
 
595
595
  export type AnthropicToolChoice =
596
596
  | { type: 'auto' | 'any'; disable_parallel_tool_use?: boolean }
597
+ | { type: 'none' }
597
598
  | { type: 'tool'; name: string; disable_parallel_tool_use?: boolean };
598
599
 
599
600
  export type AnthropicContainer = {
@@ -1,3 +1,4 @@
1
+ import { mapAnthropicCitation } from './map-anthropic-citation';
1
2
  import {
2
3
  InvalidArgumentError,
3
4
  InvalidResponseDataError,
@@ -44,8 +45,8 @@ import {
44
45
  } from './anthropic-api';
45
46
  import { anthropicFailedResponseHandler } from './anthropic-error';
46
47
  import {
47
- AnthropicLanguageModel,
48
48
  createCitationSource,
49
+ AnthropicLanguageModel,
49
50
  type AnthropicLanguageModelConfig,
50
51
  } from './anthropic-language-model';
51
52
  import {
@@ -746,6 +747,12 @@ function convertAnthropicBatchResponse(
746
747
  > = {};
747
748
  const serverToolCalls: Record<string, string> = {};
748
749
 
750
+ const hasWebSearchResults = response.content.some(
751
+ part =>
752
+ part.type === 'web_search_tool_result' &&
753
+ Array.isArray(part.content) &&
754
+ part.content.length > 0,
755
+ );
749
756
  for (const part of response.content) {
750
757
  switch (part.type) {
751
758
  case 'text':
@@ -754,6 +761,12 @@ function convertAnthropicBatchResponse(
754
761
  content.push({
755
762
  type: 'text',
756
763
  text: part.text,
764
+ ...(citations != null &&
765
+ citations.length > 0 && {
766
+ citations: citations.map(citation =>
767
+ mapAnthropicCitation(citation, []),
768
+ ),
769
+ }),
757
770
  ...(citations != null &&
758
771
  citations.length > 0 && {
759
772
  providerMetadata: {
@@ -762,12 +775,12 @@ function convertAnthropicBatchResponse(
762
775
  }),
763
776
  });
764
777
  for (const citation of part.citations ?? []) {
765
- // Batch result retrieval does not include the original prompt's
766
- // document ordering, so indexed document citations cannot be
767
- // normalized safely. Preserve them above as provider metadata.
768
- const source = createCitationSource(citation, [], generateId);
769
- if (source != null) {
770
- content.push(source);
778
+ if (
779
+ citation.type === 'web_search_result_location' &&
780
+ !hasWebSearchResults
781
+ ) {
782
+ const source = createCitationSource(citation, [], generateId);
783
+ if (source) content.push(source);
771
784
  }
772
785
  }
773
786
  break;
@@ -46,6 +46,8 @@ import type {
46
46
  AnthropicMessageMetadata,
47
47
  AnthropicUsageIteration,
48
48
  } from './anthropic-message-metadata';
49
+ import { mapAnthropicCitation } from './map-anthropic-citation';
50
+ import type { AnthropicTextProviderMetadata } from './anthropic-provider-metadata';
49
51
  import {
50
52
  anthropicChunkSchema,
51
53
  anthropicResponseSchema,
@@ -130,8 +132,8 @@ export function createCitationSource(
130
132
  ): LanguageModelV4Source | undefined {
131
133
  if (citation.type === 'web_search_result_location') {
132
134
  return {
133
- type: 'source' as const,
134
- sourceType: 'url' as const,
135
+ type: 'source',
136
+ sourceType: 'url',
135
137
  id: generateId(),
136
138
  url: citation.url,
137
139
  title: citation.title ?? undefined,
@@ -140,7 +142,7 @@ export function createCitationSource(
140
142
  citedText: citation.cited_text,
141
143
  encryptedIndex: citation.encrypted_index,
142
144
  },
143
- } satisfies SharedV4ProviderMetadata,
145
+ },
144
146
  };
145
147
  }
146
148
 
@@ -1253,31 +1255,45 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1253
1255
  const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name
1254
1256
  let isJsonResponseFromTool = false;
1255
1257
 
1258
+ const hasWebSearchResults = response.content.some(
1259
+ part =>
1260
+ part.type === 'web_search_tool_result' &&
1261
+ Array.isArray(part.content) &&
1262
+ part.content.length > 0,
1263
+ );
1256
1264
  // map response content to content array
1257
1265
  for (const part of response.content) {
1258
1266
  switch (part.type) {
1259
1267
  case 'text': {
1260
1268
  if (!usesJsonResponseTool) {
1261
- const webSearchCitations = part.citations?.filter(
1262
- citation => citation.type === 'web_search_result_location',
1263
- );
1269
+ const citations = part.citations ?? [];
1264
1270
 
1265
1271
  content.push({
1266
1272
  type: 'text',
1267
1273
  text: part.text,
1268
- ...(webSearchCitations != null &&
1269
- webSearchCitations.length > 0 && {
1270
- providerMetadata: {
1271
- anthropic: {
1272
- citations: webSearchCitations,
1273
- },
1274
+ ...(citations.length > 0 && {
1275
+ citations: citations.map(citation =>
1276
+ mapAnthropicCitation(citation, citationDocuments),
1277
+ ),
1278
+ }),
1279
+ ...(citations.length > 0 && {
1280
+ providerMetadata: {
1281
+ anthropic: {
1282
+ citations,
1274
1283
  },
1275
- }),
1284
+ } satisfies AnthropicTextProviderMetadata,
1285
+ }),
1276
1286
  });
1277
1287
 
1278
1288
  // Process citations if present
1279
1289
  if (part.citations) {
1280
1290
  for (const citation of part.citations) {
1291
+ if (
1292
+ citation.type === 'web_search_result_location' &&
1293
+ hasWebSearchResults
1294
+ ) {
1295
+ continue;
1296
+ }
1281
1297
  const source = createCitationSource(
1282
1298
  citation,
1283
1299
  citationDocuments,
@@ -1926,6 +1942,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1926
1942
  let safeguardResults: AnthropicMessageMetadata['safeguardResults'];
1927
1943
  let container: AnthropicMessageMetadata['container'] | null = null;
1928
1944
  let isJsonResponseFromTool = false;
1945
+ let hasWebSearchResults = false;
1946
+ const fallbackWebCitations: Citation[] = [];
1929
1947
  let isMessageOpen = false;
1930
1948
  let activeMessageId: string | null | undefined;
1931
1949
  let hasInvalidMessageSequence = false;
@@ -2345,6 +2363,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2345
2363
  });
2346
2364
 
2347
2365
  for (const result of part.content) {
2366
+ hasWebSearchResults = true;
2348
2367
  controller.enqueue({
2349
2368
  type: 'source',
2350
2369
  sourceType: 'url',
@@ -2587,11 +2606,14 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2587
2606
  type: 'text-end',
2588
2607
  id: String(value.index),
2589
2608
  ...(contentBlock.citations.length > 0 && {
2609
+ citations: contentBlock.citations.map(citation =>
2610
+ mapAnthropicCitation(citation, citationDocuments),
2611
+ ),
2590
2612
  providerMetadata: {
2591
2613
  anthropic: {
2592
2614
  citations: contentBlock.citations,
2593
2615
  },
2594
- },
2616
+ } satisfies AnthropicTextProviderMetadata,
2595
2617
  }),
2596
2618
  });
2597
2619
  break;
@@ -2828,21 +2850,19 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2828
2850
  const citation = value.delta.citation;
2829
2851
  const contentBlock = contentBlocks[value.index];
2830
2852
 
2831
- if (
2832
- contentBlock?.type === 'text' &&
2833
- citation.type === 'web_search_result_location'
2834
- ) {
2853
+ if (contentBlock?.type === 'text') {
2835
2854
  contentBlock.citations.push(citation);
2836
2855
  }
2837
2856
 
2838
- const source = createCitationSource(
2839
- citation,
2840
- citationDocuments,
2841
- generateId,
2842
- );
2843
-
2844
- if (source) {
2845
- controller.enqueue(source);
2857
+ if (citation.type === 'web_search_result_location') {
2858
+ fallbackWebCitations.push(citation);
2859
+ } else {
2860
+ const source = createCitationSource(
2861
+ citation,
2862
+ citationDocuments,
2863
+ generateId,
2864
+ );
2865
+ if (source) controller.enqueue(source);
2846
2866
  }
2847
2867
 
2848
2868
  return;
@@ -3101,6 +3121,17 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
3101
3121
  providerMetadata[providerOptionsName] = anthropicMetadata;
3102
3122
  }
3103
3123
 
3124
+ if (!hasWebSearchResults) {
3125
+ for (const citation of fallbackWebCitations) {
3126
+ const source = createCitationSource(
3127
+ citation,
3128
+ citationDocuments,
3129
+ generateId,
3130
+ );
3131
+ if (source) controller.enqueue(source);
3132
+ }
3133
+ }
3134
+ fallbackWebCitations.length = 0;
3104
3135
  controller.enqueue({
3105
3136
  type: 'finish',
3106
3137
  finishReason,
@@ -515,8 +515,12 @@ export async function prepareTools({
515
515
  betas,
516
516
  };
517
517
  case 'none':
518
- // Anthropic does not support 'none' tool choice, so we remove the tools:
519
- return { tools: undefined, toolChoice: undefined, toolWarnings, betas };
518
+ return {
519
+ tools: anthropicTools,
520
+ toolChoice: { type: 'none' },
521
+ toolWarnings,
522
+ betas,
523
+ };
520
524
  case 'tool':
521
525
  if (rejectsForcedToolUse) {
522
526
  toolWarnings.push({
@@ -0,0 +1,16 @@
1
+ import type { Citation } from './anthropic-api';
2
+
3
+ /**
4
+ * Citation metadata returned by Anthropic for generated text.
5
+ */
6
+ export type AnthropicCitation = Citation;
7
+
8
+ /**
9
+ * Anthropic provider metadata attached to generated text that contains
10
+ * citations.
11
+ */
12
+ export type AnthropicTextProviderMetadata = {
13
+ anthropic: {
14
+ citations: AnthropicCitation[];
15
+ };
16
+ };
@@ -1342,6 +1342,30 @@ export async function convertToAnthropicPrompt({
1342
1342
  ) {
1343
1343
  const output = part.output;
1344
1344
 
1345
+ if (
1346
+ output.type === 'error-json' ||
1347
+ (output.type === 'json' &&
1348
+ output.value != null &&
1349
+ typeof output.value === 'object' &&
1350
+ !Array.isArray(output.value) &&
1351
+ 'type' in output.value &&
1352
+ output.value.type === 'tool_search_tool_result_error')
1353
+ ) {
1354
+ anthropicContent.push({
1355
+ type: 'tool_search_tool_result',
1356
+ tool_use_id: part.toolCallId,
1357
+ content: {
1358
+ type: 'tool_search_tool_result_error',
1359
+ error_code:
1360
+ extractErrorValue(output.value).errorCode ??
1361
+ 'unavailable',
1362
+ },
1363
+ cache_control: cacheControl,
1364
+ });
1365
+
1366
+ break;
1367
+ }
1368
+
1345
1369
  if (output.type !== 'json') {
1346
1370
  warnings.push({
1347
1371
  type: 'other',
package/src/index.ts CHANGED
@@ -2,6 +2,10 @@ export type {
2
2
  AnthropicMessageMetadata,
3
3
  AnthropicUsageIteration,
4
4
  } from './anthropic-message-metadata';
5
+ export type {
6
+ AnthropicCitation,
7
+ AnthropicTextProviderMetadata,
8
+ } from './anthropic-provider-metadata';
5
9
  export type {
6
10
  AnthropicLanguageModelOptions,
7
11
  /** @deprecated Use `AnthropicLanguageModelOptions` instead. */
@@ -0,0 +1,51 @@
1
+ import type { LanguageModelV4Citation } from '@ai-sdk/provider';
2
+ import type { Citation } from './anthropic-api';
3
+
4
+ export type CitationDocument = {
5
+ title: string;
6
+ filename?: string;
7
+ mediaType: string;
8
+ };
9
+
10
+ export function mapAnthropicCitation(
11
+ citation: Citation,
12
+ documents: Array<CitationDocument>,
13
+ ): LanguageModelV4Citation {
14
+ if (
15
+ citation.type === 'web_search_result_location' ||
16
+ citation.type === 'search_result_location'
17
+ ) {
18
+ const url =
19
+ citation.type === 'web_search_result_location'
20
+ ? citation.url
21
+ : citation.source;
22
+ return {
23
+ source: {
24
+ type: 'source',
25
+ sourceType: 'url',
26
+ id: url,
27
+ url,
28
+ title: citation.title ?? undefined,
29
+ providerMetadata: { anthropic: { ...citation } },
30
+ },
31
+ citedText: citation.cited_text,
32
+ };
33
+ }
34
+
35
+ const document = documents[citation.document_index];
36
+ return {
37
+ source: {
38
+ type: 'source',
39
+ sourceType: 'document',
40
+ id: citation.file_id ?? String(citation.document_index),
41
+ mediaType: document?.mediaType ?? 'text/plain',
42
+ title:
43
+ citation.document_title ??
44
+ document?.title ??
45
+ `Document ${citation.document_index}`,
46
+ filename: document?.filename,
47
+ providerMetadata: { anthropic: { ...citation } },
48
+ },
49
+ citedText: citation.cited_text,
50
+ };
51
+ }