@ai-sdk/anthropic 4.0.59 → 4.0.61

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.
@@ -195,6 +195,48 @@ function getAnthropicCallerMetadata(
195
195
  : { providerMetadata: { anthropic: { caller: callerInfo } } };
196
196
  }
197
197
 
198
+ /**
199
+ * Maps the custom tool names of toolset tools (e.g. the computer toolset) to
200
+ * the toolset name that the Anthropic API uses in `toolset_name`.
201
+ */
202
+ function getAnthropicToolsetNames(
203
+ tools: LanguageModelV4CallOptions['tools'],
204
+ ): Record<string, string> {
205
+ const toolsetNames: Record<string, string> = {};
206
+
207
+ for (const tool of tools ?? []) {
208
+ if (
209
+ tool.type === 'provider' &&
210
+ tool.id === 'anthropic.computer_toolset_20260801'
211
+ ) {
212
+ toolsetNames[tool.name] = 'computer';
213
+ }
214
+ }
215
+
216
+ return toolsetNames;
217
+ }
218
+
219
+ /**
220
+ * Toolset member calls (e.g. `left_click` from the computer toolset) are
221
+ * exposed as a single tool call on the toolset tool. The member name is
222
+ * injected as the `action` property, matching the input shape of the older
223
+ * computer tool versions.
224
+ */
225
+ function toToolsetMemberInput({
226
+ memberName,
227
+ input,
228
+ }: {
229
+ memberName: string;
230
+ input: unknown;
231
+ }): JSONObject {
232
+ return {
233
+ action: memberName,
234
+ ...(typeof input === 'object' && input !== null && !Array.isArray(input)
235
+ ? (input as JSONObject)
236
+ : {}),
237
+ };
238
+ }
239
+
198
240
  function getProviderOptionsName(provider: string): string {
199
241
  const dotIndex = provider.indexOf('.');
200
242
  return dotIndex === -1 ? provider : provider.substring(0, dotIndex);
@@ -362,6 +404,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
362
404
  rejectsSamplingParameters,
363
405
  supportsXhighEffort,
364
406
  rejectsThinkingDisabledAboveHighEffort,
407
+ rejectsThinkingDisabled,
408
+ rejectsForcedToolUse,
365
409
  isKnownModel,
366
410
  } = getModelCapabilities(modelId);
367
411
 
@@ -414,10 +458,30 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
414
458
 
415
459
  const structureOutputMode =
416
460
  anthropicOptions?.structuredOutputMode ?? 'auto';
417
- const useStructuredOutput =
461
+ let useStructuredOutput =
418
462
  structureOutputMode === 'outputFormat' ||
419
463
  (structureOutputMode === 'auto' && supportsStructuredOutput);
420
464
 
465
+ // The JSON response tool relies on forced tool use, which some models
466
+ // reject (e.g. Opus 5.5, Fable 5.1). Fall back to native structured
467
+ // outputs when the model supports them.
468
+ if (
469
+ !useStructuredOutput &&
470
+ rejectsForcedToolUse &&
471
+ supportsStructuredOutput &&
472
+ responseFormat?.type === 'json' &&
473
+ responseFormat.schema != null
474
+ ) {
475
+ warnings.push({
476
+ type: 'unsupported',
477
+ feature: 'providerOptions.anthropic.structuredOutputMode',
478
+ details:
479
+ `structuredOutputMode 'jsonTool' is not supported by ${modelId} because it rejects forced tool use. ` +
480
+ `Using 'outputFormat' instead.`,
481
+ });
482
+ useStructuredOutput = true;
483
+ }
484
+
421
485
  const jsonResponseTool: LanguageModelV4FunctionTool | undefined =
422
486
  responseFormat?.type === 'json' &&
423
487
  responseFormat.schema != null &&
@@ -456,6 +520,8 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
456
520
  'anthropic.code_execution_20260120': 'code_execution',
457
521
  'anthropic.computer_20241022': 'computer',
458
522
  'anthropic.computer_20250124': 'computer',
523
+ 'anthropic.computer_20251124': 'computer',
524
+ 'anthropic.computer_toolset_20260801': 'computer',
459
525
  'anthropic.text_editor_20241022': 'str_replace_editor',
460
526
  'anthropic.text_editor_20250124': 'str_replace_editor',
461
527
  'anthropic.text_editor_20250429': 'str_replace_based_edit_tool',
@@ -475,12 +541,15 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
475
541
  },
476
542
  });
477
543
 
544
+ const toolsetNames = getAnthropicToolsetNames(tools);
545
+
478
546
  const { prompt: messagesPrompt, betas } = await convertToAnthropicPrompt({
479
547
  prompt,
480
548
  sendReasoning: anthropicOptions?.sendReasoning ?? true,
481
549
  warnings,
482
550
  cacheControlValidator,
483
551
  toolNameMapping,
552
+ toolsetNames,
484
553
  });
485
554
 
486
555
  /*
@@ -490,8 +559,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
490
559
  if (isCustomReasoning(reasoning) && anthropicOptions?.effort == null) {
491
560
  const reasoningConfig = resolveAnthropicReasoningConfig({
492
561
  reasoning,
562
+ modelId,
493
563
  supportsAdaptiveThinking,
494
564
  supportsXhighEffort,
565
+ rejectsThinkingDisabled,
495
566
  maxOutputTokensForModel,
496
567
  warnings,
497
568
  });
@@ -508,6 +579,33 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
508
579
  }
509
580
  }
510
581
 
582
+ // Some models always run adaptive thinking and reject `disabled` and
583
+ // budget-based `enabled` thinking with a 400. Drop the unsupported
584
+ // setting and keep the request adaptive so it still succeeds.
585
+ if (rejectsThinkingDisabled && anthropicOptions?.thinking != null) {
586
+ const thinking = anthropicOptions.thinking;
587
+
588
+ if (thinking.type === 'disabled') {
589
+ warnings.push({
590
+ type: 'unsupported',
591
+ feature: 'providerOptions.anthropic.thinking',
592
+ details:
593
+ `thinking cannot be disabled for ${modelId}; it always uses adaptive thinking. ` +
594
+ `The thinking setting has been removed. Lower 'effort' to reduce thinking.`,
595
+ });
596
+ anthropicOptions.thinking = undefined;
597
+ } else if (thinking.type === 'enabled') {
598
+ warnings.push({
599
+ type: 'unsupported',
600
+ feature: 'providerOptions.anthropic.thinking',
601
+ details:
602
+ `budget-based thinking is not supported by ${modelId}; it always uses adaptive thinking. ` +
603
+ `Using adaptive thinking instead. Use 'effort' to control how much the model thinks.`,
604
+ });
605
+ anthropicOptions.thinking = { type: 'adaptive' };
606
+ }
607
+ }
608
+
511
609
  // Newer models only allow disabling thinking at effort levels up to and
512
610
  // including `high`; at `xhigh` and `max` the API returns a 400. Lower
513
611
  // the effort to `high` to preserve the explicit request to run without
@@ -903,6 +1001,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
903
1001
  supportsStructuredOutput: false,
904
1002
  supportsStrictTools,
905
1003
  defaultEagerInputStreaming,
1004
+ rejectsForcedToolUse,
906
1005
  }
907
1006
  : {
908
1007
  tools: tools ?? [],
@@ -912,6 +1011,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
912
1011
  supportsStructuredOutput,
913
1012
  supportsStrictTools,
914
1013
  defaultEagerInputStreaming,
1014
+ rejectsForcedToolUse,
915
1015
  },
916
1016
  );
917
1017
 
@@ -1161,6 +1261,10 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1161
1261
  break;
1162
1262
  }
1163
1263
  case 'compaction': {
1264
+ if (!part.content) {
1265
+ break;
1266
+ }
1267
+
1164
1268
  content.push({
1165
1269
  type: 'text',
1166
1270
  text: part.content,
@@ -1184,6 +1288,27 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1184
1288
  type: 'text',
1185
1289
  text: JSON.stringify(part.input),
1186
1290
  });
1291
+ } else if (part.toolset_name != null) {
1292
+ // toolset member calls (e.g. the computer toolset) are mapped to
1293
+ // the toolset tool with the member name as the `action`:
1294
+ content.push({
1295
+ type: 'tool-call',
1296
+ toolCallId: part.id,
1297
+ toolName: toolNameMapping.toCustomToolName(part.toolset_name),
1298
+ input: JSON.stringify(
1299
+ toToolsetMemberInput({
1300
+ memberName: part.name,
1301
+ input: part.input,
1302
+ }),
1303
+ ),
1304
+ providerMetadata: {
1305
+ anthropic: {
1306
+ toolsetName: part.toolset_name,
1307
+ ...getAnthropicCallerMetadata(part.caller).providerMetadata
1308
+ ?.anthropic,
1309
+ },
1310
+ },
1311
+ });
1187
1312
  } else {
1188
1313
  content.push({
1189
1314
  type: 'tool-call',
@@ -1696,6 +1821,12 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1696
1821
  firstDelta: boolean;
1697
1822
  providerToolName?: string;
1698
1823
  providerToolInputType?: string;
1824
+ /**
1825
+ * Set for toolset member calls (e.g. the computer toolset). The raw
1826
+ * member input is accumulated and emitted as a single delta with the
1827
+ * member name injected as `action` when the block completes.
1828
+ */
1829
+ toolset?: { name: string; memberName: string };
1699
1830
  caller?: {
1700
1831
  type:
1701
1832
  | 'code_execution_20250825'
@@ -1862,6 +1993,34 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
1862
1993
  type: 'text-start',
1863
1994
  id: String(value.index),
1864
1995
  });
1996
+ } else if (part.toolset_name != null) {
1997
+ const callerInfo = getAnthropicCallerInfo(part.caller);
1998
+ const customToolName = toolNameMapping.toCustomToolName(
1999
+ part.toolset_name,
2000
+ );
2001
+ const hasNonEmptyInput =
2002
+ part.input && Object.keys(part.input).length > 0;
2003
+
2004
+ // toolset member input is accumulated and emitted when the
2005
+ // block completes (see content_block_stop):
2006
+ contentBlocks[value.index] = {
2007
+ type: 'tool-call',
2008
+ toolCallId: part.id,
2009
+ toolName: customToolName,
2010
+ input: hasNonEmptyInput ? JSON.stringify(part.input) : '',
2011
+ firstDelta: true,
2012
+ toolset: {
2013
+ name: part.toolset_name,
2014
+ memberName: part.name,
2015
+ },
2016
+ ...(callerInfo && { caller: callerInfo }),
2017
+ };
2018
+
2019
+ controller.enqueue({
2020
+ type: 'tool-input-start',
2021
+ id: part.id,
2022
+ toolName: customToolName,
2023
+ });
1865
2024
  } else {
1866
2025
  const callerInfo = getAnthropicCallerInfo(part.caller);
1867
2026
 
@@ -2352,6 +2511,37 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2352
2511
  usesJsonResponseTool && contentBlock.toolName === 'json';
2353
2512
 
2354
2513
  if (!isJsonResponseTool) {
2514
+ // toolset member calls: emit the accumulated input with
2515
+ // the member name injected as `action` in one delta.
2516
+ if (contentBlock.toolset != null) {
2517
+ let memberInput: unknown = {};
2518
+ try {
2519
+ memberInput =
2520
+ contentBlock.input === ''
2521
+ ? {}
2522
+ : secureJsonParse(contentBlock.input);
2523
+ } catch {
2524
+ // ignore parse errors, fall back to the raw input
2525
+ memberInput = undefined;
2526
+ }
2527
+
2528
+ contentBlock.input =
2529
+ memberInput === undefined
2530
+ ? contentBlock.input
2531
+ : JSON.stringify(
2532
+ toToolsetMemberInput({
2533
+ memberName: contentBlock.toolset.memberName,
2534
+ input: memberInput,
2535
+ }),
2536
+ );
2537
+
2538
+ controller.enqueue({
2539
+ type: 'tool-input-delta',
2540
+ id: contentBlock.toolCallId,
2541
+ delta: contentBlock.input,
2542
+ });
2543
+ }
2544
+
2355
2545
  controller.enqueue({
2356
2546
  type: 'tool-input-end',
2357
2547
  id: contentBlock.toolCallId,
@@ -2394,10 +2584,15 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2394
2584
  contentBlock.providerToolName === 'code_execution'
2395
2585
  ? { dynamic: true }
2396
2586
  : {}),
2397
- ...(contentBlock.caller && {
2587
+ ...((contentBlock.caller || contentBlock.toolset) && {
2398
2588
  providerMetadata: {
2399
2589
  anthropic: {
2400
- caller: contentBlock.caller,
2590
+ ...(contentBlock.toolset && {
2591
+ toolsetName: contentBlock.toolset.name,
2592
+ }),
2593
+ ...(contentBlock.caller && {
2594
+ caller: contentBlock.caller,
2595
+ }),
2401
2596
  },
2402
2597
  },
2403
2598
  }),
@@ -2499,6 +2694,13 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
2499
2694
  return;
2500
2695
  }
2501
2696
 
2697
+ // toolset member input is emitted as a single delta once
2698
+ // the block is complete (the member name is injected):
2699
+ if (contentBlock.toolset != null) {
2700
+ contentBlock.input += delta;
2701
+ return;
2702
+ }
2703
+
2502
2704
  // for the code execution 20250825, we need to add
2503
2705
  // the type to the delta and change the tool name.
2504
2706
  if (
@@ -2872,9 +3074,18 @@ export function getModelCapabilities(modelId: string): {
2872
3074
  rejectsSamplingParameters: boolean;
2873
3075
  supportsXhighEffort: boolean;
2874
3076
  rejectsThinkingDisabledAboveHighEffort: boolean;
3077
+ /**
3078
+ * Thinking is always adaptive: `thinking.type` `disabled` and `enabled`
3079
+ * are rejected with a 400.
3080
+ */
3081
+ rejectsThinkingDisabled: boolean;
3082
+ /**
3083
+ * Forced tool use (`tool_choice` `any` or a named tool) is rejected with a 400.
3084
+ */
3085
+ rejectsForcedToolUse: boolean;
2875
3086
  isKnownModel: boolean;
2876
3087
  } {
2877
- if (modelId.includes('claude-opus-5')) {
3088
+ if (modelId.includes('claude-opus-5-5')) {
2878
3089
  return {
2879
3090
  maxOutputTokens: 128000,
2880
3091
  supportsStructuredOutput: true,
@@ -2882,12 +3093,49 @@ export function getModelCapabilities(modelId: string): {
2882
3093
  rejectsSamplingParameters: true,
2883
3094
  supportsXhighEffort: true,
2884
3095
  rejectsThinkingDisabledAboveHighEffort: true,
3096
+ rejectsThinkingDisabled: true,
3097
+ rejectsForcedToolUse: true,
3098
+ isKnownModel: true,
3099
+ };
3100
+ } else if (modelId.includes('claude-opus-5')) {
3101
+ return {
3102
+ maxOutputTokens: 128000,
3103
+ supportsStructuredOutput: true,
3104
+ supportsAdaptiveThinking: true,
3105
+ rejectsSamplingParameters: true,
3106
+ supportsXhighEffort: true,
3107
+ rejectsThinkingDisabledAboveHighEffort: true,
3108
+ rejectsThinkingDisabled: false,
3109
+ rejectsForcedToolUse: false,
3110
+ isKnownModel: true,
3111
+ };
3112
+ } else if (modelId.includes('claude-fable-5-1')) {
3113
+ return {
3114
+ maxOutputTokens: 128000,
3115
+ supportsStructuredOutput: true,
3116
+ supportsAdaptiveThinking: true,
3117
+ rejectsSamplingParameters: true,
3118
+ supportsXhighEffort: true,
3119
+ rejectsThinkingDisabledAboveHighEffort: false,
3120
+ rejectsThinkingDisabled: true,
3121
+ rejectsForcedToolUse: true,
3122
+ isKnownModel: true,
3123
+ };
3124
+ } else if (modelId.includes('claude-fable-5')) {
3125
+ return {
3126
+ maxOutputTokens: 128000,
3127
+ supportsStructuredOutput: true,
3128
+ supportsAdaptiveThinking: true,
3129
+ rejectsSamplingParameters: true,
3130
+ supportsXhighEffort: true,
3131
+ rejectsThinkingDisabledAboveHighEffort: false,
3132
+ rejectsThinkingDisabled: true,
3133
+ rejectsForcedToolUse: false,
2885
3134
  isKnownModel: true,
2886
3135
  };
2887
3136
  } else if (
2888
3137
  modelId.includes('claude-opus-4-8') ||
2889
3138
  modelId.includes('claude-opus-4-7') ||
2890
- modelId.includes('claude-fable-5') ||
2891
3139
  modelId.includes('claude-sonnet-5')
2892
3140
  ) {
2893
3141
  return {
@@ -2897,6 +3145,8 @@ export function getModelCapabilities(modelId: string): {
2897
3145
  rejectsSamplingParameters: true,
2898
3146
  supportsXhighEffort: true,
2899
3147
  rejectsThinkingDisabledAboveHighEffort: false,
3148
+ rejectsThinkingDisabled: false,
3149
+ rejectsForcedToolUse: false,
2900
3150
  isKnownModel: true,
2901
3151
  };
2902
3152
  } else if (
@@ -2910,6 +3160,8 @@ export function getModelCapabilities(modelId: string): {
2910
3160
  rejectsSamplingParameters: false,
2911
3161
  supportsXhighEffort: false,
2912
3162
  rejectsThinkingDisabledAboveHighEffort: false,
3163
+ rejectsThinkingDisabled: false,
3164
+ rejectsForcedToolUse: false,
2913
3165
  isKnownModel: true,
2914
3166
  };
2915
3167
  } else if (
@@ -2924,6 +3176,8 @@ export function getModelCapabilities(modelId: string): {
2924
3176
  rejectsSamplingParameters: false,
2925
3177
  supportsXhighEffort: false,
2926
3178
  rejectsThinkingDisabledAboveHighEffort: false,
3179
+ rejectsThinkingDisabled: false,
3180
+ rejectsForcedToolUse: false,
2927
3181
  isKnownModel: true,
2928
3182
  };
2929
3183
  } else if (modelId.includes('claude-opus-4-1')) {
@@ -2934,6 +3188,8 @@ export function getModelCapabilities(modelId: string): {
2934
3188
  rejectsSamplingParameters: false,
2935
3189
  supportsXhighEffort: false,
2936
3190
  rejectsThinkingDisabledAboveHighEffort: false,
3191
+ rejectsThinkingDisabled: false,
3192
+ rejectsForcedToolUse: false,
2937
3193
  isKnownModel: true,
2938
3194
  };
2939
3195
  } else if (/claude-sonnet-4(?:-|@)/.test(modelId)) {
@@ -2944,6 +3200,8 @@ export function getModelCapabilities(modelId: string): {
2944
3200
  rejectsSamplingParameters: false,
2945
3201
  supportsXhighEffort: false,
2946
3202
  rejectsThinkingDisabledAboveHighEffort: false,
3203
+ rejectsThinkingDisabled: false,
3204
+ rejectsForcedToolUse: false,
2947
3205
  isKnownModel: true,
2948
3206
  };
2949
3207
  } else if (/claude-opus-4(?:-|@)/.test(modelId)) {
@@ -2954,6 +3212,8 @@ export function getModelCapabilities(modelId: string): {
2954
3212
  rejectsSamplingParameters: false,
2955
3213
  supportsXhighEffort: false,
2956
3214
  rejectsThinkingDisabledAboveHighEffort: false,
3215
+ rejectsThinkingDisabled: false,
3216
+ rejectsForcedToolUse: false,
2957
3217
  isKnownModel: true,
2958
3218
  };
2959
3219
  } else if (modelId.includes('claude-3-haiku')) {
@@ -2964,6 +3224,8 @@ export function getModelCapabilities(modelId: string): {
2964
3224
  rejectsSamplingParameters: false,
2965
3225
  supportsXhighEffort: false,
2966
3226
  rejectsThinkingDisabledAboveHighEffort: false,
3227
+ rejectsThinkingDisabled: false,
3228
+ rejectsForcedToolUse: false,
2967
3229
  isKnownModel: true,
2968
3230
  };
2969
3231
  } else if (
@@ -2976,6 +3238,8 @@ export function getModelCapabilities(modelId: string): {
2976
3238
  rejectsSamplingParameters: false,
2977
3239
  supportsXhighEffort: false,
2978
3240
  rejectsThinkingDisabledAboveHighEffort: false,
3241
+ rejectsThinkingDisabled: false,
3242
+ rejectsForcedToolUse: false,
2979
3243
  isKnownModel: false,
2980
3244
  };
2981
3245
  } else if (modelId.includes('claude-')) {
@@ -2989,6 +3253,8 @@ export function getModelCapabilities(modelId: string): {
2989
3253
  rejectsSamplingParameters: true,
2990
3254
  supportsXhighEffort: true,
2991
3255
  rejectsThinkingDisabledAboveHighEffort: true,
3256
+ rejectsThinkingDisabled: false,
3257
+ rejectsForcedToolUse: false,
2992
3258
  isKnownModel: false,
2993
3259
  };
2994
3260
  } else {
@@ -3001,6 +3267,8 @@ export function getModelCapabilities(modelId: string): {
3001
3267
  rejectsSamplingParameters: false,
3002
3268
  supportsXhighEffort: false,
3003
3269
  rejectsThinkingDisabledAboveHighEffort: false,
3270
+ rejectsThinkingDisabled: false,
3271
+ rejectsForcedToolUse: false,
3004
3272
  isKnownModel: false,
3005
3273
  };
3006
3274
  }
@@ -3040,14 +3308,18 @@ export function hasDynamicFilteringWebToolWithoutCodeExecution(
3040
3308
 
3041
3309
  function resolveAnthropicReasoningConfig({
3042
3310
  reasoning,
3311
+ modelId,
3043
3312
  supportsAdaptiveThinking,
3044
3313
  supportsXhighEffort,
3314
+ rejectsThinkingDisabled,
3045
3315
  maxOutputTokensForModel,
3046
3316
  warnings,
3047
3317
  }: {
3048
3318
  reasoning: LanguageModelV4CallOptions['reasoning'];
3319
+ modelId: string;
3049
3320
  supportsAdaptiveThinking: boolean;
3050
3321
  supportsXhighEffort: boolean;
3322
+ rejectsThinkingDisabled: boolean;
3051
3323
  maxOutputTokensForModel: number;
3052
3324
  warnings: SharedV4Warning[];
3053
3325
  }): Pick<AnthropicLanguageModelOptions, 'thinking' | 'effort'> | undefined {
@@ -3056,6 +3328,20 @@ function resolveAnthropicReasoningConfig({
3056
3328
  }
3057
3329
 
3058
3330
  if (reasoning === 'none') {
3331
+ // Models that always run adaptive thinking cannot disable it. Use low
3332
+ // effort to keep thinking short instead of sending a request that the
3333
+ // API would reject.
3334
+ if (rejectsThinkingDisabled) {
3335
+ warnings.push({
3336
+ type: 'compatibility',
3337
+ feature: 'reasoning',
3338
+ details:
3339
+ `reasoning 'none' is not supported by ${modelId}; it always uses adaptive thinking. ` +
3340
+ `Using effort 'low' to minimize thinking instead.`,
3341
+ });
3342
+ return { effort: 'low' };
3343
+ }
3344
+
3059
3345
  return { thinking: { type: 'disabled' } };
3060
3346
  }
3061
3347
 
@@ -6,6 +6,7 @@ import {
6
6
  import type { AnthropicTool, AnthropicToolChoice } from './anthropic-api';
7
7
  import { CacheControlValidator } from './get-cache-control';
8
8
  import { advisor_20260301ArgsSchema } from './tool/advisor_20260301';
9
+ import { computerToolset_20260801ArgsSchema } from './tool/computer-toolset_20260801';
9
10
  import { textEditor_20250728ArgsSchema } from './tool/text-editor_20250728';
10
11
  import { webSearch_20260318ArgsSchema } from './tool/web-search_20260318';
11
12
  import { webSearch_20260209ArgsSchema } from './tool/web-search_20260209';
@@ -31,6 +32,7 @@ export async function prepareTools({
31
32
  supportsStructuredOutput,
32
33
  supportsStrictTools,
33
34
  defaultEagerInputStreaming = false,
35
+ rejectsForcedToolUse = false,
34
36
  }: {
35
37
  tools: LanguageModelV4CallOptions['tools'];
36
38
  toolChoice: LanguageModelV4CallOptions['toolChoice'] | undefined;
@@ -52,6 +54,13 @@ export async function prepareTools({
52
54
  * it explicitly. Driven by the model-level `toolStreaming` option.
53
55
  */
54
56
  defaultEagerInputStreaming?: boolean;
57
+
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.
62
+ */
63
+ rejectsForcedToolUse?: boolean;
55
64
  }): Promise<{
56
65
  tools: Array<AnthropicTool> | undefined;
57
66
  toolChoice: AnthropicToolChoice | undefined;
@@ -185,6 +194,34 @@ export async function prepareTools({
185
194
  });
186
195
  break;
187
196
  }
197
+ case 'anthropic.computer_toolset_20260801': {
198
+ const args = await validateTypes({
199
+ value: tool.args,
200
+ schema: computerToolset_20260801ArgsSchema,
201
+ });
202
+ const configs =
203
+ args.configs != null
204
+ ? Object.fromEntries(
205
+ Object.entries(args.configs).map(([member, config]) => [
206
+ member,
207
+ {
208
+ ...(config?.enabled != null && {
209
+ enabled: config.enabled,
210
+ }),
211
+ ...(config?.deferLoading != null && {
212
+ defer_loading: config.deferLoading,
213
+ }),
214
+ },
215
+ ]),
216
+ )
217
+ : undefined;
218
+ anthropicTools.push({
219
+ type: 'computer_toolset_20260801',
220
+ ...(configs != null && { configs }),
221
+ cache_control: undefined,
222
+ });
223
+ break;
224
+ }
188
225
  case 'anthropic.computer_20241022': {
189
226
  betas.add('computer-use-2024-10-22');
190
227
  anthropicTools.push({
@@ -450,6 +487,24 @@ export async function prepareTools({
450
487
  betas,
451
488
  };
452
489
  case 'required':
490
+ if (rejectsForcedToolUse) {
491
+ toolWarnings.push({
492
+ type: 'unsupported',
493
+ feature: 'toolChoice',
494
+ details:
495
+ `toolChoice 'required' is not supported by this model because it rejects forced tool use. ` +
496
+ `Using 'auto' instead. Instruct the model to use a tool in the prompt and verify that a tool call was made.`,
497
+ });
498
+ return {
499
+ tools: anthropicTools,
500
+ toolChoice: {
501
+ type: 'auto',
502
+ disable_parallel_tool_use: disableParallelToolUse,
503
+ },
504
+ toolWarnings,
505
+ betas,
506
+ };
507
+ }
453
508
  return {
454
509
  tools: anthropicTools,
455
510
  toolChoice: {
@@ -463,6 +518,27 @@ export async function prepareTools({
463
518
  // Anthropic does not support 'none' tool choice, so we remove the tools:
464
519
  return { tools: undefined, toolChoice: undefined, toolWarnings, betas };
465
520
  case 'tool':
521
+ if (rejectsForcedToolUse) {
522
+ toolWarnings.push({
523
+ type: 'unsupported',
524
+ feature: 'toolChoice',
525
+ details:
526
+ `toolChoice 'tool' is not supported by this model because it rejects forced tool use. ` +
527
+ `Only the '${toolChoice.toolName}' tool is sent with 'auto' tool choice. ` +
528
+ `Instruct the model to use the tool in the prompt and verify that a tool call was made.`,
529
+ });
530
+ return {
531
+ tools: anthropicTools.filter(
532
+ tool => 'name' in tool && tool.name === toolChoice.toolName,
533
+ ),
534
+ toolChoice: {
535
+ type: 'auto',
536
+ disable_parallel_tool_use: disableParallelToolUse,
537
+ },
538
+ toolWarnings,
539
+ betas,
540
+ };
541
+ }
466
542
  return {
467
543
  tools: anthropicTools,
468
544
  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';
@@ -147,6 +148,30 @@ export const anthropicTools = {
147
148
  */
148
149
  computer_20251124,
149
150
 
151
+ /**
152
+ * The computer toolset lets Claude control a computer through screenshots,
153
+ * mouse, and keyboard. It replaces the versioned computer tools
154
+ * (`computer_20251124` and earlier) on newer models.
155
+ *
156
+ * The API returns each computer action as its own `tool_use` block with
157
+ * `toolset_name: 'computer'`. The AI SDK maps every member call to this tool
158
+ * and exposes the member name as `action`, so `execute` receives the same
159
+ * input shape as the older computer tools. Claude may return several actions
160
+ * in one turn.
161
+ *
162
+ * Coordinates are always in the pixel space of the screenshots you return;
163
+ * there are no display size parameters. Zoom is enabled by default.
164
+ *
165
+ * Does not require a beta header.
166
+ *
167
+ * Supported models: Claude Opus 5.5 (required; older computer tool types
168
+ * are rejected), Opus 5, Sonnet 5, Fable 5, Fable 5.1, Opus 4.8.
169
+ *
170
+ * @param configs - Per-member settings keyed by member name, e.g.
171
+ * `{ zoom: { enabled: false } }`. Omitted members keep their defaults.
172
+ */
173
+ computerToolset_20260801,
174
+
150
175
  /**
151
176
  * The memory tool enables Claude to store and retrieve information across conversations through a memory file directory.
152
177
  * Claude can create, read, update, and delete files that persist between sessions,