@ai-sdk/anthropic 4.0.18 → 4.0.20

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.
@@ -175,7 +175,9 @@ const result = streamText({
175
175
 
176
176
  ### Effort
177
177
 
178
- 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-fable-5`, and `claude-sonnet-5` additionally support `xhigh` for maximum reasoning effort.
178
+ 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.
179
+
180
+ 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.
179
181
 
180
182
  ```ts highlight="8-10"
181
183
  import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
@@ -301,7 +303,26 @@ A classifier block looks like this:
301
303
 
302
304
  Branch on the finish reason rather than on the presence of `stop_details` — the API may return a refusal with no details at all.
303
305
 
304
- To avoid receiving a classifier block, pass the `fallbacks` option. When the primary model's classifiers block a turn, the API automatically retries it server-side on the next model in the chain and returns that model's answer. The required beta header is added for you.
306
+ To avoid receiving a classifier block, pass the `fallbacks` option. When the primary model's classifiers block a turn, the API automatically retries it server-side on a fallback model and returns that model's answer. The required beta header is added for you.
307
+
308
+ The recommended configuration is `fallbacks: 'default'`, which lets the API route the retry to Anthropic's recommended fallback model based on the refusal category and removes the need to migrate when a fallback model is deprecated:
309
+
310
+ ```ts highlight="8-10"
311
+ import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
312
+ import { generateText } from 'ai';
313
+
314
+ const { text } = await generateText({
315
+ model: anthropic('claude-fable-5'),
316
+ prompt: 'Explain the history of cryptography.',
317
+ providerOptions: {
318
+ anthropic: {
319
+ fallbacks: 'default',
320
+ } satisfies AnthropicLanguageModelOptions,
321
+ },
322
+ });
323
+ ```
324
+
325
+ You can also pin an explicit fallback chain:
305
326
 
306
327
  ```ts highlight="8-10"
307
328
  import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
@@ -1187,6 +1208,61 @@ const result = await generateText({
1187
1208
 
1188
1209
  This sends `tool_reference` blocks to Anthropic, which loads the corresponding deferred tool schemas into Claude's context.
1189
1210
 
1211
+ ### Mid-Conversation Tool Changes
1212
+
1213
+ 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.
1214
+
1215
+ Tools referenced by a `tool_addition` must be declared in the `tools` option — typically with `deferLoading: true`, so they are not loaded into context until the addition surfaces them. A `tool_removal` removes a previously available tool from the conversation going forward.
1216
+
1217
+ ```ts
1218
+ import { anthropic } from '@ai-sdk/anthropic';
1219
+ import { generateText, tool } from 'ai';
1220
+ import { z } from 'zod';
1221
+
1222
+ const result = await generateText({
1223
+ model: anthropic('claude-opus-4-8'),
1224
+ // required for system messages inside `messages`:
1225
+ allowSystemInMessages: true,
1226
+ tools: {
1227
+ get_weather: tool({
1228
+ description: 'Get weather',
1229
+ inputSchema: z.object({ city: z.string() }),
1230
+ }),
1231
+ get_forecast: tool({
1232
+ description: 'Get 5-day forecast',
1233
+ inputSchema: z.object({ city: z.string() }),
1234
+ providerOptions: {
1235
+ // declared up front but not loaded until a tool_addition surfaces it
1236
+ anthropic: { deferLoading: true },
1237
+ },
1238
+ }),
1239
+ },
1240
+ messages: [
1241
+ { role: 'user', content: 'What tools do you have for weather in Paris?' },
1242
+ {
1243
+ role: 'system',
1244
+ content: '',
1245
+ providerOptions: {
1246
+ anthropic: {
1247
+ toolChanges: [
1248
+ { type: 'tool_addition', toolName: 'get_forecast' },
1249
+ { type: 'tool_removal', toolName: 'get_weather' },
1250
+ ],
1251
+ },
1252
+ },
1253
+ },
1254
+ ],
1255
+ });
1256
+ ```
1257
+
1258
+ To change a tool's definition, remove the old definition at the end of one request, then carry the conversation forward with the updated definition in `tools` on the next request.
1259
+
1260
+ <Note>
1261
+ Tool changes are not supported on the initial system message. The AI SDK
1262
+ ignores them there and emits a warning — configure the initial tool set via
1263
+ the `tools` option instead.
1264
+ </Note>
1265
+
1190
1266
  ### MCP Connectors
1191
1267
 
1192
1268
  Anthropic supports connecting to [MCP servers](https://docs.claude.com/en/docs/agents-and-tools/mcp-connector) as part of their execution.
@@ -1611,6 +1687,7 @@ and the `mediaType` should be set to `'application/pdf'`.
1611
1687
 
1612
1688
  | Model | Image Input | Object Generation | Tool Usage | Computer Use | Web Search | Tool Search | Compaction |
1613
1689
  | ------------------- | ----------- | ----------------- | ---------- | ------------ | ---------- | ----------- | ---------- |
1690
+ | `claude-opus-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1614
1691
  | `claude-sonnet-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1615
1692
  | `claude-fable-5` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
1616
1693
  | `claude-opus-4-8` | <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.18",
3
+ "version": "4.0.20",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -43,8 +43,8 @@
43
43
  "tsup": "^8.5.1",
44
44
  "typescript": "5.8.3",
45
45
  "zod": "3.25.76",
46
- "@ai-sdk/test-server": "2.0.0",
47
- "@vercel/ai-tsconfig": "0.0.0"
46
+ "@vercel/ai-tsconfig": "0.0.0",
47
+ "@ai-sdk/test-server": "2.0.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "zod": "^3.25.76 || ^4.1.8"
@@ -23,7 +23,20 @@ export type AnthropicCacheControl = {
23
23
 
24
24
  export interface AnthropicSystemMessage {
25
25
  role: 'system';
26
- content: Array<AnthropicTextContent>;
26
+ content: Array<AnthropicTextContent | AnthropicToolChangeContent>;
27
+ }
28
+
29
+ /**
30
+ * Mid-conversation tool change content block. Adds or removes a tool from the
31
+ * conversation's tool set without invalidating the prompt cache.
32
+ *
33
+ * Only valid inside system messages that appear in the `messages` array.
34
+ * Requires the `mid-conversation-tool-changes-2026-07-01` beta.
35
+ */
36
+ export interface AnthropicToolChangeContent {
37
+ type: 'tool_addition' | 'tool_removal';
38
+ tool: { type: 'tool_reference'; name: string };
39
+ cache_control?: never;
27
40
  }
28
41
 
29
42
  export interface AnthropicUserMessage {
@@ -19,6 +19,7 @@ export type AnthropicModelId =
19
19
  | 'claude-opus-4-6'
20
20
  | 'claude-opus-4-7'
21
21
  | 'claude-opus-4-8'
22
+ | 'claude-opus-5'
22
23
  | 'claude-fable-5'
23
24
  | 'claude-sonnet-5'
24
25
  | (string & {});
@@ -65,6 +66,44 @@ export type AnthropicFilePartProviderOptions = z.infer<
65
66
  typeof anthropicFilePartProviderOptions
66
67
  >;
67
68
 
69
+ /**
70
+ * Anthropic provider options for system messages.
71
+ */
72
+ export const anthropicSystemMessageProviderOptions = z.object({
73
+ /**
74
+ * Mid-conversation tool changes. Adds or removes tools from the
75
+ * conversation's tool set between turns without invalidating the prompt
76
+ * cache.
77
+ *
78
+ * Only supported on system messages that appear mid-conversation (not the
79
+ * initial system prompt). A system message carrying tool changes must come
80
+ * right before an assistant message or at the end of the messages.
81
+ *
82
+ * Tools referenced by a `tool_addition` must be declared in the `tools`
83
+ * option (typically with `deferLoading: true` so they are not loaded until
84
+ * the addition surfaces them). The required
85
+ * `mid-conversation-tool-changes-2026-07-01` beta is added automatically.
86
+ */
87
+ toolChanges: z
88
+ .array(
89
+ z.discriminatedUnion('type', [
90
+ z.object({
91
+ type: z.literal('tool_addition'),
92
+ toolName: z.string(),
93
+ }),
94
+ z.object({
95
+ type: z.literal('tool_removal'),
96
+ toolName: z.string(),
97
+ }),
98
+ ]),
99
+ )
100
+ .optional(),
101
+ });
102
+
103
+ export type AnthropicSystemMessageProviderOptions = z.infer<
104
+ typeof anthropicSystemMessageProviderOptions
105
+ >;
106
+
68
107
  export const anthropicLanguageModelOptions = z.object({
69
108
  /**
70
109
  * Whether to send reasoning to the model.
@@ -240,31 +279,36 @@ export const anthropicLanguageModelOptions = z.object({
240
279
  inferenceGeo: z.enum(['us', 'global']).optional(),
241
280
 
242
281
  /**
243
- * Server-side fallback chain.
282
+ * Server-side fallback configuration.
244
283
  *
245
284
  * When the primary model's safety classifiers block a turn, the API
246
- * automatically retries it on the next model in the chain, server-side. A
247
- * `content-filter` finish reason means the entire chain refused.
285
+ * automatically retries it server-side on a fallback model. A
286
+ * `content-filter` finish reason means the fallback(s) refused as well.
248
287
  *
249
- * Each entry is merged into the request as a direct request to that entry's
250
- * model, so it must be formatted accordingly: `model` is required, and an
251
- * entry may additionally override `max_tokens`, `thinking`, `output_config`,
252
- * and `speed` for that attempt only (`speed` additionally requires the speed
253
- * beta). The value is passed through to the API as-is.
254
- *
255
- * The required `server-side-fallback-2026-06-01` beta is added automatically
256
- * when this option is set.
288
+ * - `'default'` (recommended): the API routes the retry to Anthropic's
289
+ * recommended fallback model based on the refusal category. Requires the
290
+ * `server-side-fallback-2026-07-01` beta, which is added automatically.
291
+ * - Array form: an explicit fallback chain. Each entry is merged into the
292
+ * request as a direct request to that entry's model, so it must be
293
+ * formatted accordingly: `model` is required, and an entry may
294
+ * additionally override `max_tokens`, `thinking`, `output_config`, and
295
+ * `speed` for that attempt only (`speed` additionally requires the speed
296
+ * beta). The value is passed through to the API as-is, and the
297
+ * `server-side-fallback-2026-06-01` beta is added automatically.
257
298
  */
258
299
  fallbacks: z
259
- .array(
260
- z.object({
261
- model: z.string(),
262
- max_tokens: z.number().int().optional(),
263
- thinking: z.record(z.string(), z.unknown()).optional(),
264
- output_config: z.record(z.string(), z.unknown()).optional(),
265
- speed: z.enum(['fast', 'standard']).optional(),
266
- }),
267
- )
300
+ .union([
301
+ z.literal('default'),
302
+ z.array(
303
+ z.object({
304
+ model: z.string(),
305
+ max_tokens: z.number().int().optional(),
306
+ thinking: z.record(z.string(), z.unknown()).optional(),
307
+ output_config: z.record(z.string(), z.unknown()).optional(),
308
+ speed: z.enum(['fast', 'standard']).optional(),
309
+ }),
310
+ ),
311
+ ])
268
312
  .optional(),
269
313
 
270
314
  /**
@@ -298,6 +298,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
298
298
  supportsAdaptiveThinking,
299
299
  rejectsSamplingParameters,
300
300
  supportsXhighEffort,
301
+ rejectsThinkingDisabledAboveHighEffort,
301
302
  isKnownModel,
302
303
  } = getModelCapabilities(this.modelId);
303
304
 
@@ -339,7 +340,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
339
340
  }
340
341
  }
341
342
 
342
- const isAnthropicModel = isKnownModel || this.modelId.startsWith('claude-');
343
+ const isAnthropicModel = isKnownModel || this.modelId.includes('claude-');
343
344
 
344
345
  const supportsStructuredOutput =
345
346
  (this.config.supportsNativeStructuredOutput ?? true) &&
@@ -443,6 +444,25 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
443
444
  }
444
445
  }
445
446
 
447
+ // Newer models only allow disabling thinking at effort levels up to and
448
+ // including `high`; at `xhigh` and `max` the API returns a 400. Lower
449
+ // the effort to `high` to preserve the explicit request to run without
450
+ // thinking.
451
+ if (
452
+ rejectsThinkingDisabledAboveHighEffort &&
453
+ anthropicOptions?.thinking?.type === 'disabled' &&
454
+ (anthropicOptions.effort === 'xhigh' || anthropicOptions.effort === 'max')
455
+ ) {
456
+ warnings.push({
457
+ type: 'unsupported',
458
+ feature: 'providerOptions.anthropic.effort',
459
+ details:
460
+ `effort '${anthropicOptions.effort}' is not supported by ${this.modelId} when thinking is disabled. ` +
461
+ `The effort has been lowered to 'high'.`,
462
+ });
463
+ anthropicOptions.effort = 'high';
464
+ }
465
+
446
466
  const thinkingType = anthropicOptions?.thinking?.type;
447
467
  const isThinking =
448
468
  thinkingType === 'enabled' || thinkingType === 'adaptive';
@@ -514,8 +534,9 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
514
534
  ...(anthropicOptions?.inferenceGeo && {
515
535
  inference_geo: anthropicOptions.inferenceGeo,
516
536
  }),
517
- ...(anthropicOptions?.fallbacks &&
518
- anthropicOptions.fallbacks.length > 0 && {
537
+ ...(anthropicOptions?.fallbacks != null &&
538
+ (anthropicOptions.fallbacks === 'default' ||
539
+ anthropicOptions.fallbacks.length > 0) && {
519
540
  fallbacks: anthropicOptions.fallbacks,
520
541
  }),
521
542
  ...(anthropicOptions?.cacheControl && {
@@ -750,7 +771,12 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
750
771
  betas.add('fast-mode-2026-02-01');
751
772
  }
752
773
 
753
- if (anthropicOptions?.fallbacks && anthropicOptions.fallbacks.length > 0) {
774
+ if (anthropicOptions?.fallbacks === 'default') {
775
+ betas.add('server-side-fallback-2026-07-01');
776
+ } else if (
777
+ anthropicOptions?.fallbacks &&
778
+ anthropicOptions.fallbacks.length > 0
779
+ ) {
754
780
  betas.add('server-side-fallback-2026-06-01');
755
781
  }
756
782
 
@@ -2650,9 +2676,20 @@ export function getModelCapabilities(modelId: string): {
2650
2676
  supportsAdaptiveThinking: boolean;
2651
2677
  rejectsSamplingParameters: boolean;
2652
2678
  supportsXhighEffort: boolean;
2679
+ rejectsThinkingDisabledAboveHighEffort: boolean;
2653
2680
  isKnownModel: boolean;
2654
2681
  } {
2655
- if (
2682
+ if (modelId.includes('claude-opus-5')) {
2683
+ return {
2684
+ maxOutputTokens: 128000,
2685
+ supportsStructuredOutput: true,
2686
+ supportsAdaptiveThinking: true,
2687
+ rejectsSamplingParameters: true,
2688
+ supportsXhighEffort: true,
2689
+ rejectsThinkingDisabledAboveHighEffort: true,
2690
+ isKnownModel: true,
2691
+ };
2692
+ } else if (
2656
2693
  modelId.includes('claude-opus-4-8') ||
2657
2694
  modelId.includes('claude-opus-4-7') ||
2658
2695
  modelId.includes('claude-fable-5') ||
@@ -2664,6 +2701,7 @@ export function getModelCapabilities(modelId: string): {
2664
2701
  supportsAdaptiveThinking: true,
2665
2702
  rejectsSamplingParameters: true,
2666
2703
  supportsXhighEffort: true,
2704
+ rejectsThinkingDisabledAboveHighEffort: false,
2667
2705
  isKnownModel: true,
2668
2706
  };
2669
2707
  } else if (
@@ -2676,6 +2714,7 @@ export function getModelCapabilities(modelId: string): {
2676
2714
  supportsAdaptiveThinking: true,
2677
2715
  rejectsSamplingParameters: false,
2678
2716
  supportsXhighEffort: false,
2717
+ rejectsThinkingDisabledAboveHighEffort: false,
2679
2718
  isKnownModel: true,
2680
2719
  };
2681
2720
  } else if (
@@ -2689,6 +2728,7 @@ export function getModelCapabilities(modelId: string): {
2689
2728
  supportsAdaptiveThinking: false,
2690
2729
  rejectsSamplingParameters: false,
2691
2730
  supportsXhighEffort: false,
2731
+ rejectsThinkingDisabledAboveHighEffort: false,
2692
2732
  isKnownModel: true,
2693
2733
  };
2694
2734
  } else if (modelId.includes('claude-opus-4-1')) {
@@ -2698,6 +2738,7 @@ export function getModelCapabilities(modelId: string): {
2698
2738
  supportsAdaptiveThinking: false,
2699
2739
  rejectsSamplingParameters: false,
2700
2740
  supportsXhighEffort: false,
2741
+ rejectsThinkingDisabledAboveHighEffort: false,
2701
2742
  isKnownModel: true,
2702
2743
  };
2703
2744
  } else if (modelId.includes('claude-sonnet-4-')) {
@@ -2707,6 +2748,7 @@ export function getModelCapabilities(modelId: string): {
2707
2748
  supportsAdaptiveThinking: false,
2708
2749
  rejectsSamplingParameters: false,
2709
2750
  supportsXhighEffort: false,
2751
+ rejectsThinkingDisabledAboveHighEffort: false,
2710
2752
  isKnownModel: true,
2711
2753
  };
2712
2754
  } else if (modelId.includes('claude-opus-4-')) {
@@ -2716,6 +2758,7 @@ export function getModelCapabilities(modelId: string): {
2716
2758
  supportsAdaptiveThinking: false,
2717
2759
  rejectsSamplingParameters: false,
2718
2760
  supportsXhighEffort: false,
2761
+ rejectsThinkingDisabledAboveHighEffort: false,
2719
2762
  isKnownModel: true,
2720
2763
  };
2721
2764
  } else if (modelId.includes('claude-3-haiku')) {
@@ -2725,15 +2768,44 @@ export function getModelCapabilities(modelId: string): {
2725
2768
  supportsAdaptiveThinking: false,
2726
2769
  rejectsSamplingParameters: false,
2727
2770
  supportsXhighEffort: false,
2771
+ rejectsThinkingDisabledAboveHighEffort: false,
2728
2772
  isKnownModel: true,
2729
2773
  };
2774
+ } else if (
2775
+ /claude-(?:instant(?:-|$)|v?2(?=$|[-.:])|3(?=$|[-.]))/.test(modelId)
2776
+ ) {
2777
+ return {
2778
+ maxOutputTokens: 4096,
2779
+ supportsStructuredOutput: false,
2780
+ supportsAdaptiveThinking: false,
2781
+ rejectsSamplingParameters: false,
2782
+ supportsXhighEffort: false,
2783
+ rejectsThinkingDisabledAboveHighEffort: false,
2784
+ isKnownModel: false,
2785
+ };
2786
+ } else if (modelId.includes('claude-')) {
2787
+ // Known and legacy Claude families are handled above, so any remaining
2788
+ // Claude ID is assumed to be newer than the known list. Keep it unknown so
2789
+ // callers still receive the maxOutputTokens compatibility warning.
2790
+ return {
2791
+ maxOutputTokens: 128000,
2792
+ supportsStructuredOutput: true,
2793
+ supportsAdaptiveThinking: true,
2794
+ rejectsSamplingParameters: true,
2795
+ supportsXhighEffort: true,
2796
+ rejectsThinkingDisabledAboveHighEffort: true,
2797
+ isKnownModel: false,
2798
+ };
2730
2799
  } else {
2800
+ // Non-Claude models (e.g. served through Anthropic-compatible APIs)
2801
+ // keep conservative defaults.
2731
2802
  return {
2732
2803
  maxOutputTokens: 4096,
2733
2804
  supportsStructuredOutput: false,
2734
2805
  supportsAdaptiveThinking: false,
2735
2806
  rejectsSamplingParameters: false,
2736
2807
  supportsXhighEffort: false,
2808
+ rejectsThinkingDisabledAboveHighEffort: false,
2737
2809
  isKnownModel: false,
2738
2810
  };
2739
2811
  }
@@ -23,12 +23,18 @@ import {
23
23
  anthropicReasoningMetadataSchema,
24
24
  type AnthropicAssistantMessage,
25
25
  type AnthropicPrompt,
26
+ type AnthropicSystemMessage,
27
+ type AnthropicTextContent,
28
+ type AnthropicToolChangeContent,
26
29
  type AnthropicToolResultContent,
27
30
  type AnthropicUserMessage,
28
31
  type AnthropicWebFetchToolResultContent,
29
32
  type Citation,
30
33
  } from './anthropic-api';
31
- import { anthropicFilePartProviderOptions } from './anthropic-language-model-options';
34
+ import {
35
+ anthropicFilePartProviderOptions,
36
+ anthropicSystemMessageProviderOptions,
37
+ } from './anthropic-language-model-options';
32
38
  import { CacheControlValidator } from './get-cache-control';
33
39
  import { advisor_20260301OutputSchema } from './tool/advisor_20260301';
34
40
  import { codeExecution_20250522OutputSchema } from './tool/code-execution_20250522';
@@ -137,20 +143,66 @@ export async function convertToAnthropicPrompt({
137
143
 
138
144
  switch (type) {
139
145
  case 'system': {
140
- const content = block.messages.map(({ content, providerOptions }) => ({
141
- type: 'text' as const,
142
- text: content,
143
- cache_control: validator.getCacheControl(providerOptions, {
144
- type: 'system message',
145
- canCache: true,
146
- }),
147
- }));
148
-
149
- if (system == null) {
150
- system = content;
146
+ const content: AnthropicSystemMessage['content'] = [];
147
+ let toolChangeCount = 0;
148
+
149
+ for (const { content: text, providerOptions } of block.messages) {
150
+ const systemMessageOptions = await parseProviderOptions({
151
+ provider: 'anthropic',
152
+ providerOptions,
153
+ schema: anthropicSystemMessageProviderOptions,
154
+ });
155
+ const toolChanges = systemMessageOptions?.toolChanges ?? [];
156
+
157
+ // A system message that only carries tool changes may have empty
158
+ // text; do not emit an empty text block for it.
159
+ if (text !== '' || toolChanges.length === 0) {
160
+ content.push({
161
+ type: 'text' as const,
162
+ text,
163
+ cache_control: validator.getCacheControl(providerOptions, {
164
+ type: 'system message',
165
+ canCache: true,
166
+ }),
167
+ });
168
+ }
169
+
170
+ for (const toolChange of toolChanges) {
171
+ toolChangeCount++;
172
+ content.push({
173
+ type: toolChange.type,
174
+ tool: {
175
+ type: 'tool_reference',
176
+ name: toolNameMapping.toProviderToolName(toolChange.toolName),
177
+ },
178
+ } satisfies AnthropicToolChangeContent);
179
+ }
180
+ }
181
+
182
+ // The first block becomes the top-level system prompt. Later system
183
+ // blocks are sent as inline system messages — always when they carry
184
+ // tool changes (which are only valid mid-conversation), and otherwise
185
+ // only when a top-level system prompt already exists (preserving the
186
+ // existing hoisting behavior for plain text).
187
+ if (i === 0 || (system == null && toolChangeCount === 0)) {
188
+ if (toolChangeCount > 0) {
189
+ warnings.push({
190
+ type: 'other',
191
+ message:
192
+ 'tool changes on the initial system message are not supported by Anthropic. ' +
193
+ 'Configure the initial tool set via the tools option instead. ' +
194
+ 'The tool changes have been ignored.',
195
+ });
196
+ }
197
+ system = content.filter(
198
+ (part): part is AnthropicTextContent => part.type === 'text',
199
+ );
151
200
  } else {
152
201
  messages.push({ role: 'system', content });
153
202
  betas.add('mid-conversation-system-2026-04-07');
203
+ if (toolChangeCount > 0) {
204
+ betas.add('mid-conversation-tool-changes-2026-07-01');
205
+ }
154
206
  }
155
207
 
156
208
  break;
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export type {
6
6
  AnthropicLanguageModelOptions,
7
7
  /** @deprecated Use `AnthropicLanguageModelOptions` instead. */
8
8
  AnthropicLanguageModelOptions as AnthropicProviderOptions,
9
+ AnthropicSystemMessageProviderOptions,
9
10
  } from './anthropic-language-model-options';
10
11
  export type { AnthropicToolOptions } from './anthropic-prepare-tools';
11
12
  export { anthropic, createAnthropic } from './anthropic-provider';