@ai-sdk/anthropic 4.0.78 → 4.0.80

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.
@@ -742,6 +742,13 @@ room for both, the provider adds `budgetTokens` to `maxOutputTokens` when
742
742
  setting `max_tokens`. For known models, the combined value is capped at the
743
743
  model's maximum output token limit.
744
744
 
745
+ With manual thinking (`thinking.type: 'enabled'`), a `required` or named
746
+ `toolChoice` falls back to `auto` with a warning. For a named tool choice, only
747
+ the selected tool is sent. This also applies when the top-level `reasoning`
748
+ option enables manual thinking, including when structured output uses the
749
+ internal JSON response tool. Tool use is no longer guaranteed: instruct the
750
+ model to use the tool in your prompt and verify that a tool call was made.
751
+
745
752
  See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
746
753
  on how to integrate reasoning into your chatbot.
747
754
 
@@ -1420,6 +1427,40 @@ const result = await generateText({
1420
1427
  settings](https://console.anthropic.com/settings/privacy).
1421
1428
  </Note>
1422
1429
 
1430
+ #### Sources and Citations
1431
+
1432
+ `result.sources` and source stream events expose web-search results. Inline
1433
+ references are attached separately to generated text as `part.citations`, and
1434
+ to streamed text as `text-end.citations`. This shared citation representation
1435
+ includes the referenced URL or document and its supporting passage. If web-search
1436
+ results are unavailable, web citations also provide source references for
1437
+ compatibility. Indexed document references retain their existing document source
1438
+ parts when the original prompt supplies document information.
1439
+
1440
+ Raw citations remain available as
1441
+ `text.providerMetadata.anthropic.citations`, including on streamed `text-end`
1442
+ events. Import `AnthropicTextProviderMetadata` and `AnthropicCitation` for typed
1443
+ access. `AnthropicCitation` is a union of `web_search_result_location`,
1444
+ `search_result_location`, `page_location`, `char_location`, and
1445
+ `content_block_location`. Document page, character, and block locations refer to
1446
+ the source document, rather than the generated answer.
1447
+
1448
+ ```ts
1449
+ import type { AnthropicTextProviderMetadata } from '@ai-sdk/anthropic';
1450
+
1451
+ for (const part of result.content) {
1452
+ if (part.type === 'text') {
1453
+ console.log(part.citations);
1454
+ const metadata = part.providerMetadata as
1455
+ | AnthropicTextProviderMetadata
1456
+ | undefined;
1457
+ for (const citation of metadata?.anthropic.citations ?? []) {
1458
+ console.log(citation.type, citation.cited_text);
1459
+ }
1460
+ }
1461
+ }
1462
+ ```
1463
+
1423
1464
  #### Configuration Options
1424
1465
 
1425
1466
  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.80",
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.60"
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
 
@@ -253,6 +255,12 @@ export type AnthropicLanguageModelConfig = {
253
255
  args: Record<string, any>,
254
256
  betas: Set<string>,
255
257
  ) => Record<string, any>;
258
+ transformRequestHeaders?: (
259
+ headers: Record<string, string | undefined>,
260
+ providerOptions: LanguageModelV4CallOptions['providerOptions'],
261
+ ) =>
262
+ | Record<string, string | undefined>
263
+ | PromiseLike<Record<string, string | undefined>>;
256
264
  supportedUrls?: () => LanguageModelV4['supportedUrls'];
257
265
  generateId?: () => string;
258
266
  supportsNativeStructuredOutput?: boolean;
@@ -1065,7 +1073,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1065
1073
  supportsStructuredOutput: false,
1066
1074
  supportsStrictTools,
1067
1075
  defaultEagerInputStreaming,
1068
- rejectsForcedToolUse,
1076
+ rejectsForcedToolUse:
1077
+ rejectsForcedToolUse || thinkingType === 'enabled',
1069
1078
  }
1070
1079
  : {
1071
1080
  tools: tools ?? [],
@@ -1075,7 +1084,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1075
1084
  supportsStructuredOutput,
1076
1085
  supportsStrictTools,
1077
1086
  defaultEagerInputStreaming,
1078
- rejectsForcedToolUse,
1087
+ rejectsForcedToolUse:
1088
+ rejectsForcedToolUse || thinkingType === 'enabled',
1079
1089
  },
1080
1090
  );
1081
1091
 
@@ -1119,15 +1129,21 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1119
1129
  private async getHeaders({
1120
1130
  betas,
1121
1131
  headers,
1132
+ providerOptions,
1122
1133
  }: {
1123
1134
  betas: Set<string>;
1124
1135
  headers: Record<string, string | undefined> | undefined;
1136
+ providerOptions: LanguageModelV4CallOptions['providerOptions'];
1125
1137
  }) {
1126
- return combineHeaders(
1138
+ const combinedHeaders = combineHeaders(
1127
1139
  this.config.headers ? await resolve(this.config.headers) : undefined,
1128
1140
  headers,
1129
1141
  betas.size > 0 ? { 'anthropic-beta': Array.from(betas).join(',') } : {},
1130
1142
  );
1143
+
1144
+ return this.config.transformRequestHeaders
1145
+ ? this.config.transformRequestHeaders(combinedHeaders, providerOptions)
1146
+ : combinedHeaders;
1131
1147
  }
1132
1148
 
1133
1149
  private async getBetasFromHeaders(
@@ -1238,7 +1254,11 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1238
1254
  rawValue: rawResponse,
1239
1255
  } = await postJsonToApi({
1240
1256
  url: this.buildRequestUrl(false),
1241
- headers: await this.getHeaders({ betas, headers: options.headers }),
1257
+ headers: await this.getHeaders({
1258
+ betas,
1259
+ headers: options.headers,
1260
+ providerOptions: options.providerOptions,
1261
+ }),
1242
1262
  body: this.transformRequestBody(args, betas),
1243
1263
  failedResponseHandler: anthropicFailedResponseHandler,
1244
1264
  successfulResponseHandler: createJsonResponseHandler(
@@ -1253,31 +1273,45 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1253
1273
  const serverToolCalls: Record<string, string> = {}; // tool_use_id -> provider tool name
1254
1274
  let isJsonResponseFromTool = false;
1255
1275
 
1276
+ const hasWebSearchResults = response.content.some(
1277
+ part =>
1278
+ part.type === 'web_search_tool_result' &&
1279
+ Array.isArray(part.content) &&
1280
+ part.content.length > 0,
1281
+ );
1256
1282
  // map response content to content array
1257
1283
  for (const part of response.content) {
1258
1284
  switch (part.type) {
1259
1285
  case 'text': {
1260
1286
  if (!usesJsonResponseTool) {
1261
- const webSearchCitations = part.citations?.filter(
1262
- citation => citation.type === 'web_search_result_location',
1263
- );
1287
+ const citations = part.citations ?? [];
1264
1288
 
1265
1289
  content.push({
1266
1290
  type: 'text',
1267
1291
  text: part.text,
1268
- ...(webSearchCitations != null &&
1269
- webSearchCitations.length > 0 && {
1270
- providerMetadata: {
1271
- anthropic: {
1272
- citations: webSearchCitations,
1273
- },
1292
+ ...(citations.length > 0 && {
1293
+ citations: citations.map(citation =>
1294
+ mapAnthropicCitation(citation, citationDocuments),
1295
+ ),
1296
+ }),
1297
+ ...(citations.length > 0 && {
1298
+ providerMetadata: {
1299
+ anthropic: {
1300
+ citations,
1274
1301
  },
1275
- }),
1302
+ } satisfies AnthropicTextProviderMetadata,
1303
+ }),
1276
1304
  });
1277
1305
 
1278
1306
  // Process citations if present
1279
1307
  if (part.citations) {
1280
1308
  for (const citation of part.citations) {
1309
+ if (
1310
+ citation.type === 'web_search_result_location' &&
1311
+ hasWebSearchResults
1312
+ ) {
1313
+ continue;
1314
+ }
1281
1315
  const source = createCitationSource(
1282
1316
  citation,
1283
1317
  citationDocuments,
@@ -1864,7 +1898,11 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1864
1898
  const url = this.buildRequestUrl(true);
1865
1899
  const { responseHeaders, value: response } = await postJsonToApi({
1866
1900
  url,
1867
- headers: await this.getHeaders({ betas, headers: options.headers }),
1901
+ headers: await this.getHeaders({
1902
+ betas,
1903
+ headers: options.headers,
1904
+ providerOptions: options.providerOptions,
1905
+ }),
1868
1906
  body: this.transformRequestBody(body, betas),
1869
1907
  failedResponseHandler: anthropicFailedResponseHandler,
1870
1908
  successfulResponseHandler:
@@ -1926,6 +1964,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1926
1964
  let safeguardResults: AnthropicMessageMetadata['safeguardResults'];
1927
1965
  let container: AnthropicMessageMetadata['container'] | null = null;
1928
1966
  let isJsonResponseFromTool = false;
1967
+ let hasWebSearchResults = false;
1968
+ const fallbackWebCitations: Citation[] = [];
1929
1969
  let isMessageOpen = false;
1930
1970
  let activeMessageId: string | null | undefined;
1931
1971
  let hasInvalidMessageSequence = false;
@@ -2345,6 +2385,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2345
2385
  });
2346
2386
 
2347
2387
  for (const result of part.content) {
2388
+ hasWebSearchResults = true;
2348
2389
  controller.enqueue({
2349
2390
  type: 'source',
2350
2391
  sourceType: 'url',
@@ -2587,11 +2628,14 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2587
2628
  type: 'text-end',
2588
2629
  id: String(value.index),
2589
2630
  ...(contentBlock.citations.length > 0 && {
2631
+ citations: contentBlock.citations.map(citation =>
2632
+ mapAnthropicCitation(citation, citationDocuments),
2633
+ ),
2590
2634
  providerMetadata: {
2591
2635
  anthropic: {
2592
2636
  citations: contentBlock.citations,
2593
2637
  },
2594
- },
2638
+ } satisfies AnthropicTextProviderMetadata,
2595
2639
  }),
2596
2640
  });
2597
2641
  break;
@@ -2828,21 +2872,19 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2828
2872
  const citation = value.delta.citation;
2829
2873
  const contentBlock = contentBlocks[value.index];
2830
2874
 
2831
- if (
2832
- contentBlock?.type === 'text' &&
2833
- citation.type === 'web_search_result_location'
2834
- ) {
2875
+ if (contentBlock?.type === 'text') {
2835
2876
  contentBlock.citations.push(citation);
2836
2877
  }
2837
2878
 
2838
- const source = createCitationSource(
2839
- citation,
2840
- citationDocuments,
2841
- generateId,
2842
- );
2843
-
2844
- if (source) {
2845
- controller.enqueue(source);
2879
+ if (citation.type === 'web_search_result_location') {
2880
+ fallbackWebCitations.push(citation);
2881
+ } else {
2882
+ const source = createCitationSource(
2883
+ citation,
2884
+ citationDocuments,
2885
+ generateId,
2886
+ );
2887
+ if (source) controller.enqueue(source);
2846
2888
  }
2847
2889
 
2848
2890
  return;
@@ -3101,6 +3143,17 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
3101
3143
  providerMetadata[providerOptionsName] = anthropicMetadata;
3102
3144
  }
3103
3145
 
3146
+ if (!hasWebSearchResults) {
3147
+ for (const citation of fallbackWebCitations) {
3148
+ const source = createCitationSource(
3149
+ citation,
3150
+ citationDocuments,
3151
+ generateId,
3152
+ );
3153
+ if (source) controller.enqueue(source);
3154
+ }
3155
+ }
3156
+ fallbackWebCitations.length = 0;
3104
3157
  controller.enqueue({
3105
3158
  type: 'finish',
3106
3159
  finishReason,
@@ -56,9 +56,9 @@ export async function prepareTools({
56
56
  defaultEagerInputStreaming?: boolean;
57
57
 
58
58
  /**
59
- * Whether the model rejects forced tool use (`tool_choice` `any` or a
60
- * named tool). When true, `required` and `tool` tool choices fall back to
61
- * `auto` with a warning.
59
+ * Whether the model or thinking configuration rejects forced tool use
60
+ * (`tool_choice` `any` or a named tool). When true, `required` and `tool`
61
+ * tool choices fall back to `auto` with a warning.
62
62
  */
63
63
  rejectsForcedToolUse?: boolean;
64
64
  }): Promise<{
@@ -492,7 +492,7 @@ export async function prepareTools({
492
492
  type: 'unsupported',
493
493
  feature: 'toolChoice',
494
494
  details:
495
- `toolChoice 'required' is not supported by this model because it rejects forced tool use. ` +
495
+ `toolChoice 'required' is not supported with this model or thinking configuration. ` +
496
496
  `Using 'auto' instead. Instruct the model to use a tool in the prompt and verify that a tool call was made.`,
497
497
  });
498
498
  return {
@@ -515,15 +515,19 @@ 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({
523
527
  type: 'unsupported',
524
528
  feature: 'toolChoice',
525
529
  details:
526
- `toolChoice 'tool' is not supported by this model because it rejects forced tool use. ` +
530
+ `toolChoice 'tool' is not supported with this model or thinking configuration. ` +
527
531
  `Only the '${toolChoice.toolName}' tool is sent with 'auto' tool choice. ` +
528
532
  `Instruct the model to use the tool in the prompt and verify that a tool call was made.`,
529
533
  });
@@ -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
+ }