ai 6.0.271 → 6.0.273

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.
@@ -164,7 +164,7 @@ function detectMediaType({
164
164
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
165
165
 
166
166
  // src/version.ts
167
- var VERSION = true ? "6.0.271" : "0.0.0-test";
167
+ var VERSION = true ? "6.0.273" : "0.0.0-test";
168
168
 
169
169
  // src/util/download/download.ts
170
170
  var download = async ({
@@ -144,7 +144,7 @@ import {
144
144
  } from "@ai-sdk/provider-utils";
145
145
 
146
146
  // src/version.ts
147
- var VERSION = true ? "6.0.271" : "0.0.0-test";
147
+ var VERSION = true ? "6.0.273" : "0.0.0-test";
148
148
 
149
149
  // src/util/download/download.ts
150
150
  var download = async ({
@@ -218,6 +218,30 @@ const paymentTool = tool({
218
218
 
219
219
  In this example, only transactions over $1000 require approval. Smaller transactions execute automatically.
220
220
 
221
+ ### Securing Approval Requests
222
+
223
+ When your application rebuilds a conversation from client-provided messages, a
224
+ client could forge an approval response. For tools that perform sensitive
225
+ operations, set `experimental_toolApprovalSecret` to cryptographically bind
226
+ each approval to the server that issued it:
227
+
228
+ ```ts highlight="7"
229
+ import { ToolLoopAgent } from 'ai';
230
+
231
+ const agent = new ToolLoopAgent({
232
+ model: __MODEL__,
233
+ tools: { runCommand },
234
+ experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET,
235
+ });
236
+
237
+ const result = await agent.generate({ messages });
238
+ ```
239
+
240
+ The server HMAC-signs each approval request and verifies the signature when an
241
+ approval is replayed. A forged or tampered approval is rejected before the tool
242
+ executes. Keep the secret on the server and use the same value on every
243
+ instance that can handle a later approval response.
244
+
221
245
  ### Tool Execution Approval with useChat
222
246
 
223
247
  When using `useChat`, the approval flow is handled through UI state. See [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval) for details on handling approvals in your UI with `addToolApprovalResponse`.
@@ -811,11 +835,12 @@ async function generateSomething(prompt: string): Promise<{
811
835
 
812
836
  ## Handling Errors
813
837
 
814
- The AI SDK has three tool-call related errors:
838
+ The AI SDK has four tool-call related errors:
815
839
 
816
840
  - [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error): the model tries to call a tool that is not defined in the tools object
817
841
  - [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error): the model calls a tool with inputs that do not match the tool's input schema
818
842
  - [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error): an error that occurred during tool call repair
843
+ - [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error): the model response does not satisfy a required or specifically selected tool choice
819
844
 
820
845
  When tool execution fails (errors thrown by your tool's `execute` function), the AI SDK adds them as `tool-error` content parts to enable automated LLM roundtrips in multi-step scenarios.
821
846
 
@@ -1450,6 +1450,13 @@ To see `streamText` in action, check out [these examples](#examples).
1450
1450
  description:
1451
1451
  'The raw reason why the generation finished (from the provider).',
1452
1452
  },
1453
+ {
1454
+ name: 'output',
1455
+ type: 'COMPLETE_OUTPUT | undefined',
1456
+ isOptional: true,
1457
+ description:
1458
+ 'The parsed output when an output setting was provided and parsing succeeded.',
1459
+ },
1453
1460
  {
1454
1461
  name: 'usage',
1455
1462
  type: 'LanguageModelUsage',
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: AI_ToolChoiceViolationError
3
+ description: Learn how to handle AI_ToolChoiceViolationError
4
+ ---
5
+
6
+ # AI_ToolChoiceViolationError
7
+
8
+ This error occurs when a model response does not satisfy an enforced tool
9
+ choice. It is thrown when `toolChoice` is set to `'required'` but the response
10
+ contains no structured tool call, or when a specifically selected tool was not
11
+ called.
12
+
13
+ The error does not automatically interpret text or reasoning as an executable
14
+ tool call. You can inspect `content` to implement opt-in recovery, including
15
+ schema validation before executing any recovered call.
16
+
17
+ ## Properties
18
+
19
+ - `toolChoice`: The effective tool choice that the response did not satisfy
20
+ - `finishReason`: The reason why the model finished generating the response
21
+ - `provider`: The provider that returned the response
22
+ - `modelId`: The model that returned the response
23
+ - `content`: The normalized content returned by the model
24
+ - `message`: The error message
25
+
26
+ ## Checking for this Error
27
+
28
+ You can check if an error is an instance of `AI_ToolChoiceViolationError` using:
29
+
30
+ ```typescript
31
+ import { ToolChoiceViolationError } from 'ai';
32
+
33
+ if (ToolChoiceViolationError.isInstance(error)) {
34
+ const serializedCall = error.content.find(part => part.type === 'text')?.text;
35
+
36
+ // Parse and validate serializedCall before treating it as a tool call.
37
+ }
38
+ ```
@@ -33,6 +33,7 @@ collapsed: true
33
33
  - [AI_RetryError](/docs/reference/ai-sdk-errors/ai-retry-error)
34
34
  - [AI_ToolCallNotFoundForApprovalError](/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error)
35
35
  - [AI_ToolCallRepairError](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error)
36
+ - [AI_ToolChoiceViolationError](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error)
36
37
  - [AI_TooManyEmbeddingValuesForCallError](/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error)
37
38
  - [AI_TypeValidationError](/docs/reference/ai-sdk-errors/ai-type-validation-error)
38
39
  - [AI_UIMessageStreamError](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.271",
3
+ "version": "6.0.273",
4
4
  "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.",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@opentelemetry/api": "^1.9.0",
48
- "@ai-sdk/gateway": "3.0.184",
48
+ "@ai-sdk/gateway": "3.0.186",
49
49
  "@ai-sdk/provider": "3.0.15",
50
- "@ai-sdk/provider-utils": "4.0.49"
50
+ "@ai-sdk/provider-utils": "4.0.50"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@edge-runtime/vm": "^5.0.0",
@@ -119,6 +119,13 @@ export type ToolLoopAgentSettings<
119
119
  */
120
120
  onFinish?: ToolLoopAgentOnFinishCallback<NoInfer<TOOLS>>;
121
121
 
122
+ /**
123
+ * Secret for HMAC-signing tool approval requests. When set, the server
124
+ * signs each approval request at issuance and verifies the signature when
125
+ * the approval is replayed, preventing client-forged approvals.
126
+ */
127
+ experimental_toolApprovalSecret?: string | Uint8Array;
128
+
122
129
  /**
123
130
  * Additional provider-specific options. They are passed through
124
131
  * to the provider from the AI SDK and enable provider-specific
@@ -185,6 +192,7 @@ export type ToolLoopAgentSettings<
185
192
  | 'stopWhen'
186
193
  | 'experimental_telemetry'
187
194
  | 'activeTools'
195
+ | 'experimental_toolApprovalSecret'
188
196
  | 'providerOptions'
189
197
  | 'experimental_context'
190
198
  | 'experimental_download'
@@ -209,6 +217,7 @@ export type ToolLoopAgentSettings<
209
217
  | 'stopWhen'
210
218
  | 'experimental_telemetry'
211
219
  | 'activeTools'
220
+ | 'experimental_toolApprovalSecret'
212
221
  | 'providerOptions'
213
222
  | 'experimental_context'
214
223
  | 'experimental_download'
@@ -28,6 +28,7 @@ export { NoSpeechGeneratedError } from './no-speech-generated-error';
28
28
  export { NoTranscriptGeneratedError } from './no-transcript-generated-error';
29
29
  export { NoVideoGeneratedError } from './no-video-generated-error';
30
30
  export { NoSuchToolError } from './no-such-tool-error';
31
+ export { ToolChoiceViolationError } from './tool-choice-violation-error';
31
32
  export { ToolCallRepairError } from './tool-call-repair-error';
32
33
  export { UnsupportedModelVersionError } from './unsupported-model-version-error';
33
34
  export { UIMessageStreamError } from './ui-message-stream-error';
@@ -0,0 +1,80 @@
1
+ import {
2
+ AISDKError,
3
+ type LanguageModelV3Content,
4
+ type LanguageModelV3ToolChoice,
5
+ } from '@ai-sdk/provider';
6
+ import type { FinishReason } from '../types/language-model';
7
+
8
+ const name = 'AI_ToolChoiceViolationError';
9
+ const marker = `vercel.ai.error.${name}`;
10
+ const symbol = Symbol.for(marker);
11
+
12
+ type EnforcedToolChoice = Extract<
13
+ LanguageModelV3ToolChoice,
14
+ { type: 'required' } | { type: 'tool' }
15
+ >;
16
+
17
+ /**
18
+ * Thrown when a model response does not satisfy an enforced tool choice.
19
+ */
20
+ export class ToolChoiceViolationError extends AISDKError {
21
+ private readonly [symbol] = true; // used in isInstance
22
+
23
+ /**
24
+ * The tool choice that the model response did not satisfy.
25
+ */
26
+ readonly toolChoice: EnforcedToolChoice;
27
+
28
+ /**
29
+ * Reason why the model finished generating the response.
30
+ */
31
+ readonly finishReason: FinishReason;
32
+
33
+ /**
34
+ * The provider that returned the response.
35
+ */
36
+ readonly provider: string;
37
+
38
+ /**
39
+ * The model that returned the response.
40
+ */
41
+ readonly modelId: string;
42
+
43
+ /**
44
+ * The normalized content returned by the model.
45
+ *
46
+ * This can be inspected to recover a tool call that the provider returned as
47
+ * text or reasoning instead of a structured tool call.
48
+ */
49
+ readonly content: Array<LanguageModelV3Content>;
50
+
51
+ constructor({
52
+ toolChoice,
53
+ finishReason,
54
+ provider,
55
+ modelId,
56
+ content,
57
+ message = toolChoice.type === 'required'
58
+ ? 'Model response did not contain a tool call even though tool choice was required.'
59
+ : `Model response did not contain a call to the required tool '${toolChoice.toolName}'.`,
60
+ }: {
61
+ toolChoice: EnforcedToolChoice;
62
+ finishReason: FinishReason;
63
+ provider: string;
64
+ modelId: string;
65
+ content: Array<LanguageModelV3Content>;
66
+ message?: string;
67
+ }) {
68
+ super({ name, message });
69
+
70
+ this.toolChoice = toolChoice;
71
+ this.finishReason = finishReason;
72
+ this.provider = provider;
73
+ this.modelId = modelId;
74
+ this.content = content;
75
+ }
76
+
77
+ static isInstance(error: unknown): error is ToolChoiceViolationError {
78
+ return AISDKError.hasMarker(error, marker);
79
+ }
80
+ }
@@ -11,7 +11,7 @@ import {
11
11
  type ProviderOptions,
12
12
  } from '@ai-sdk/provider-utils';
13
13
  import type { Tracer } from '@opentelemetry/api';
14
- import { NoOutputGeneratedError } from '../error';
14
+ import { NoOutputGeneratedError, ToolChoiceViolationError } from '../error';
15
15
  import { notify } from '../util/notify';
16
16
  import { logWarnings } from '../logger/log-warnings';
17
17
  import { resolveLanguageModel } from '../model/resolve-model';
@@ -961,6 +961,30 @@ export async function generateText<
961
961
  }),
962
962
  ),
963
963
  );
964
+
965
+ const enforcedToolChoice =
966
+ stepToolChoice?.type === 'required' ||
967
+ stepToolChoice?.type === 'tool'
968
+ ? stepToolChoice
969
+ : undefined;
970
+
971
+ if (
972
+ enforcedToolChoice != null &&
973
+ !stepToolCalls.some(
974
+ toolCall =>
975
+ enforcedToolChoice.type === 'required' ||
976
+ toolCall.toolName === enforcedToolChoice.toolName,
977
+ )
978
+ ) {
979
+ throw new ToolChoiceViolationError({
980
+ toolChoice: enforcedToolChoice,
981
+ finishReason: currentModelResponse.finishReason.unified,
982
+ provider: stepModel.provider,
983
+ modelId: stepModel.modelId,
984
+ content: currentModelResponse.content,
985
+ });
986
+ }
987
+
964
988
  const toolApprovalRequests: Record<
965
989
  string,
966
990
  ToolApprovalRequestOutput<TOOLS>
@@ -33,7 +33,9 @@ export {
33
33
  } from './stop-condition';
34
34
  export {
35
35
  streamText,
36
+ type StreamTextEndEvent,
36
37
  type StreamTextOnChunkCallback,
38
+ type StreamTextOnEndCallback,
37
39
  type StreamTextOnErrorCallback,
38
40
  type StreamTextOnFinishCallback,
39
41
  type StreamTextOnStartCallback,
@@ -214,14 +214,31 @@ export type StreamTextOnChunkCallback<TOOLS extends ToolSet> = (event: {
214
214
  >;
215
215
  }) => PromiseLike<void> | void;
216
216
 
217
+ export type StreamTextEndEvent<
218
+ TOOLS extends ToolSet = ToolSet,
219
+ OUTPUT extends Output = Output,
220
+ > = OnFinishEvent<TOOLS> & {
221
+ /**
222
+ * The parsed output when an output setting was provided and parsing
223
+ * succeeded.
224
+ */
225
+ readonly output?: InferCompleteOutput<OUTPUT>;
226
+ };
227
+
228
+ export type StreamTextOnEndCallback<
229
+ TOOLS extends ToolSet = ToolSet,
230
+ OUTPUT extends Output = Output,
231
+ > = (event: StreamTextEndEvent<TOOLS, OUTPUT>) => PromiseLike<void> | void;
232
+
217
233
  /**
218
234
  * Callback that is set using the `onFinish` option.
219
235
  *
220
236
  * @param event - The event that is passed to the callback.
221
237
  */
222
- export type StreamTextOnFinishCallback<TOOLS extends ToolSet> = (
223
- event: OnFinishEvent<TOOLS>,
224
- ) => PromiseLike<void> | void;
238
+ export type StreamTextOnFinishCallback<
239
+ TOOLS extends ToolSet = ToolSet,
240
+ OUTPUT extends Output = Output,
241
+ > = StreamTextOnEndCallback<TOOLS, OUTPUT>;
225
242
 
226
243
  /**
227
244
  * Callback that is set using the `onAbort` option.
@@ -492,7 +509,7 @@ export function streamText<
492
509
  *
493
510
  * The usage is the combined usage of all steps.
494
511
  */
495
- onFinish?: StreamTextOnFinishCallback<TOOLS>;
512
+ onFinish?: StreamTextOnFinishCallback<NoInfer<TOOLS>, NoInfer<OUTPUT>>;
496
513
 
497
514
  onAbort?: StreamTextOnAbortCallback<TOOLS>;
498
515
 
@@ -758,6 +775,8 @@ class DefaultStreamTextResult<
758
775
  Awaited<StreamTextResult<TOOLS, OUTPUT>['steps']>
759
776
  >();
760
777
 
778
+ private outputPromise: Promise<InferCompleteOutput<OUTPUT>> | undefined;
779
+
761
780
  private readonly addStream: (
762
781
  stream: ReadableStream<TextStreamPart<TOOLS>>,
763
782
  callbacks?: {
@@ -862,7 +881,9 @@ class DefaultStreamTextResult<
862
881
  // callbacks:
863
882
  onChunk: undefined | StreamTextOnChunkCallback<TOOLS>;
864
883
  onError: StreamTextOnErrorCallback;
865
- onFinish: undefined | StreamTextOnFinishCallback<TOOLS>;
884
+ onFinish:
885
+ | undefined
886
+ | StreamTextOnFinishCallback<NoInfer<TOOLS>, NoInfer<OUTPUT>>;
866
887
  onAbort: undefined | StreamTextOnAbortCallback<TOOLS>;
867
888
  onStepFinish: undefined | StreamTextOnStepFinishCallback<TOOLS>;
868
889
  onStart: undefined | StreamTextOnStartCallback<TOOLS, OUTPUT>;
@@ -1211,43 +1232,61 @@ class DefaultStreamTextResult<
1211
1232
 
1212
1233
  // call onFinish callback:
1213
1234
  const finalStep = recordedSteps[recordedSteps.length - 1];
1214
-
1215
- await notify({
1216
- event: {
1217
- stepNumber: finalStep.stepNumber,
1218
- model: finalStep.model,
1219
- functionId: finalStep.functionId,
1220
- metadata: finalStep.metadata,
1221
- experimental_context: finalStep.experimental_context,
1222
- finishReason: finalStep.finishReason,
1223
- rawFinishReason: finalStep.rawFinishReason,
1224
- totalUsage,
1225
- usage: finalStep.usage,
1226
- content: finalStep.content,
1227
- text: finalStep.text,
1228
- reasoningText: finalStep.reasoningText,
1229
- reasoning: finalStep.reasoning,
1230
- files: finalStep.files,
1231
- sources: finalStep.sources,
1232
- toolCalls: finalStep.toolCalls,
1233
- staticToolCalls: finalStep.staticToolCalls,
1234
- dynamicToolCalls: finalStep.dynamicToolCalls,
1235
- toolResults: finalStep.toolResults,
1236
- staticToolResults: finalStep.staticToolResults,
1237
- dynamicToolResults: finalStep.dynamicToolResults,
1238
- request: finalStep.request,
1239
- response: finalStep.response,
1240
- warnings: finalStep.warnings,
1241
- providerMetadata: finalStep.providerMetadata,
1242
- steps: recordedSteps,
1243
- },
1244
- callbacks: [
1245
- onFinish,
1246
- globalTelemetry.onFinish as
1235
+ const onFinishEvent: OnFinishEvent<TOOLS> = {
1236
+ stepNumber: finalStep.stepNumber,
1237
+ model: finalStep.model,
1238
+ functionId: finalStep.functionId,
1239
+ metadata: finalStep.metadata,
1240
+ experimental_context: finalStep.experimental_context,
1241
+ finishReason: finalStep.finishReason,
1242
+ rawFinishReason: finalStep.rawFinishReason,
1243
+ totalUsage,
1244
+ usage: finalStep.usage,
1245
+ content: finalStep.content,
1246
+ text: finalStep.text,
1247
+ reasoningText: finalStep.reasoningText,
1248
+ reasoning: finalStep.reasoning,
1249
+ files: finalStep.files,
1250
+ sources: finalStep.sources,
1251
+ toolCalls: finalStep.toolCalls,
1252
+ staticToolCalls: finalStep.staticToolCalls,
1253
+ dynamicToolCalls: finalStep.dynamicToolCalls,
1254
+ toolResults: finalStep.toolResults,
1255
+ staticToolResults: finalStep.staticToolResults,
1256
+ dynamicToolResults: finalStep.dynamicToolResults,
1257
+ request: finalStep.request,
1258
+ response: finalStep.response,
1259
+ warnings: finalStep.warnings,
1260
+ providerMetadata: finalStep.providerMetadata,
1261
+ steps: recordedSteps,
1262
+ };
1263
+ const onFinishWithOutput =
1264
+ onFinish == null
1265
+ ? undefined
1266
+ : async (event: OnFinishEvent<TOOLS>) => {
1267
+ const parsedOutput =
1268
+ output == null
1269
+ ? undefined
1270
+ : await self.getOutputPromise().catch(() => undefined);
1271
+
1272
+ await onFinish({
1273
+ ...event,
1274
+ ...(output != null ? { output: parsedOutput } : {}),
1275
+ });
1276
+ };
1277
+
1278
+ await Promise.all([
1279
+ notify({
1280
+ event: onFinishEvent,
1281
+ callbacks: onFinishWithOutput,
1282
+ }),
1283
+ notify({
1284
+ event: onFinishEvent,
1285
+ callbacks: globalTelemetry.onFinish as
1247
1286
  | undefined
1248
- | StreamTextOnFinishCallback<TOOLS>,
1249
- ],
1250
- });
1287
+ | ((event: OnFinishEvent<TOOLS>) => PromiseLike<void> | void),
1288
+ }),
1289
+ ]);
1251
1290
 
1252
1291
  // Add response information to the root span:
1253
1292
  rootSpan.setAttributes(
@@ -2634,18 +2673,26 @@ class DefaultStreamTextResult<
2634
2673
  return createAsyncIterableStream(this.teeStream().pipeThrough(transform));
2635
2674
  }
2636
2675
 
2676
+ private getOutputPromise(): Promise<InferCompleteOutput<OUTPUT>> {
2677
+ if (this.outputPromise == null) {
2678
+ this.outputPromise = this.finalStep.then(step => {
2679
+ const output = this.outputSpecification ?? text();
2680
+ return output.parseCompleteOutput(
2681
+ { text: step.text },
2682
+ {
2683
+ response: step.response,
2684
+ usage: step.usage,
2685
+ finishReason: step.finishReason,
2686
+ },
2687
+ );
2688
+ });
2689
+ }
2690
+
2691
+ return this.outputPromise;
2692
+ }
2693
+
2637
2694
  get output(): Promise<InferCompleteOutput<OUTPUT>> {
2638
- return this.finalStep.then(step => {
2639
- const output = this.outputSpecification ?? text();
2640
- return output.parseCompleteOutput(
2641
- { text: step.text },
2642
- {
2643
- response: step.response,
2644
- usage: step.usage,
2645
- finishReason: step.finishReason,
2646
- },
2647
- );
2648
- });
2695
+ return this.getOutputPromise();
2649
2696
  }
2650
2697
 
2651
2698
  toUIMessageStream<UI_MESSAGE extends UIMessage>({