@ai-sdk/anthropic 4.0.46 → 4.0.48

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.
@@ -233,9 +233,14 @@ const result = streamText({
233
233
  });
234
234
  ```
235
235
 
236
+ For `claude-fable-5-1`, the default `"auto"` mode uses native structured
237
+ outputs through `output_config.format`. Fable 5.1 rejects forced tool use, so
238
+ do not use `"jsonTool"` or a required or named tool choice to implement
239
+ structured output with this model.
240
+
236
241
  ### Effort
237
242
 
238
- Anthropic introduced an `effort` option with `claude-opus-4-5` that affects thinking, text responses, and function calls. Effort defaults to `high` and you can set it to `medium` or `low` to save tokens and to lower time-to-last-token latency (TTLT). `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-fable-5`, and `claude-sonnet-5` additionally support `xhigh` for maximum reasoning effort.
243
+ Anthropic introduced an `effort` option with `claude-opus-4-5` that affects thinking, text responses, and function calls. Effort defaults to `high` and you can set it to `medium` or `low` to save tokens and to lower time-to-last-token latency (TTLT). `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-fable-5`, `claude-fable-5-1`, and `claude-sonnet-5` additionally support `xhigh` for maximum reasoning effort.
239
244
 
240
245
  On `claude-opus-5`, thinking can only be disabled at effort levels up to and including `high`. When you combine `thinking: { type: 'disabled' }` with `effort: 'xhigh'` or `effort: 'max'`, the AI SDK lowers the effort to `high` and emits a warning instead of sending a request that the API would reject.
241
246
 
@@ -484,6 +489,56 @@ console.log(text);
484
489
  `display: "summarized"` to restore visible progress during thinking.
485
490
  </Note>
486
491
 
492
+ ##### Thinking Updates
493
+
494
+ Use `display: 'updates'` with `claude-fable-5-1` to stream thinking summaries
495
+ between tool calls. The provider adds the required
496
+ `thinking-display-updates-2026-08-18` beta header automatically:
497
+
498
+ ```ts highlight="12-16"
499
+ import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
500
+ import { streamText, tool } from 'ai';
501
+ import { z } from 'zod';
502
+
503
+ const result = streamText({
504
+ model: anthropic('claude-fable-5-1'),
505
+ tools: {
506
+ weather: tool({
507
+ inputSchema: z.object({ city: z.string() }),
508
+ }),
509
+ },
510
+ providerOptions: {
511
+ anthropic: {
512
+ thinking: { type: 'adaptive', display: 'updates' },
513
+ } satisfies AnthropicLanguageModelOptions,
514
+ },
515
+ prompt: 'Compare the weather in San Francisco and New York.',
516
+ });
517
+ ```
518
+
519
+ ##### Thinking Binding Controls
520
+
521
+ Fable 5.1 can recover from a thinking-block prefix mismatch by dropping the
522
+ mismatched block. Set `blockBinding.prefixMismatchBehavior` to `drop_block`.
523
+ You can provide block binding by itself to preserve the model's default
524
+ thinking mode, or combine it with adaptive thinking.
525
+
526
+ ```ts highlight="7-11"
527
+ const { text } = await generateText({
528
+ model: anthropic('claude-fable-5-1'),
529
+ prompt: 'Continue from this conversation.',
530
+ providerOptions: {
531
+ anthropic: {
532
+ thinking: {
533
+ blockBinding: {
534
+ prefixMismatchBehavior: 'drop_block',
535
+ },
536
+ },
537
+ } satisfies AnthropicLanguageModelOptions,
538
+ },
539
+ });
540
+ ```
541
+
487
542
  #### Budget-Based Thinking
488
543
 
489
544
  For earlier models (`claude-opus-4-20250514`, `claude-sonnet-4-20250514`, `claude-sonnet-4-5-20250929`),
@@ -1279,6 +1334,59 @@ const result = await generateText({
1279
1334
 
1280
1335
  This sends `tool_reference` blocks to Anthropic, which loads the corresponding deferred tool schemas into Claude's context.
1281
1336
 
1337
+ ### Mid-Conversation System Controls
1338
+
1339
+ With `claude-fable-5-1`, a mid-conversation system message can be cleared
1340
+ before the next user message with `clearAt: 'next_user_message'`, or set the
1341
+ effort for the next turn. The provider adds the required
1342
+ `mid-conversation-system-clear-at-2026-08-21` and
1343
+ `mid-conversation-effort-2026-08-01` beta headers automatically.
1344
+
1345
+ ```ts highlight="17-25"
1346
+ import { anthropic } from '@ai-sdk/anthropic';
1347
+ import { generateText } from 'ai';
1348
+
1349
+ const result = await generateText({
1350
+ model: anthropic('claude-fable-5-1'),
1351
+ allowSystemInMessages: true,
1352
+ messages: [
1353
+ {
1354
+ role: 'user',
1355
+ content: 'Draft a migration plan.',
1356
+ },
1357
+ {
1358
+ role: 'assistant',
1359
+ content: 'First, inventory the public API.',
1360
+ },
1361
+ {
1362
+ role: 'system',
1363
+ content: 'For the next turn, verify every compatibility claim.',
1364
+ providerOptions: {
1365
+ anthropic: {
1366
+ clearAt: 'next_user_message',
1367
+ effort: 'high',
1368
+ },
1369
+ },
1370
+ },
1371
+ {
1372
+ role: 'user',
1373
+ content: 'Complete the plan.',
1374
+ },
1375
+ ],
1376
+ });
1377
+ ```
1378
+
1379
+ An effort-only system message can use an empty content array at the API level;
1380
+ in an AI SDK prompt, set `content: ''`. The provider serializes it as
1381
+ `content: []`.
1382
+
1383
+ <Note>
1384
+ `clearAt` and per-message `effort` apply only to mid-conversation system
1385
+ messages. When used on the initial system message, the provider ignores them
1386
+ and emits a warning. Use the top-level `effort` provider option to configure
1387
+ effort for the full request.
1388
+ </Note>
1389
+
1282
1390
  ### Mid-Conversation Tool Changes
1283
1391
 
1284
1392
  With `claude-opus-4-8`, you can add or remove tools between turns of a conversation without invalidating the prompt cache. Attach `toolChanges` to a system message that appears mid-conversation (right before an assistant message or at the end of the messages). The required `mid-conversation-tool-changes-2026-07-01` beta header is added automatically.
@@ -1760,6 +1868,7 @@ and the `mediaType` should be set to `'application/pdf'`.
1760
1868
  | ------------------- | ----------- | ----------------- | ---------- | ------------ | ---------- | ----------- | ---------- |
1761
1869
  | `claude-opus-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1762
1870
  | `claude-sonnet-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1871
+ | `claude-fable-5-1` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1763
1872
  | `claude-fable-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1764
1873
  | `claude-opus-4-8` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1765
1874
  | `claude-opus-4-7` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/anthropic",
3
- "version": "4.0.46",
3
+ "version": "4.0.48",
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.9",
39
- "@ai-sdk/provider-utils": "5.0.34"
38
+ "@ai-sdk/provider": "4.0.10",
39
+ "@ai-sdk/provider-utils": "5.0.36"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.1",
@@ -24,6 +24,10 @@ export type AnthropicCacheControl = {
24
24
  export interface AnthropicSystemMessage {
25
25
  role: 'system';
26
26
  content: Array<AnthropicTextContent | AnthropicToolChangeContent>;
27
+ clear_at?: 'next_user_message';
28
+ output_config?: {
29
+ effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
30
+ };
27
31
  }
28
32
 
29
33
  /**
@@ -49,6 +49,8 @@ export class AnthropicFiles implements FilesV4 {
49
49
  data,
50
50
  mediaType,
51
51
  filename,
52
+ abortSignal,
53
+ headers,
52
54
  }: FilesV4UploadFileCallOptions): Promise<FilesV4UploadFileResult> {
53
55
  const fileBytes = convertInlineFileDataToUint8Array(data);
54
56
 
@@ -63,14 +65,17 @@ export class AnthropicFiles implements FilesV4 {
63
65
 
64
66
  const { value: response } = await postFormDataToApi({
65
67
  url: `${this.config.baseURL}/files`,
66
- headers: combineHeaders(this.config.headers(), {
67
- 'anthropic-beta': 'files-api-2025-04-14',
68
- }),
68
+ headers: combineHeaders(
69
+ this.config.headers(),
70
+ { 'anthropic-beta': 'files-api-2025-04-14' },
71
+ headers,
72
+ ),
69
73
  formData,
70
74
  failedResponseHandler: anthropicFailedResponseHandler,
71
75
  successfulResponseHandler: createJsonResponseHandler(
72
76
  anthropicUploadFileResponseSchema,
73
77
  ),
78
+ abortSignal,
74
79
  fetch: this.config.fetch,
75
80
  });
76
81
 
@@ -21,6 +21,7 @@ export type AnthropicModelId =
21
21
  | 'claude-opus-4-8'
22
22
  | 'claude-opus-5'
23
23
  | 'claude-fable-5'
24
+ | 'claude-fable-5-1'
24
25
  | 'claude-sonnet-5'
25
26
  | (string & {});
26
27
 
@@ -70,6 +71,22 @@ export type AnthropicFilePartProviderOptions = z.infer<
70
71
  * Anthropic provider options for system messages.
71
72
  */
72
73
  export const anthropicSystemMessageProviderOptions = z.object({
74
+ /**
75
+ * Controls when an ephemeral mid-conversation system message is cleared.
76
+ *
77
+ * Only supported on system messages that appear mid-conversation.
78
+ * The required `mid-conversation-system-clear-at-2026-08-21` beta is
79
+ * added automatically.
80
+ */
81
+ clearAt: z.literal('next_user_message').optional(),
82
+
83
+ /**
84
+ * Sets the model effort for the turn that follows this mid-conversation
85
+ * system message. The required `mid-conversation-effort-2026-08-01` beta
86
+ * is added automatically.
87
+ */
88
+ effort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
89
+
73
90
  /**
74
91
  * Mid-conversation tool changes. Adds or removes tools from the
75
92
  * conversation's tool set between turns without invalidating the prompt
@@ -128,24 +145,42 @@ export const anthropicLanguageModelOptions = z.object({
128
145
  * Requires a minimum budget of 1,024 tokens and counts towards the `max_tokens` limit.
129
146
  */
130
147
  thinking: z
131
- .discriminatedUnion('type', [
132
- z.object({
133
- /** for Sonnet 4.6, Opus 4.6, and newer models */
134
- type: z.literal('adaptive'),
135
- /**
136
- * Controls whether thinking content is included in the response.
137
- * - `"omitted"`: Thinking blocks are present but text is empty (default for Opus 4.7+).
138
- * - `"summarized"`: Thinking content is returned. Required to see reasoning output.
139
- */
140
- display: z.enum(['omitted', 'summarized']).optional(),
141
- }),
142
- z.object({
143
- /** for models before Opus 4.6, except Sonnet 4.6 still supports it */
144
- type: z.literal('enabled'),
145
- budgetTokens: z.number().optional(),
146
- }),
148
+ .union([
149
+ z.discriminatedUnion('type', [
150
+ z.object({
151
+ /** for Sonnet 4.6, Opus 4.6, and newer models */
152
+ type: z.literal('adaptive'),
153
+ /**
154
+ * Controls whether thinking content is included in the response.
155
+ * - `"omitted"`: Thinking blocks are present but text is empty (default for Opus 4.7+).
156
+ * - `"summarized"`: Thinking content is returned.
157
+ * - `"updates"`: Thinking updates are returned between tool calls.
158
+ */
159
+ display: z.enum(['omitted', 'summarized', 'updates']).optional(),
160
+ /**
161
+ * Controls how thinking blocks are bound to an existing thinking
162
+ * prefix. Requires the `thinking-binding-controls-2026-08-01` beta.
163
+ */
164
+ blockBinding: z
165
+ .object({
166
+ prefixMismatchBehavior: z.literal('drop_block'),
167
+ })
168
+ .optional(),
169
+ }),
170
+ z.object({
171
+ /** for models before Opus 4.6, except Sonnet 4.6 still supports it */
172
+ type: z.literal('enabled'),
173
+ budgetTokens: z.number().optional(),
174
+ }),
175
+ z.object({
176
+ type: z.literal('disabled'),
177
+ }),
178
+ ]),
147
179
  z.object({
148
- type: z.literal('disabled'),
180
+ type: z.never().optional(),
181
+ blockBinding: z.object({
182
+ prefixMismatchBehavior: z.literal('drop_block'),
183
+ }),
149
184
  }),
150
185
  ])
151
186
  .optional(),
@@ -535,10 +535,17 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
535
535
  const thinkingType = anthropicOptions?.thinking?.type;
536
536
  const isThinking =
537
537
  thinkingType === 'enabled' || thinkingType === 'adaptive';
538
+ const thinkingBlockBinding =
539
+ anthropicOptions?.thinking != null &&
540
+ 'blockBinding' in anthropicOptions.thinking
541
+ ? anthropicOptions.thinking.blockBinding
542
+ : undefined;
538
543
  // `disabled` must still be forwarded to the API: some models (e.g. Sonnet 5)
539
544
  // default thinking on, so omitting it would leave thinking enabled and
540
- // consume the max_tokens budget.
541
- const sendThinking = isThinking || thinkingType === 'disabled';
545
+ // consume the max_tokens budget. Binding-only recovery requests must also
546
+ // send a thinking object without a type.
547
+ const sendThinking =
548
+ isThinking || thinkingType === 'disabled' || thinkingBlockBinding != null;
542
549
  let thinkingBudget =
543
550
  thinkingType === 'enabled'
544
551
  ? anthropicOptions?.thinking?.budgetTokens
@@ -564,9 +571,15 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
564
571
  // provider specific settings:
565
572
  ...(sendThinking && {
566
573
  thinking: {
567
- type: thinkingType,
574
+ ...(thinkingType != null && { type: thinkingType }),
568
575
  ...(thinkingBudget != null && { budget_tokens: thinkingBudget }),
569
576
  ...(thinkingDisplay != null && { display: thinkingDisplay }),
577
+ ...(thinkingBlockBinding != null && {
578
+ block_binding: {
579
+ prefix_mismatch_behavior:
580
+ thinkingBlockBinding.prefixMismatchBehavior,
581
+ },
582
+ }),
570
583
  },
571
584
  }),
572
585
  ...((anthropicOptions?.effort ||
@@ -843,6 +856,14 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
843
856
  betas.add('fast-mode-2026-02-01');
844
857
  }
845
858
 
859
+ if (thinkingDisplay === 'updates') {
860
+ betas.add('thinking-display-updates-2026-08-18');
861
+ }
862
+
863
+ if (thinkingBlockBinding != null) {
864
+ betas.add('thinking-binding-controls-2026-08-01');
865
+ }
866
+
846
867
  if (anthropicOptions?.fallbacks === 'default') {
847
868
  betas.add('server-side-fallback-2026-07-01');
848
869
  } else if (
@@ -144,8 +144,12 @@ export async function convertToAnthropicPrompt({
144
144
 
145
145
  switch (type) {
146
146
  case 'system': {
147
- const content: AnthropicSystemMessage['content'] = [];
148
- let toolChangeCount = 0;
147
+ const convertedMessages: Array<{
148
+ content: AnthropicSystemMessage['content'];
149
+ clearAt?: 'next_user_message';
150
+ effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
151
+ toolChangeCount: number;
152
+ }> = [];
149
153
 
150
154
  for (const { content: text, providerOptions } of block.messages) {
151
155
  const systemMessageOptions = await parseProviderOptions({
@@ -154,10 +158,16 @@ export async function convertToAnthropicPrompt({
154
158
  schema: anthropicSystemMessageProviderOptions,
155
159
  });
156
160
  const toolChanges = systemMessageOptions?.toolChanges ?? [];
157
-
158
- // A system message that only carries tool changes may have empty
159
- // text; do not emit an empty text block for it.
160
- if (text !== '' || toolChanges.length === 0) {
161
+ const content: AnthropicSystemMessage['content'] = [];
162
+
163
+ // A system message that only carries message-level controls may have
164
+ // empty text; do not emit an empty text block for it.
165
+ if (
166
+ text !== '' ||
167
+ (toolChanges.length === 0 &&
168
+ systemMessageOptions?.clearAt == null &&
169
+ systemMessageOptions?.effort == null)
170
+ ) {
161
171
  content.push({
162
172
  type: 'text' as const,
163
173
  text,
@@ -169,7 +179,6 @@ export async function convertToAnthropicPrompt({
169
179
  }
170
180
 
171
181
  for (const toolChange of toolChanges) {
172
- toolChangeCount++;
173
182
  content.push({
174
183
  type: toolChange.type,
175
184
  tool: {
@@ -178,6 +187,13 @@ export async function convertToAnthropicPrompt({
178
187
  },
179
188
  } satisfies AnthropicToolChangeContent);
180
189
  }
190
+
191
+ convertedMessages.push({
192
+ content,
193
+ clearAt: systemMessageOptions?.clearAt,
194
+ effort: systemMessageOptions?.effort,
195
+ toolChangeCount: toolChanges.length,
196
+ });
181
197
  }
182
198
 
183
199
  // The first block becomes the top-level system prompt. Later system
@@ -185,7 +201,17 @@ export async function convertToAnthropicPrompt({
185
201
  // tool changes (which are only valid mid-conversation), and otherwise
186
202
  // only when a top-level system prompt already exists (preserving the
187
203
  // existing hoisting behavior for plain text).
188
- if (i === 0 || (system == null && toolChangeCount === 0)) {
204
+ const toolChangeCount = convertedMessages.reduce(
205
+ (count, message) => count + message.toolChangeCount,
206
+ 0,
207
+ );
208
+ const hasInlineSystemOptions = convertedMessages.some(
209
+ message => message.clearAt != null || message.effort != null,
210
+ );
211
+ if (
212
+ i === 0 ||
213
+ (system == null && toolChangeCount === 0 && !hasInlineSystemOptions)
214
+ ) {
189
215
  if (toolChangeCount > 0) {
190
216
  warnings.push({
191
217
  type: 'other',
@@ -195,14 +221,45 @@ export async function convertToAnthropicPrompt({
195
221
  'The tool changes have been ignored.',
196
222
  });
197
223
  }
198
- system = content.filter(
199
- (part): part is AnthropicTextContent => part.type === 'text',
224
+
225
+ for (const message of convertedMessages) {
226
+ if (message.clearAt != null || message.effort != null) {
227
+ warnings.push({
228
+ type: 'other',
229
+ message:
230
+ 'clearAt and effort on the initial system message are not supported by Anthropic. ' +
231
+ 'These options have been ignored.',
232
+ });
233
+ }
234
+ }
235
+
236
+ system = convertedMessages.flatMap(message =>
237
+ message.content.filter(
238
+ (part): part is AnthropicTextContent => part.type === 'text',
239
+ ),
200
240
  );
201
241
  } else {
202
- messages.push({ role: 'system', content });
203
242
  betas.add('mid-conversation-system-2026-04-07');
204
- if (toolChangeCount > 0) {
205
- betas.add('mid-conversation-tool-changes-2026-07-01');
243
+
244
+ for (const message of convertedMessages) {
245
+ messages.push({
246
+ role: 'system',
247
+ content: message.content,
248
+ ...(message.clearAt != null && { clear_at: message.clearAt }),
249
+ ...(message.effort != null && {
250
+ output_config: { effort: message.effort },
251
+ }),
252
+ });
253
+
254
+ if (message.toolChangeCount > 0) {
255
+ betas.add('mid-conversation-tool-changes-2026-07-01');
256
+ }
257
+ if (message.clearAt != null) {
258
+ betas.add('mid-conversation-system-clear-at-2026-08-21');
259
+ }
260
+ if (message.effort != null) {
261
+ betas.add('mid-conversation-effort-2026-08-01');
262
+ }
206
263
  }
207
264
  }
208
265