@ai-sdk/anthropic 3.0.119 → 3.0.120

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.
@@ -138,6 +138,48 @@ function getAnthropicCallerMetadata(
138
138
  : { providerMetadata: { anthropic: { caller: callerInfo } } };
139
139
  }
140
140
 
141
+ /**
142
+ * Maps the custom tool names of toolset tools (e.g. the computer toolset) to
143
+ * the toolset name that the Anthropic API uses in `toolset_name`.
144
+ */
145
+ function getAnthropicToolsetNames(
146
+ tools: LanguageModelV3CallOptions['tools'],
147
+ ): Record<string, string> {
148
+ const toolsetNames: Record<string, string> = {};
149
+
150
+ for (const tool of tools ?? []) {
151
+ if (
152
+ tool.type === 'provider' &&
153
+ tool.id === 'anthropic.computer_toolset_20260801'
154
+ ) {
155
+ toolsetNames[tool.name] = 'computer';
156
+ }
157
+ }
158
+
159
+ return toolsetNames;
160
+ }
161
+
162
+ /**
163
+ * Toolset member calls (e.g. `left_click` from the computer toolset) are
164
+ * exposed as a single tool call on the toolset tool. The member name is
165
+ * injected as the `action` property, matching the input shape of the older
166
+ * computer tool versions.
167
+ */
168
+ function toToolsetMemberInput({
169
+ memberName,
170
+ input,
171
+ }: {
172
+ memberName: string;
173
+ input: unknown;
174
+ }): JSONObject {
175
+ return {
176
+ action: memberName,
177
+ ...(typeof input === 'object' && input !== null && !Array.isArray(input)
178
+ ? (input as JSONObject)
179
+ : {}),
180
+ };
181
+ }
182
+
141
183
  type AnthropicMessagesConfig = {
142
184
  provider: string;
143
185
  baseURL: string;
@@ -297,6 +339,8 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
297
339
  supportsStructuredOutput: modelSupportsStructuredOutput,
298
340
  rejectsSamplingParameters,
299
341
  rejectsThinkingDisabledAboveHighEffort,
342
+ rejectsThinkingDisabled,
343
+ rejectsForcedToolUse,
300
344
  isKnownModel,
301
345
  } = getModelCapabilities(this.modelId);
302
346
 
@@ -350,10 +394,30 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
350
394
 
351
395
  const structureOutputMode =
352
396
  anthropicOptions?.structuredOutputMode ?? 'auto';
353
- const useStructuredOutput =
397
+ let useStructuredOutput =
354
398
  structureOutputMode === 'outputFormat' ||
355
399
  (structureOutputMode === 'auto' && supportsStructuredOutput);
356
400
 
401
+ // The JSON response tool relies on forced tool use, which some models
402
+ // reject (e.g. Opus 5.5, Fable 5.1). Fall back to native structured
403
+ // outputs when the model supports them.
404
+ if (
405
+ !useStructuredOutput &&
406
+ rejectsForcedToolUse &&
407
+ supportsStructuredOutput &&
408
+ responseFormat?.type === 'json' &&
409
+ responseFormat.schema != null
410
+ ) {
411
+ warnings.push({
412
+ type: 'unsupported',
413
+ feature: 'providerOptions.anthropic.structuredOutputMode',
414
+ details:
415
+ `structuredOutputMode 'jsonTool' is not supported by ${this.modelId} because it rejects forced tool use. ` +
416
+ `Using 'outputFormat' instead.`,
417
+ });
418
+ useStructuredOutput = true;
419
+ }
420
+
357
421
  const jsonResponseTool: LanguageModelV3FunctionTool | undefined =
358
422
  responseFormat?.type === 'json' &&
359
423
  responseFormat.schema != null &&
@@ -392,6 +456,8 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
392
456
  'anthropic.code_execution_20260120': 'code_execution',
393
457
  'anthropic.computer_20241022': 'computer',
394
458
  'anthropic.computer_20250124': 'computer',
459
+ 'anthropic.computer_20251124': 'computer',
460
+ 'anthropic.computer_toolset_20260801': 'computer',
395
461
  'anthropic.text_editor_20241022': 'str_replace_editor',
396
462
  'anthropic.text_editor_20250124': 'str_replace_editor',
397
463
  'anthropic.text_editor_20250429': 'str_replace_based_edit_tool',
@@ -409,6 +475,8 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
409
475
  },
410
476
  });
411
477
 
478
+ const toolsetNames = getAnthropicToolsetNames(tools);
479
+
412
480
  const { prompt: messagesPrompt, betas } =
413
481
  await convertToAnthropicMessagesPrompt({
414
482
  prompt,
@@ -416,8 +484,38 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
416
484
  warnings,
417
485
  cacheControlValidator,
418
486
  toolNameMapping,
487
+ toolsetNames,
419
488
  });
420
489
 
490
+ // Some models always run adaptive thinking and reject `disabled` and
491
+ // budget-based `enabled` thinking with a 400. Drop the unsupported
492
+ // setting and keep the request adaptive so it still succeeds.
493
+ if (rejectsThinkingDisabled && anthropicOptions?.thinking != null) {
494
+ const requestedThinking = anthropicOptions.thinking;
495
+ const requestedType =
496
+ 'type' in requestedThinking ? requestedThinking.type : undefined;
497
+
498
+ if (requestedType === 'disabled') {
499
+ warnings.push({
500
+ type: 'unsupported',
501
+ feature: 'providerOptions.anthropic.thinking',
502
+ details:
503
+ `thinking cannot be disabled for ${this.modelId}; it always uses adaptive thinking. ` +
504
+ `The thinking setting has been removed. Lower 'effort' to reduce thinking.`,
505
+ });
506
+ anthropicOptions.thinking = undefined;
507
+ } else if (requestedType === 'enabled') {
508
+ warnings.push({
509
+ type: 'unsupported',
510
+ feature: 'providerOptions.anthropic.thinking',
511
+ details:
512
+ `budget-based thinking is not supported by ${this.modelId}; it always uses adaptive thinking. ` +
513
+ `Using adaptive thinking instead. Use 'effort' to control how much the model thinks.`,
514
+ });
515
+ anthropicOptions.thinking = { type: 'adaptive' };
516
+ }
517
+ }
518
+
421
519
  const thinking = anthropicOptions?.thinking;
422
520
  const thinkingType =
423
521
  thinking != null && 'type' in thinking ? thinking.type : undefined;
@@ -790,6 +888,7 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
790
888
  supportsStructuredOutput: false,
791
889
  supportsStrictTools,
792
890
  defaultEagerInputStreaming,
891
+ rejectsForcedToolUse,
793
892
  }
794
893
  : {
795
894
  tools: tools ?? [],
@@ -799,6 +898,7 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
799
898
  supportsStructuredOutput,
800
899
  supportsStrictTools,
801
900
  defaultEagerInputStreaming,
901
+ rejectsForcedToolUse,
802
902
  },
803
903
  );
804
904
 
@@ -1049,6 +1149,27 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
1049
1149
  type: 'text',
1050
1150
  text: JSON.stringify(part.input),
1051
1151
  });
1152
+ } else if (part.toolset_name != null) {
1153
+ // toolset member calls (e.g. the computer toolset) are mapped to
1154
+ // the toolset tool with the member name as the `action`:
1155
+ content.push({
1156
+ type: 'tool-call',
1157
+ toolCallId: part.id,
1158
+ toolName: toolNameMapping.toCustomToolName(part.toolset_name),
1159
+ input: JSON.stringify(
1160
+ toToolsetMemberInput({
1161
+ memberName: part.name,
1162
+ input: part.input,
1163
+ }),
1164
+ ),
1165
+ providerMetadata: {
1166
+ anthropic: {
1167
+ toolsetName: part.toolset_name,
1168
+ ...getAnthropicCallerMetadata(part.caller).providerMetadata
1169
+ ?.anthropic,
1170
+ },
1171
+ },
1172
+ });
1052
1173
  } else {
1053
1174
  content.push({
1054
1175
  type: 'tool-call',
@@ -1554,6 +1675,12 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
1554
1675
  firstDelta: boolean;
1555
1676
  providerToolName?: string;
1556
1677
  providerToolInputType?: string;
1678
+ /**
1679
+ * Set for toolset member calls (e.g. the computer toolset). The raw
1680
+ * member input is accumulated and emitted as a single delta with the
1681
+ * member name injected as `action` when the block completes.
1682
+ */
1683
+ toolset?: { name: string; memberName: string };
1557
1684
  caller?: {
1558
1685
  type:
1559
1686
  | 'code_execution_20250825'
@@ -1720,6 +1847,34 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
1720
1847
  type: 'text-start',
1721
1848
  id: String(value.index),
1722
1849
  });
1850
+ } else if (part.toolset_name != null) {
1851
+ const callerInfo = getAnthropicCallerInfo(part.caller);
1852
+ const customToolName = toolNameMapping.toCustomToolName(
1853
+ part.toolset_name,
1854
+ );
1855
+ const hasNonEmptyInput =
1856
+ part.input && Object.keys(part.input).length > 0;
1857
+
1858
+ // toolset member input is accumulated and emitted when the
1859
+ // block completes (see content_block_stop):
1860
+ contentBlocks[value.index] = {
1861
+ type: 'tool-call',
1862
+ toolCallId: part.id,
1863
+ toolName: customToolName,
1864
+ input: hasNonEmptyInput ? JSON.stringify(part.input) : '',
1865
+ firstDelta: true,
1866
+ toolset: {
1867
+ name: part.toolset_name,
1868
+ memberName: part.name,
1869
+ },
1870
+ ...(callerInfo && { caller: callerInfo }),
1871
+ };
1872
+
1873
+ controller.enqueue({
1874
+ type: 'tool-input-start',
1875
+ id: part.id,
1876
+ toolName: customToolName,
1877
+ });
1723
1878
  } else {
1724
1879
  const callerInfo = getAnthropicCallerInfo(part.caller);
1725
1880
 
@@ -2200,6 +2355,37 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
2200
2355
  usesJsonResponseTool && contentBlock.toolName === 'json';
2201
2356
 
2202
2357
  if (!isJsonResponseTool) {
2358
+ // toolset member calls: emit the accumulated input with
2359
+ // the member name injected as `action` in one delta.
2360
+ if (contentBlock.toolset != null) {
2361
+ let memberInput: unknown = {};
2362
+ try {
2363
+ memberInput =
2364
+ contentBlock.input === ''
2365
+ ? {}
2366
+ : secureJsonParse(contentBlock.input);
2367
+ } catch {
2368
+ // ignore parse errors, fall back to the raw input
2369
+ memberInput = undefined;
2370
+ }
2371
+
2372
+ contentBlock.input =
2373
+ memberInput === undefined
2374
+ ? contentBlock.input
2375
+ : JSON.stringify(
2376
+ toToolsetMemberInput({
2377
+ memberName: contentBlock.toolset.memberName,
2378
+ input: memberInput,
2379
+ }),
2380
+ );
2381
+
2382
+ controller.enqueue({
2383
+ type: 'tool-input-delta',
2384
+ id: contentBlock.toolCallId,
2385
+ delta: contentBlock.input,
2386
+ });
2387
+ }
2388
+
2203
2389
  controller.enqueue({
2204
2390
  type: 'tool-input-end',
2205
2391
  id: contentBlock.toolCallId,
@@ -2242,10 +2428,15 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
2242
2428
  contentBlock.providerToolName === 'code_execution'
2243
2429
  ? { dynamic: true }
2244
2430
  : {}),
2245
- ...(contentBlock.caller && {
2431
+ ...((contentBlock.caller || contentBlock.toolset) && {
2246
2432
  providerMetadata: {
2247
2433
  anthropic: {
2248
- caller: contentBlock.caller,
2434
+ ...(contentBlock.toolset && {
2435
+ toolsetName: contentBlock.toolset.name,
2436
+ }),
2437
+ ...(contentBlock.caller && {
2438
+ caller: contentBlock.caller,
2439
+ }),
2249
2440
  },
2250
2441
  },
2251
2442
  }),
@@ -2347,6 +2538,13 @@ export class AnthropicMessagesLanguageModel implements LanguageModelV3 {
2347
2538
  return;
2348
2539
  }
2349
2540
 
2541
+ // toolset member input is emitted as a single delta once
2542
+ // the block is complete (the member name is injected):
2543
+ if (contentBlock.toolset != null) {
2544
+ contentBlock.input += delta;
2545
+ return;
2546
+ }
2547
+
2350
2548
  // for the code execution 20250825, we need to add
2351
2549
  // the type to the delta and change the tool name.
2352
2550
  if (
@@ -2710,20 +2908,60 @@ export function getModelCapabilities(modelId: string): {
2710
2908
  supportsStructuredOutput: boolean;
2711
2909
  rejectsSamplingParameters: boolean;
2712
2910
  rejectsThinkingDisabledAboveHighEffort: boolean;
2911
+ /**
2912
+ * Thinking is always adaptive: `thinking.type` `disabled` and `enabled`
2913
+ * are rejected with a 400.
2914
+ */
2915
+ rejectsThinkingDisabled: boolean;
2916
+ /**
2917
+ * Forced tool use (`tool_choice` `any` or a named tool) is rejected with a 400.
2918
+ */
2919
+ rejectsForcedToolUse: boolean;
2713
2920
  isKnownModel: boolean;
2714
2921
  } {
2715
- if (modelId.includes('claude-opus-5')) {
2922
+ if (modelId.includes('claude-opus-5-5')) {
2716
2923
  return {
2717
2924
  maxOutputTokens: 128000,
2718
2925
  supportsStructuredOutput: true,
2719
2926
  rejectsSamplingParameters: true,
2720
2927
  rejectsThinkingDisabledAboveHighEffort: true,
2928
+ rejectsThinkingDisabled: true,
2929
+ rejectsForcedToolUse: true,
2930
+ isKnownModel: true,
2931
+ };
2932
+ } else if (modelId.includes('claude-opus-5')) {
2933
+ return {
2934
+ maxOutputTokens: 128000,
2935
+ supportsStructuredOutput: true,
2936
+ rejectsSamplingParameters: true,
2937
+ rejectsThinkingDisabledAboveHighEffort: true,
2938
+ rejectsThinkingDisabled: false,
2939
+ rejectsForcedToolUse: false,
2940
+ isKnownModel: true,
2941
+ };
2942
+ } else if (modelId.includes('claude-fable-5-1')) {
2943
+ return {
2944
+ maxOutputTokens: 128000,
2945
+ supportsStructuredOutput: true,
2946
+ rejectsSamplingParameters: true,
2947
+ rejectsThinkingDisabledAboveHighEffort: false,
2948
+ rejectsThinkingDisabled: true,
2949
+ rejectsForcedToolUse: true,
2950
+ isKnownModel: true,
2951
+ };
2952
+ } else if (modelId.includes('claude-fable-5')) {
2953
+ return {
2954
+ maxOutputTokens: 128000,
2955
+ supportsStructuredOutput: true,
2956
+ rejectsSamplingParameters: true,
2957
+ rejectsThinkingDisabledAboveHighEffort: false,
2958
+ rejectsThinkingDisabled: true,
2959
+ rejectsForcedToolUse: false,
2721
2960
  isKnownModel: true,
2722
2961
  };
2723
2962
  } else if (
2724
2963
  modelId.includes('claude-opus-4-8') ||
2725
2964
  modelId.includes('claude-opus-4-7') ||
2726
- modelId.includes('claude-fable-5') ||
2727
2965
  modelId.includes('claude-sonnet-5')
2728
2966
  ) {
2729
2967
  return {
@@ -2731,6 +2969,8 @@ export function getModelCapabilities(modelId: string): {
2731
2969
  supportsStructuredOutput: true,
2732
2970
  rejectsSamplingParameters: true,
2733
2971
  rejectsThinkingDisabledAboveHighEffort: false,
2972
+ rejectsThinkingDisabled: false,
2973
+ rejectsForcedToolUse: false,
2734
2974
  isKnownModel: true,
2735
2975
  };
2736
2976
  } else if (
@@ -2742,6 +2982,8 @@ export function getModelCapabilities(modelId: string): {
2742
2982
  supportsStructuredOutput: true,
2743
2983
  rejectsSamplingParameters: false,
2744
2984
  rejectsThinkingDisabledAboveHighEffort: false,
2985
+ rejectsThinkingDisabled: false,
2986
+ rejectsForcedToolUse: false,
2745
2987
  isKnownModel: true,
2746
2988
  };
2747
2989
  } else if (
@@ -2754,6 +2996,8 @@ export function getModelCapabilities(modelId: string): {
2754
2996
  supportsStructuredOutput: true,
2755
2997
  rejectsSamplingParameters: false,
2756
2998
  rejectsThinkingDisabledAboveHighEffort: false,
2999
+ rejectsThinkingDisabled: false,
3000
+ rejectsForcedToolUse: false,
2757
3001
  isKnownModel: true,
2758
3002
  };
2759
3003
  } else if (modelId.includes('claude-opus-4-1')) {
@@ -2762,6 +3006,8 @@ export function getModelCapabilities(modelId: string): {
2762
3006
  supportsStructuredOutput: true,
2763
3007
  rejectsSamplingParameters: false,
2764
3008
  rejectsThinkingDisabledAboveHighEffort: false,
3009
+ rejectsThinkingDisabled: false,
3010
+ rejectsForcedToolUse: false,
2765
3011
  isKnownModel: true,
2766
3012
  };
2767
3013
  } else if (modelId.includes('claude-sonnet-4-')) {
@@ -2770,6 +3016,8 @@ export function getModelCapabilities(modelId: string): {
2770
3016
  supportsStructuredOutput: false,
2771
3017
  rejectsSamplingParameters: false,
2772
3018
  rejectsThinkingDisabledAboveHighEffort: false,
3019
+ rejectsThinkingDisabled: false,
3020
+ rejectsForcedToolUse: false,
2773
3021
  isKnownModel: true,
2774
3022
  };
2775
3023
  } else if (modelId.includes('claude-opus-4-')) {
@@ -2778,6 +3026,8 @@ export function getModelCapabilities(modelId: string): {
2778
3026
  supportsStructuredOutput: false,
2779
3027
  rejectsSamplingParameters: false,
2780
3028
  rejectsThinkingDisabledAboveHighEffort: false,
3029
+ rejectsThinkingDisabled: false,
3030
+ rejectsForcedToolUse: false,
2781
3031
  isKnownModel: true,
2782
3032
  };
2783
3033
  } else if (modelId.includes('claude-3-haiku')) {
@@ -2786,6 +3036,8 @@ export function getModelCapabilities(modelId: string): {
2786
3036
  supportsStructuredOutput: false,
2787
3037
  rejectsSamplingParameters: false,
2788
3038
  rejectsThinkingDisabledAboveHighEffort: false,
3039
+ rejectsThinkingDisabled: false,
3040
+ rejectsForcedToolUse: false,
2789
3041
  isKnownModel: true,
2790
3042
  };
2791
3043
  } else if (
@@ -2796,6 +3048,8 @@ export function getModelCapabilities(modelId: string): {
2796
3048
  supportsStructuredOutput: false,
2797
3049
  rejectsSamplingParameters: false,
2798
3050
  rejectsThinkingDisabledAboveHighEffort: false,
3051
+ rejectsThinkingDisabled: false,
3052
+ rejectsForcedToolUse: false,
2799
3053
  isKnownModel: false,
2800
3054
  };
2801
3055
  } else if (modelId.includes('claude-')) {
@@ -2807,6 +3061,8 @@ export function getModelCapabilities(modelId: string): {
2807
3061
  supportsStructuredOutput: true,
2808
3062
  rejectsSamplingParameters: true,
2809
3063
  rejectsThinkingDisabledAboveHighEffort: true,
3064
+ rejectsThinkingDisabled: false,
3065
+ rejectsForcedToolUse: false,
2810
3066
  isKnownModel: false,
2811
3067
  };
2812
3068
  } else {
@@ -2817,6 +3073,8 @@ export function getModelCapabilities(modelId: string): {
2817
3073
  supportsStructuredOutput: false,
2818
3074
  rejectsSamplingParameters: false,
2819
3075
  rejectsThinkingDisabledAboveHighEffort: false,
3076
+ rejectsThinkingDisabled: false,
3077
+ rejectsForcedToolUse: false,
2820
3078
  isKnownModel: false,
2821
3079
  };
2822
3080
  }
@@ -2839,7 +3097,7 @@ function hasWebTool20260209WithoutCodeExecution(
2839
3097
  hasWebTool20260209 = true;
2840
3098
  continue;
2841
3099
  }
2842
- if (tool.name === 'code_execution') {
3100
+ if ('name' in tool && tool.name === 'code_execution') {
2843
3101
  hasCodeExecutionTool = true;
2844
3102
  break;
2845
3103
  }
@@ -20,6 +20,7 @@ export type AnthropicMessagesModelId =
20
20
  | 'claude-opus-4-7'
21
21
  | 'claude-opus-4-8'
22
22
  | 'claude-opus-5'
23
+ | 'claude-opus-5-5'
23
24
  | 'claude-fable-5'
24
25
  | 'claude-fable-5-1'
25
26
  | 'claude-sonnet-5'
@@ -77,7 +78,7 @@ export const anthropicSystemMessageProviderOptions = z.object({
77
78
  * Overrides the effort level for the turn following this
78
79
  * mid-conversation system message.
79
80
  *
80
- * Requires the `mid-conversation-effort-2026-08-01` beta,
81
+ * Requires the `mid-conversation-output-config-2026-07-01` beta,
81
82
  * which is added automatically.
82
83
  */
83
84
  effort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
@@ -142,6 +143,11 @@ export const anthropicLanguageModelOptions = z.object({
142
143
  *
143
144
  * When enabled, responses include thinking content blocks showing Claude's thinking process before the final answer.
144
145
  * Requires a minimum budget of 1,024 tokens and counts towards the `max_tokens` limit.
146
+ *
147
+ * Models that always use adaptive thinking (e.g. `claude-opus-5-5`,
148
+ * `claude-fable-5-1`) reject `enabled` and `disabled`. For those models the
149
+ * provider drops the unsupported setting, emits a warning, and sends an
150
+ * adaptive thinking request. Use `effort` to control how much they think.
145
151
  */
146
152
  thinking: z
147
153
  .union([
@@ -269,7 +275,11 @@ export const anthropicLanguageModelOptions = z.object({
269
275
  toolStreaming: z.boolean().optional(),
270
276
 
271
277
  /**
272
- * @default 'high'
278
+ * Controls how much effort the model spends on thinking, text responses,
279
+ * and tool calls. On models that always use adaptive thinking
280
+ * (e.g. `claude-opus-5-5`), effort is the main lever for latency and cost.
281
+ *
282
+ * The API default is `high` for most models and `medium` for `claude-opus-5-5`.
273
283
  */
274
284
  effort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
275
285
 
@@ -9,6 +9,7 @@ import type {
9
9
  } from './anthropic-messages-api';
10
10
  import { CacheControlValidator } from './get-cache-control';
11
11
  import { advisor_20260301ArgsSchema } from './tool/advisor_20260301';
12
+ import { computerToolset_20260801ArgsSchema } from './tool/computer-toolset_20260801';
12
13
  import { textEditor_20250728ArgsSchema } from './tool/text-editor_20250728';
13
14
  import { webSearch_20260209ArgsSchema } from './tool/web-search_20260209';
14
15
  import { webSearch_20250305ArgsSchema } from './tool/web-search_20250305';
@@ -32,6 +33,7 @@ export async function prepareTools({
32
33
  supportsStructuredOutput,
33
34
  supportsStrictTools,
34
35
  defaultEagerInputStreaming = false,
36
+ rejectsForcedToolUse = false,
35
37
  }: {
36
38
  tools: LanguageModelV3CallOptions['tools'];
37
39
  toolChoice: LanguageModelV3CallOptions['toolChoice'] | undefined;
@@ -53,6 +55,13 @@ export async function prepareTools({
53
55
  * it explicitly. Driven by the model-level `toolStreaming` option.
54
56
  */
55
57
  defaultEagerInputStreaming?: boolean;
58
+
59
+ /**
60
+ * Whether the model rejects forced tool use (`tool_choice` `any` or a
61
+ * named tool). When true, `required` and `tool` tool choices fall back to
62
+ * `auto` with a warning.
63
+ */
64
+ rejectsForcedToolUse?: boolean;
56
65
  }): Promise<{
57
66
  tools: Array<AnthropicTool> | undefined;
58
67
  toolChoice: AnthropicToolChoice | undefined;
@@ -186,6 +195,34 @@ export async function prepareTools({
186
195
  });
187
196
  break;
188
197
  }
198
+ case 'anthropic.computer_toolset_20260801': {
199
+ const args = await validateTypes({
200
+ value: tool.args,
201
+ schema: computerToolset_20260801ArgsSchema,
202
+ });
203
+ const configs =
204
+ args.configs != null
205
+ ? Object.fromEntries(
206
+ Object.entries(args.configs).map(([member, config]) => [
207
+ member,
208
+ {
209
+ ...(config?.enabled != null && {
210
+ enabled: config.enabled,
211
+ }),
212
+ ...(config?.deferLoading != null && {
213
+ defer_loading: config.deferLoading,
214
+ }),
215
+ },
216
+ ]),
217
+ )
218
+ : undefined;
219
+ anthropicTools.push({
220
+ type: 'computer_toolset_20260801',
221
+ ...(configs != null && { configs }),
222
+ cache_control: undefined,
223
+ });
224
+ break;
225
+ }
189
226
  case 'anthropic.computer_20241022': {
190
227
  betas.add('computer-use-2024-10-22');
191
228
  anthropicTools.push({
@@ -412,6 +449,24 @@ export async function prepareTools({
412
449
  betas,
413
450
  };
414
451
  case 'required':
452
+ if (rejectsForcedToolUse) {
453
+ toolWarnings.push({
454
+ type: 'unsupported',
455
+ feature: 'toolChoice',
456
+ details:
457
+ `toolChoice 'required' is not supported by this model because it rejects forced tool use. ` +
458
+ `Using 'auto' instead. Instruct the model to use a tool in the prompt and verify that a tool call was made.`,
459
+ });
460
+ return {
461
+ tools: anthropicTools,
462
+ toolChoice: {
463
+ type: 'auto',
464
+ disable_parallel_tool_use: disableParallelToolUse,
465
+ },
466
+ toolWarnings,
467
+ betas,
468
+ };
469
+ }
415
470
  return {
416
471
  tools: anthropicTools,
417
472
  toolChoice: {
@@ -425,6 +480,27 @@ export async function prepareTools({
425
480
  // Anthropic does not support 'none' tool choice, so we remove the tools:
426
481
  return { tools: undefined, toolChoice: undefined, toolWarnings, betas };
427
482
  case 'tool':
483
+ if (rejectsForcedToolUse) {
484
+ toolWarnings.push({
485
+ type: 'unsupported',
486
+ feature: 'toolChoice',
487
+ details:
488
+ `toolChoice 'tool' is not supported by this model because it rejects forced tool use. ` +
489
+ `Only the '${toolChoice.toolName}' tool is sent with 'auto' tool choice. ` +
490
+ `Instruct the model to use the tool in the prompt and verify that a tool call was made.`,
491
+ });
492
+ return {
493
+ tools: anthropicTools.filter(
494
+ tool => 'name' in tool && tool.name === toolChoice.toolName,
495
+ ),
496
+ toolChoice: {
497
+ type: 'auto',
498
+ disable_parallel_tool_use: disableParallelToolUse,
499
+ },
500
+ toolWarnings,
501
+ betas,
502
+ };
503
+ }
428
504
  return {
429
505
  tools: anthropicTools,
430
506
  toolChoice: {
@@ -7,6 +7,7 @@ import { codeExecution_20260120 } from './tool/code-execution_20260120';
7
7
  import { computer_20241022 } from './tool/computer_20241022';
8
8
  import { computer_20250124 } from './tool/computer_20250124';
9
9
  import { computer_20251124 } from './tool/computer_20251124';
10
+ import { computerToolset_20260801 } from './tool/computer-toolset_20260801';
10
11
  import { memory_20250818 } from './tool/memory_20250818';
11
12
  import { textEditor_20241022 } from './tool/text-editor_20241022';
12
13
  import { textEditor_20250124 } from './tool/text-editor_20250124';
@@ -143,6 +144,30 @@ export const anthropicTools = {
143
144
  */
144
145
  computer_20251124,
145
146
 
147
+ /**
148
+ * The computer toolset lets Claude control a computer through screenshots,
149
+ * mouse, and keyboard. It replaces the versioned computer tools
150
+ * (`computer_20251124` and earlier) on newer models.
151
+ *
152
+ * The API returns each computer action as its own `tool_use` block with
153
+ * `toolset_name: 'computer'`. The AI SDK maps every member call to this tool
154
+ * and exposes the member name as `action`, so `execute` receives the same
155
+ * input shape as the older computer tools. Claude may return several actions
156
+ * in one turn.
157
+ *
158
+ * Coordinates are always in the pixel space of the screenshots you return;
159
+ * there are no display size parameters. Zoom is enabled by default.
160
+ *
161
+ * Does not require a beta header.
162
+ *
163
+ * Supported models: Claude Opus 5.5 (required; older computer tool types
164
+ * are rejected), Opus 5, Sonnet 5, Fable 5, Fable 5.1, Opus 4.8.
165
+ *
166
+ * @param configs - Per-member settings keyed by member name, e.g.
167
+ * `{ zoom: { enabled: false } }`. Omitted members keep their defaults.
168
+ */
169
+ computerToolset_20260801,
170
+
146
171
  /**
147
172
  * The memory tool enables Claude to store and retrieve information across conversations through a memory file directory.
148
173
  * Claude can create, read, update, and delete files that persist between sessions,