ai 7.0.41 → 7.0.43

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.41",
3
+ "version": "7.0.43",
4
4
  "type": "module",
5
5
  "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
6
6
  "license": "Apache-2.0",
@@ -42,9 +42,9 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.31",
45
+ "@ai-sdk/gateway": "4.0.33",
46
46
  "@ai-sdk/provider": "4.0.4",
47
- "@ai-sdk/provider-utils": "5.0.14"
47
+ "@ai-sdk/provider-utils": "5.0.16"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@edge-runtime/vm": "^5.0.0",
@@ -55,7 +55,7 @@
55
55
  "tsx": "^4.22.0",
56
56
  "typescript": "5.8.3",
57
57
  "zod": "3.25.76",
58
- "@ai-sdk/test-server": "2.0.0",
58
+ "@ai-sdk/test-server": "2.0.1",
59
59
  "@vercel/ai-tsconfig": "0.0.0"
60
60
  },
61
61
  "peerDependencies": {
@@ -40,7 +40,9 @@ export interface GenerateTextResult<
40
40
  readonly content: Array<ContentPart<TOOLS>>;
41
41
 
42
42
  /**
43
- * The text that was generated in the final step.
43
+ * The concatenation of all text parts generated in the final step.
44
+ * It is an empty string if the final step contains no text parts.
45
+ * Inspect `finalStep.content` to distinguish that case.
44
46
  */
45
47
  readonly text: string;
46
48
 
@@ -84,6 +84,7 @@ import { text, type Output } from './output';
84
84
  import type { InferCompleteOutput } from './output-utils';
85
85
  import { parseToolCall } from './parse-tool-call';
86
86
  import type { PrepareStepFunction } from './prepare-step';
87
+ import { prepareStepCallSettings } from './prepare-step-call-settings';
87
88
  import { convertToReasoningOutputs } from './reasoning-output';
88
89
  import { resolveToolApproval } from './resolve-tool-approval';
89
90
  import type { ResponseMessage } from './response-message';
@@ -103,6 +104,11 @@ import { toResponseMessages } from './to-response-messages';
103
104
  import type { ToolApprovalConfiguration } from './tool-approval-configuration';
104
105
  import type { ToolApprovalRequestOutput } from './tool-approval-request-output';
105
106
  import type { ToolApprovalResponseOutput } from './tool-approval-response-output';
107
+ import {
108
+ prepareToolsForToolCallers,
109
+ resolveToolCallerConfiguration,
110
+ type Experimental_ToolCallers,
111
+ } from './tool-caller-configuration';
106
112
  import type { TypedToolCall } from './tool-call';
107
113
  import type { ToolCallRepairFunction } from './tool-call-repair-function';
108
114
  import type { TypedToolError } from './tool-error';
@@ -241,6 +247,7 @@ export async function generateText<
241
247
  experimental_sandbox: sandbox,
242
248
  output,
243
249
  toolApproval,
250
+ experimental_toolCallers,
244
251
  experimental_toolApprovalSecret,
245
252
  experimental_telemetry,
246
253
  telemetry = experimental_telemetry,
@@ -357,6 +364,11 @@ export async function generateText<
357
364
  */
358
365
  toolApproval?: ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT>;
359
366
 
367
+ /**
368
+ * Configures which caller tools may invoke each tool.
369
+ */
370
+ experimental_toolCallers?: Experimental_ToolCallers<NoInfer<TOOLS>>;
371
+
360
372
  /**
361
373
  * Secret for HMAC-signing tool approval requests. When set, the server
362
374
  * signs each approval request at issuance and verifies the signature when
@@ -559,6 +571,10 @@ export async function generateText<
559
571
  };
560
572
 
561
573
  const model = resolveLanguageModel(modelArg);
574
+ const resolvedToolCallers = resolveToolCallerConfiguration({
575
+ tools,
576
+ toolCallers: experimental_toolCallers,
577
+ });
562
578
  const stopConditions = asArray(stopWhen);
563
579
  const resolvedOnStart = onStart ?? experimental_onStart;
564
580
  const resolvedOnStepStart = onStepStart ?? experimental_onStepStart;
@@ -862,10 +878,20 @@ export async function generateText<
862
878
  tools,
863
879
  activeTools: prepareStepResult?.activeTools ?? activeTools,
864
880
  });
881
+ const {
882
+ executionTools: stepExecutionTools,
883
+ modelTools: stepModelTools,
884
+ } = prepareToolsForToolCallers({
885
+ tools: stepActiveTools,
886
+ toolCallers: resolvedToolCallers,
887
+ });
865
888
  const stepToolOrder = prepareStepResult?.toolOrder ?? toolOrder;
866
889
 
867
890
  const stepTools = await prepareTools({
868
- tools: stepActiveTools,
891
+ tools: stepModelTools as ActiveToolSubset<
892
+ TOOLS,
893
+ ActiveTools<NoInfer<TOOLS>>
894
+ >,
869
895
  toolOrder: stepToolOrder as ToolOrder<
870
896
  ActiveToolSubset<TOOLS, ActiveTools<NoInfer<TOOLS>>>
871
897
  >,
@@ -888,6 +914,11 @@ export async function generateText<
888
914
  prepareStepResult?.providerOptions,
889
915
  );
890
916
 
917
+ const stepCallSettings = prepareStepCallSettings({
918
+ callSettings,
919
+ stepSettings: prepareStepResult,
920
+ });
921
+
891
922
  await notify({
892
923
  event: {
893
924
  callId,
@@ -921,7 +952,7 @@ export async function generateText<
921
952
  instructions: stepInstructions,
922
953
  messages: stepMessages,
923
954
  tools: stepTools,
924
- ...callSettings,
955
+ ...stepCallSettings,
925
956
  };
926
957
  const languageModelCallStartEvent = {
927
958
  callId,
@@ -951,7 +982,7 @@ export async function generateText<
951
982
  ...languageModelCallStartEvent,
952
983
  execute: async () =>
953
984
  await stepModel.doGenerate({
954
- ...callSettings,
985
+ ...stepCallSettings,
955
986
  tools: stepTools,
956
987
  toolChoice: stepToolChoice,
957
988
  responseFormat: await output?.responseFormat,
@@ -988,7 +1019,7 @@ export async function generateText<
988
1019
  .map(toolCall =>
989
1020
  parseToolCall({
990
1021
  toolCall,
991
- tools,
1022
+ tools: stepExecutionTools as TOOLS,
992
1023
  repairToolCall,
993
1024
  refineToolInput,
994
1025
  instructions: stepInstructions,
@@ -1056,7 +1087,7 @@ export async function generateText<
1056
1087
  continue; // ignore invalid tool calls
1057
1088
  }
1058
1089
 
1059
- const tool = getOwn(tools, toolCall.toolName);
1090
+ const tool = getOwn(stepExecutionTools, toolCall.toolName);
1060
1091
 
1061
1092
  if (tool == null) {
1062
1093
  // ignore tool calls for tools that are not available,
@@ -1084,7 +1115,7 @@ export async function generateText<
1084
1115
  }
1085
1116
 
1086
1117
  const toolApprovalStatus = await resolveToolApproval({
1087
- tools,
1118
+ tools: stepExecutionTools as TOOLS,
1088
1119
  toolApproval,
1089
1120
  toolCall,
1090
1121
  messages: stepMessages,
@@ -1193,14 +1224,14 @@ export async function generateText<
1193
1224
  );
1194
1225
  const toolExecutionMs: Record<string, number> = {};
1195
1226
 
1196
- if (tools != null) {
1227
+ if (stepExecutionTools != null) {
1197
1228
  const toolExecutionResults = await executeTools({
1198
1229
  toolCalls: clientToolCalls.filter(
1199
1230
  toolCall =>
1200
1231
  !toolCall.invalid &&
1201
1232
  !blockedToolCallIds.has(toolCall.toolCallId),
1202
1233
  ),
1203
- tools,
1234
+ tools: stepExecutionTools as TOOLS,
1204
1235
  callId,
1205
1236
  messages: stepMessages,
1206
1237
  abortSignal: mergedAbortSignal,
@@ -1262,7 +1293,7 @@ export async function generateText<
1262
1293
  // the client tool's result is sent back.
1263
1294
  for (const toolCall of stepToolCalls) {
1264
1295
  if (!toolCall.providerExecuted) continue;
1265
- const tool = getOwn(tools, toolCall.toolName);
1296
+ const tool = getOwn(stepExecutionTools, toolCall.toolName);
1266
1297
  if (tool?.type === 'provider' && tool.supportsDeferredResults) {
1267
1298
  // Check if this tool call already has a result in the current response
1268
1299
  const hasResultInResponse = currentModelResponse.content.some(
@@ -80,6 +80,10 @@ export type {
80
80
  ToolApprovalConfiguration,
81
81
  ToolApprovalStatus,
82
82
  } from './tool-approval-configuration';
83
+ export type {
84
+ Experimental_ToolCallerReference,
85
+ Experimental_ToolCallers,
86
+ } from './tool-caller-configuration';
83
87
  export { detectToolDrift, fingerprintTools } from './tool-fingerprint';
84
88
  export type { ToolApprovalRequestOutput } from './tool-approval-request-output';
85
89
  export type { ToolApprovalResponseOutput } from './tool-approval-response-output';
@@ -0,0 +1,31 @@
1
+ import type { LanguageModelCallOptions } from '../prompt/language-model-call-options';
2
+ import { prepareLanguageModelCallOptions } from '../prompt/prepare-language-model-call-options';
3
+
4
+ /**
5
+ * Resolves model call settings for a single step.
6
+ *
7
+ * Undefined step settings intentionally fall back to the outer call settings,
8
+ * while defined falsy values such as `temperature: 0` and `seed: 0` are kept.
9
+ */
10
+ export function prepareStepCallSettings({
11
+ callSettings,
12
+ stepSettings,
13
+ }: {
14
+ callSettings: LanguageModelCallOptions;
15
+ stepSettings: LanguageModelCallOptions | undefined;
16
+ }): LanguageModelCallOptions {
17
+ return prepareLanguageModelCallOptions({
18
+ maxOutputTokens:
19
+ stepSettings?.maxOutputTokens ?? callSettings.maxOutputTokens,
20
+ temperature: stepSettings?.temperature ?? callSettings.temperature,
21
+ topP: stepSettings?.topP ?? callSettings.topP,
22
+ topK: stepSettings?.topK ?? callSettings.topK,
23
+ presencePenalty:
24
+ stepSettings?.presencePenalty ?? callSettings.presencePenalty,
25
+ frequencyPenalty:
26
+ stepSettings?.frequencyPenalty ?? callSettings.frequencyPenalty,
27
+ stopSequences: stepSettings?.stopSequences ?? callSettings.stopSequences,
28
+ seed: stepSettings?.seed ?? callSettings.seed,
29
+ reasoning: stepSettings?.reasoning ?? callSettings.reasoning,
30
+ });
31
+ }
@@ -7,6 +7,7 @@ import type {
7
7
  ToolSet,
8
8
  } from '@ai-sdk/provider-utils';
9
9
  import type { Instructions } from '../prompt';
10
+ import type { LanguageModelCallOptions } from '../prompt/language-model-call-options';
10
11
  import type { LanguageModel, ToolChoice } from '../types/language-model';
11
12
  import type { ActiveTools } from './active-tools';
12
13
  import type { ResponseMessage } from './response-message';
@@ -95,13 +96,17 @@ export type PrepareStepFunction<
95
96
 
96
97
  /**
97
98
  * The result type returned by a {@link PrepareStepFunction},
98
- * allowing per-step overrides of model, tools, instructions, or messages.
99
+ * allowing per-step overrides of model call settings, model, tools,
100
+ * instructions, or messages.
101
+ *
102
+ * Model call setting overrides apply only to the current step. Undefined
103
+ * settings fall back to the outer call settings.
99
104
  */
100
105
  export type PrepareStepResult<
101
106
  TOOLS extends ToolSet,
102
107
  RUNTIME_CONTEXT extends Context = Context,
103
108
  > =
104
- | {
109
+ | ({
105
110
  /**
106
111
  * Optionally override which LanguageModel instance is used for this step.
107
112
  */
@@ -175,5 +180,5 @@ export type PrepareStepResult<
175
180
  * container IDs for Anthropic's code execution.
176
181
  */
177
182
  providerOptions?: ProviderOptions;
178
- }
183
+ } & LanguageModelCallOptions)
179
184
  | undefined;
@@ -176,7 +176,8 @@ export type StepResult<
176
176
  readonly content: Array<ContentPart<TOOLS>>;
177
177
 
178
178
  /**
179
- * The generated text. Can be an empty string if the model has not generated any text.
179
+ * The concatenation of all text parts generated in this step.
180
+ * It is an empty string if the step contains no text parts.
180
181
  */
181
182
  readonly text: string;
182
183
 
@@ -113,6 +113,7 @@ import type {
113
113
  InferPartialOutput,
114
114
  } from './output-utils';
115
115
  import type { PrepareStepFunction } from './prepare-step';
116
+ import { prepareStepCallSettings } from './prepare-step-call-settings';
116
117
  import { convertToReasoningOutputs } from './reasoning-output';
117
118
  import type { ResponseMessage } from './response-message';
118
119
  import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
@@ -915,6 +916,11 @@ function createOutputTransformStream<
915
916
  textChunk += chunk.text;
916
917
  textProviderMetadata = chunk.providerMetadata ?? textProviderMetadata;
917
918
 
919
+ if (chunk.text.length === 0 && chunk.providerMetadata != null) {
920
+ controller.enqueue({ part: chunk, partialOutput: undefined });
921
+ return;
922
+ }
923
+
918
924
  // only publish if partial json can be parsed:
919
925
  const result = await output.parsePartialOutput({ text });
920
926
 
@@ -1963,6 +1969,11 @@ class DefaultStreamTextResult<
1963
1969
  prepareStepResult?.providerOptions,
1964
1970
  );
1965
1971
 
1972
+ const stepCallSettings = prepareStepCallSettings({
1973
+ callSettings,
1974
+ stepSettings: prepareStepResult,
1975
+ });
1976
+
1966
1977
  const stepStartTimestampMs = now();
1967
1978
 
1968
1979
  const { retry } = prepareRetries({ maxRetries, abortSignal });
@@ -2035,7 +2046,7 @@ class DefaultStreamTextResult<
2035
2046
  _internal: {
2036
2047
  now,
2037
2048
  },
2038
- ...callSettings,
2049
+ ...stepCallSettings,
2039
2050
  }),
2040
2051
  ),
2041
2052
  );
@@ -2198,7 +2209,10 @@ class DefaultStreamTextResult<
2198
2209
  }
2199
2210
 
2200
2211
  case 'text-delta': {
2201
- if (chunk.text.length > 0) {
2212
+ if (
2213
+ chunk.text.length > 0 ||
2214
+ chunk.providerMetadata != null
2215
+ ) {
2202
2216
  controller.enqueue(chunk);
2203
2217
  }
2204
2218
  break;
@@ -0,0 +1,186 @@
1
+ import {
2
+ experimental_getToolCaller,
3
+ type Experimental_ToolCallerTool,
4
+ type Tool,
5
+ type ToolSet,
6
+ } from '@ai-sdk/provider-utils';
7
+ import { InvalidArgumentError } from '../error/invalid-argument-error';
8
+
9
+ export interface Experimental_ToolCallerReference<
10
+ NAME extends string = string,
11
+ > {
12
+ readonly toolName: NAME;
13
+ }
14
+
15
+ type ToolCallerName<TOOLS extends ToolSet> = {
16
+ [NAME in keyof TOOLS]: TOOLS[NAME] extends Experimental_ToolCallerTool
17
+ ? NAME
18
+ : never;
19
+ }[keyof TOOLS] &
20
+ string;
21
+
22
+ type ToolCallerReferenceUnion<TOOLS extends ToolSet> = {
23
+ [NAME in ToolCallerName<TOOLS>]: Experimental_ToolCallerReference<NAME>;
24
+ }[ToolCallerName<TOOLS>];
25
+
26
+ export type Experimental_ToolCallers<TOOLS extends ToolSet> = (callers: {
27
+ [NAME in ToolCallerName<TOOLS>]: Experimental_ToolCallerReference<NAME>;
28
+ }) => {
29
+ [NAME in keyof TOOLS]?: ReadonlyArray<
30
+ 'direct' | ToolCallerReferenceUnion<TOOLS>
31
+ >;
32
+ };
33
+
34
+ export type ResolvedToolCallers = Record<
35
+ string,
36
+ ReadonlyArray<'direct' | string>
37
+ >;
38
+
39
+ export function resolveToolCallerConfiguration<TOOLS extends ToolSet>({
40
+ tools,
41
+ toolCallers,
42
+ }: {
43
+ tools: TOOLS | undefined;
44
+ toolCallers: Experimental_ToolCallers<TOOLS> | undefined;
45
+ }): ResolvedToolCallers | undefined {
46
+ if (tools == null || toolCallers == null) {
47
+ return undefined;
48
+ }
49
+
50
+ const namesByReference = new WeakMap<object, string>();
51
+ const callerReferences: Record<string, Experimental_ToolCallerReference> = {};
52
+
53
+ for (const [toolName, tool] of Object.entries(tools)) {
54
+ if (experimental_getToolCaller(tool) == null) {
55
+ continue;
56
+ }
57
+
58
+ const reference = Object.freeze({ toolName });
59
+ namesByReference.set(reference, toolName);
60
+ callerReferences[toolName] = reference;
61
+ }
62
+
63
+ const configuration = toolCallers(callerReferences as never);
64
+ const resolved: ResolvedToolCallers = {};
65
+
66
+ for (const [toolName, callers] of Object.entries(configuration)) {
67
+ if (!Object.prototype.hasOwnProperty.call(tools, toolName)) {
68
+ throw new InvalidArgumentError({
69
+ parameter: 'experimental_toolCallers',
70
+ value: configuration,
71
+ message: `unknown tool "${toolName}".`,
72
+ });
73
+ }
74
+
75
+ if (!Array.isArray(callers)) {
76
+ throw new InvalidArgumentError({
77
+ parameter: 'experimental_toolCallers',
78
+ value: configuration,
79
+ message: `callers for tool "${toolName}" must be an array.`,
80
+ });
81
+ }
82
+
83
+ resolved[toolName] = callers.map(caller => {
84
+ if (caller === 'direct') {
85
+ return caller;
86
+ }
87
+
88
+ const callerName =
89
+ caller != null && typeof caller === 'object'
90
+ ? namesByReference.get(caller)
91
+ : undefined;
92
+
93
+ if (callerName == null) {
94
+ throw new InvalidArgumentError({
95
+ parameter: 'experimental_toolCallers',
96
+ value: configuration,
97
+ message: `tool "${toolName}" contains an invalid caller reference.`,
98
+ });
99
+ }
100
+
101
+ return callerName;
102
+ });
103
+ }
104
+
105
+ return resolved;
106
+ }
107
+
108
+ export function prepareToolsForToolCallers({
109
+ tools,
110
+ toolCallers,
111
+ }: {
112
+ tools: ToolSet | undefined;
113
+ toolCallers: ResolvedToolCallers | undefined;
114
+ }): {
115
+ executionTools: ToolSet | undefined;
116
+ modelTools: ToolSet | undefined;
117
+ } {
118
+ if (tools == null || toolCallers == null) {
119
+ return { executionTools: tools, modelTools: tools };
120
+ }
121
+
122
+ const executionTools: ToolSet = { ...tools };
123
+ const modelTools: ToolSet = { ...tools };
124
+ const localToolsByCaller = new Map<string, ToolSet>();
125
+
126
+ for (const [toolName, callerNames] of Object.entries(toolCallers)) {
127
+ const tool = executionTools[toolName];
128
+ if (tool == null) {
129
+ continue;
130
+ }
131
+
132
+ let availableDirectly = false;
133
+ let availableToProvider = false;
134
+ let preparedTool: Tool = tool;
135
+
136
+ for (const callerName of callerNames) {
137
+ if (callerName === 'direct') {
138
+ availableDirectly = true;
139
+ continue;
140
+ }
141
+
142
+ const caller = experimental_getToolCaller(executionTools[callerName]);
143
+ if (caller == null) {
144
+ continue;
145
+ }
146
+
147
+ if (caller.type === 'provider') {
148
+ availableToProvider = true;
149
+ preparedTool = {
150
+ ...preparedTool,
151
+ providerOptions: caller.prepareProviderOptions(
152
+ preparedTool.providerOptions,
153
+ ),
154
+ } as Tool;
155
+ } else {
156
+ const localTools = localToolsByCaller.get(callerName) ?? {};
157
+ localTools[toolName] = preparedTool;
158
+ localToolsByCaller.set(callerName, localTools);
159
+ }
160
+ }
161
+
162
+ executionTools[toolName] = preparedTool;
163
+
164
+ if (availableDirectly || availableToProvider) {
165
+ modelTools[toolName] = preparedTool;
166
+ } else {
167
+ delete modelTools[toolName];
168
+ }
169
+ }
170
+
171
+ for (const [callerName, callerTool] of Object.entries(executionTools)) {
172
+ const caller = experimental_getToolCaller(callerTool);
173
+ if (caller?.type !== 'local') {
174
+ continue;
175
+ }
176
+
177
+ const boundCaller = caller.bind(localToolsByCaller.get(callerName) ?? {});
178
+ executionTools[callerName] = boundCaller;
179
+
180
+ if (Object.prototype.hasOwnProperty.call(modelTools, callerName)) {
181
+ modelTools[callerName] = boundCaller;
182
+ }
183
+ }
184
+
185
+ return { executionTools, modelTools };
186
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export {
7
7
  asSchema,
8
8
  createIdGenerator,
9
9
  dynamicTool,
10
+ experimental_toolCaller,
10
11
  generateId,
11
12
  jsonSchema,
12
13
  parseJsonEventStream,
@@ -19,6 +20,7 @@ export {
19
20
  type InferToolOutput,
20
21
  type Experimental_SandboxSession,
21
22
  type Experimental_SandboxProcess,
23
+ type Experimental_ToolCallerTool,
22
24
  type Schema,
23
25
  type Tool,
24
26
  type ToolApprovalRequest,