ai 7.0.78 → 7.0.82

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/index.d.ts +103 -37
  3. package/dist/index.js +608 -422
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +9 -1
  6. package/dist/internal/index.js +4 -2
  7. package/dist/internal/index.js.map +1 -1
  8. package/dist/test/index.d.ts +4 -1
  9. package/dist/test/index.js +4 -0
  10. package/dist/test/index.js.map +1 -1
  11. package/docs/02-foundations/02-providers-and-models.mdx +2 -2
  12. package/docs/03-agents/06-policy-tool-approvals.mdx +1 -1
  13. package/docs/03-agents/06-tool-approvals.mdx +6 -0
  14. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +6 -4
  15. package/docs/03-ai-sdk-core/50-error-handling.mdx +17 -2
  16. package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +4 -4
  17. package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +6 -0
  18. package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
  19. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +1 -1
  20. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +5 -4
  21. package/docs/07-reference/01-ai-sdk-core/06-embed-many.mdx +6 -2
  22. package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +1 -1
  23. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +2 -2
  24. package/docs/07-reference/04-ai-sdk-workflow/03-generate-video.mdx +157 -0
  25. package/docs/07-reference/04-ai-sdk-workflow/index.mdx +6 -0
  26. package/docs/07-reference/05-ai-sdk-errors/ai-stream-provider-error.mdx +52 -0
  27. package/docs/07-reference/05-ai-sdk-errors/index.mdx +1 -0
  28. package/package.json +18 -7
  29. package/src/batch/batch-types.ts +7 -0
  30. package/src/batch/batch.ts +2 -0
  31. package/src/embed/embed-many.ts +78 -8
  32. package/src/error/index.ts +1 -0
  33. package/src/error/stream-provider-error.ts +78 -0
  34. package/src/generate-text/execute-tools-from-stream.ts +3 -0
  35. package/src/generate-text/generate-text-result.ts +2 -1
  36. package/src/generate-text/generate-text.ts +9 -2
  37. package/src/generate-text/stream-language-model-call.ts +8 -0
  38. package/src/generate-text/to-response-messages.ts +1 -0
  39. package/src/generate-text/tool-approval-configuration.ts +5 -1
  40. package/src/generate-text/tool-approval-request-output.ts +5 -0
  41. package/src/index.ts +7 -1
  42. package/src/middleware/wrap-embedding-model.ts +11 -2
  43. package/src/model/get-embedding-model-max-input-bytes-per-call.ts +15 -0
  44. package/src/prompt/content-part.ts +1 -0
  45. package/src/prompt/normalize-stream-provider-error.ts +131 -0
  46. package/src/test/mock-embedding-model-v4.ts +9 -0
  47. package/src/ui/convert-to-model-messages.ts +3 -0
  48. package/src/ui/process-ui-message-stream.ts +8 -3
  49. package/src/ui/ui-messages.ts +10 -0
  50. package/src/ui/validate-ui-messages.ts +10 -0
  51. package/src/ui-message-stream/to-ui-message-chunk.ts +1 -0
  52. package/src/ui-message-stream/ui-message-chunks.ts +2 -0
@@ -0,0 +1,157 @@
1
+ ---
2
+ title: generateVideo
3
+ description: API Reference for durable video generation in workflows.
4
+ ---
5
+
6
+ # `generateVideo()`
7
+
8
+ Generates videos durably inside a workflow. The helper starts an asynchronous
9
+ video generation job with a Workflow webhook URL, suspends the workflow until
10
+ the provider sends a terminal notification, and then retrieves the completed
11
+ result with one status request.
12
+
13
+ Unlike [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video)
14
+ from `ai`, this helper does not download provider-hosted videos. URL results
15
+ remain URLs so your workflow can decide whether to persist, copy, or process
16
+ them in another step without serializing the video bytes through workflow step
17
+ boundaries.
18
+
19
+ ```ts
20
+ import { experimental_generateVideo as generateVideo } from '@ai-sdk/workflow/video';
21
+
22
+ export async function videoWorkflow(prompt: string) {
23
+ 'use workflow';
24
+
25
+ // The workflow suspends while the video renders, consuming no compute.
26
+ const result = await generateVideo({
27
+ model: 'klingai/kling-v3.0-t2v',
28
+ prompt,
29
+ });
30
+
31
+ return result.videos;
32
+ }
33
+ ```
34
+
35
+ The selected model must support asynchronous start and status operations and
36
+ provider webhooks. The helper must be called from a Workflow SDK workflow.
37
+
38
+ ## Import
39
+
40
+ <Snippet
41
+ text={`import { experimental_generateVideo as generateVideo } from "@ai-sdk/workflow/video"`}
42
+ prompt={false}
43
+ />
44
+
45
+ ## Parameters
46
+
47
+ The helper accepts the same parameters as `experimental_startVideo` from `ai`,
48
+ except `webhookUrl` and `abortSignal`. It creates and manages the webhook URL.
49
+
50
+ <PropertiesTable
51
+ content={[
52
+ {
53
+ name: 'model',
54
+ type: 'VideoModel',
55
+ isRequired: true,
56
+ description:
57
+ 'The asynchronous video model to use. A string compatible with Vercel AI Gateway or a serializable provider model.',
58
+ },
59
+ {
60
+ name: 'prompt',
61
+ type: 'string | GenerateVideoPrompt',
62
+ isRequired: true,
63
+ description: 'The prompt or image prompt used to generate the video.',
64
+ },
65
+ {
66
+ name: 'n',
67
+ type: 'number',
68
+ isOptional: true,
69
+ description: 'Number of videos to generate. Default: 1.',
70
+ },
71
+ {
72
+ name: 'aspectRatio',
73
+ type: '`${number}:${number}` | "adaptive"',
74
+ isOptional: true,
75
+ description: 'Aspect ratio of the generated videos.',
76
+ },
77
+ {
78
+ name: 'resolution',
79
+ type: '`${number}x${number}`',
80
+ isOptional: true,
81
+ description: 'Resolution of the generated videos.',
82
+ },
83
+ {
84
+ name: 'duration',
85
+ type: 'number',
86
+ isOptional: true,
87
+ description: 'Duration of each generated video in seconds.',
88
+ },
89
+ {
90
+ name: 'maxVideosPerCall',
91
+ type: 'number',
92
+ isOptional: true,
93
+ description: 'Maximum number of videos that may be started in one call.',
94
+ },
95
+ {
96
+ name: 'fps',
97
+ type: 'number',
98
+ isOptional: true,
99
+ description: 'Frames per second for the generated videos.',
100
+ },
101
+ {
102
+ name: 'seed',
103
+ type: 'number',
104
+ isOptional: true,
105
+ description: 'Seed used for deterministic generation when supported.',
106
+ },
107
+ {
108
+ name: 'frameImages',
109
+ type: 'Array<{ image: DataContent; frameType: VideoModelV4FrameType }>',
110
+ isOptional: true,
111
+ description: 'Role-tagged first and last frame images.',
112
+ },
113
+ {
114
+ name: 'inputReferences',
115
+ type: 'Array<DataContent | { data: DataContent; mediaType?: string }>',
116
+ isOptional: true,
117
+ description: 'Reference images or videos for the generation.',
118
+ },
119
+ {
120
+ name: 'generateAudio',
121
+ type: 'boolean',
122
+ isOptional: true,
123
+ description: 'Whether to generate audio with the video.',
124
+ },
125
+ {
126
+ name: 'providerOptions',
127
+ type: 'ProviderOptions',
128
+ isOptional: true,
129
+ description: 'Additional provider-specific options.',
130
+ },
131
+ {
132
+ name: 'headers',
133
+ type: 'Record<string, string>',
134
+ isOptional: true,
135
+ description: 'Additional HTTP headers for start and status requests.',
136
+ },
137
+ {
138
+ name: 'maxRetries',
139
+ type: 'number',
140
+ isOptional: true,
141
+ description:
142
+ 'Maximum number of AI SDK retries for each start and status request. Default: 2.',
143
+ },
144
+ ]}
145
+ />
146
+
147
+ ## Returns
148
+
149
+ Returns the completed status result from the provider. `videos` contains raw
150
+ provider video data discriminated by `type`:
151
+
152
+ - `url`: A provider-hosted URL and media type
153
+ - `base64`: Base64-encoded video data and media type
154
+ - `binary`: A `Uint8Array` and media type
155
+
156
+ Hosted URLs can expire. Handle any video you need to retain in a separate
157
+ workflow step.
@@ -22,5 +22,11 @@ collapsed: true
22
22
  'Chat transport with automatic stream reconnection for workflow-based apps.',
23
23
  href: '/docs/reference/ai-sdk-workflow/workflow-chat-transport',
24
24
  },
25
+ {
26
+ title: 'generateVideo',
27
+ description:
28
+ 'Generate videos durably with provider webhooks and no implicit downloads.',
29
+ href: '/docs/reference/ai-sdk-workflow/generate-video',
30
+ },
25
31
  ]}
26
32
  />
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: AI_StreamProviderError
3
+ description: Learn how to handle AI_StreamProviderError
4
+ ---
5
+
6
+ # AI_StreamProviderError
7
+
8
+ This error represents a well-formed error event reported by a provider after a
9
+ model response stream has started. The AI SDK exposes it in streaming error
10
+ parts and callbacks such as the `streamText` `onError` callback.
11
+
12
+ ## Properties
13
+
14
+ - `message`: The provider error message
15
+ - `type`: The provider-defined error type (optional)
16
+ - `code`: The provider-defined error code as a string or number (optional)
17
+ - `statusCode`: The HTTP-equivalent status code when supplied by or inferable from provider metadata (optional)
18
+ - `isRetryable`: Whether retrying the model call may succeed
19
+ - `data`: The original provider error payload (optional)
20
+ - `cause`: The underlying error that caused the failure (optional)
21
+
22
+ `isRetryable` is a retry classification, not an automatic retry. A stream may
23
+ already contain partial output, so applications should decide whether to
24
+ discard, replace, or preserve that output before starting another model call.
25
+
26
+ ## Checking for this Error
27
+
28
+ Use `StreamProviderError.isInstance` so identification also works when multiple
29
+ AI SDK versions are present:
30
+
31
+ ```typescript
32
+ import { StreamProviderError, streamText } from 'ai';
33
+
34
+ const result = streamText({
35
+ model: __MODEL__,
36
+ prompt: 'Write a vegetarian lasagna recipe for 4 people.',
37
+ onError: ({ error }) => {
38
+ if (StreamProviderError.isInstance(error) && error.isRetryable) {
39
+ // Schedule an application-managed retry.
40
+ }
41
+ },
42
+ });
43
+ ```
44
+
45
+ Providers may not include enough metadata to determine a status code. In that
46
+ case, `statusCode` is `undefined`, and retryability is classified
47
+ conservatively. Provider-specific status and retry mappings are supplied by the
48
+ provider adapter rather than inferred from arbitrary provider error type or code
49
+ substrings. Provider `type` and `code` values are preserved independently, even
50
+ when a numeric code is also used to determine `statusCode`. Errors that are
51
+ already `Error` instances and malformed or unknown provider values are
52
+ preserved unchanged.
@@ -33,6 +33,7 @@ collapsed: true
33
33
  - [AI_NoSuchProviderReferenceError](/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error)
34
34
  - [AI_NoSuchToolError](/docs/reference/ai-sdk-errors/ai-no-such-tool-error)
35
35
  - [AI_RetryError](/docs/reference/ai-sdk-errors/ai-retry-error)
36
+ - [AI_StreamProviderError](/docs/reference/ai-sdk-errors/ai-stream-provider-error)
36
37
  - [AI_ToolCallNotFoundForApprovalError](/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error)
37
38
  - [AI_ToolCallRepairError](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error)
38
39
  - [AI_TooManyEmbeddingValuesForCallError](/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.78",
3
+ "version": "7.0.82",
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,21 +42,32 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.63",
46
- "@ai-sdk/provider": "4.0.7",
47
- "@ai-sdk/provider-utils": "5.0.29"
45
+ "@ai-sdk/gateway": "4.0.67",
46
+ "@ai-sdk/provider": "4.0.8",
47
+ "@ai-sdk/provider-utils": "5.0.32"
48
48
  },
49
49
  "devDependencies": {
50
+ "@ai-sdk/amazon-bedrock": "5.0.65",
51
+ "@ai-sdk/deepseek": "3.0.34",
52
+ "@ai-sdk/google": "4.0.53",
53
+ "@ai-sdk/groq": "4.0.33",
54
+ "@ai-sdk/huggingface": "2.0.39",
55
+ "@ai-sdk/moonshotai": "3.0.41",
56
+ "@ai-sdk/open-responses": "2.0.34",
57
+ "@ai-sdk/openai": "4.0.49",
58
+ "@ai-sdk/test-server": "2.0.1",
59
+ "@ai-sdk/xai": "4.0.47",
50
60
  "@edge-runtime/vm": "^5.0.0",
61
+ "@smithy/eventstream-codec": "^4.3.3",
62
+ "@smithy/util-utf8": "^4.3.3",
51
63
  "@types/json-schema": "7.0.15",
52
64
  "@types/node": "22.19.19",
65
+ "@vercel/ai-tsconfig": "0.0.0",
53
66
  "esbuild": "^0.28.1",
54
67
  "tsup": "^7.2.0",
55
68
  "tsx": "4.23.12",
56
69
  "typescript": "5.8.3",
57
- "zod": "3.25.76",
58
- "@ai-sdk/test-server": "2.0.1",
59
- "@vercel/ai-tsconfig": "0.0.0"
70
+ "zod": "3.25.76"
60
71
  },
61
72
  "peerDependencies": {
62
73
  "zod": "^3.25.76 || ^4.1.8"
@@ -77,6 +77,13 @@ export type StartTextBatchOptions = {
77
77
  model: BatchLanguageModel;
78
78
  requests: ReadonlyArray<TextBatchRequest>;
79
79
  providerOptions?: ProviderOptions;
80
+
81
+ /**
82
+ * URL that the provider should notify when the batch reaches a terminal
83
+ * state. Providers that do not support completion webhooks return an
84
+ * unsupported warning.
85
+ */
86
+ webhookUrl?: string;
80
87
  } & BatchRequestOptions;
81
88
 
82
89
  /**
@@ -37,6 +37,7 @@ export async function startTextBatch({
37
37
  model: modelArg,
38
38
  requests,
39
39
  providerOptions,
40
+ webhookUrl,
40
41
  abortSignal,
41
42
  headers,
42
43
  timeout,
@@ -81,6 +82,7 @@ export async function startTextBatch({
81
82
  providerOptions,
82
83
  abortSignal: operationAbortSignal,
83
84
  headers: headersWithUserAgent,
85
+ ...(webhookUrl != null && { webhookUrl }),
84
86
  });
85
87
  const { batchId, warnings, ...status } = result;
86
88
 
@@ -4,6 +4,7 @@ import {
4
4
  type ProviderOptions,
5
5
  } from '@ai-sdk/provider-utils';
6
6
  import { logWarnings } from '../logger/log-warnings';
7
+ import { getEmbeddingModelMaxInputBytesPerCall } from '../model/get-embedding-model-max-input-bytes-per-call';
7
8
  import { resolveEmbeddingModel } from '../model/resolve-model';
8
9
  import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
9
10
  import type { TelemetryOptions } from '../telemetry/telemetry-options';
@@ -26,8 +27,9 @@ const originalGenerateCallId = createIdGenerator({
26
27
  * Embed several values using an embedding model. The type of the value is defined
27
28
  * by the embedding model.
28
29
  *
29
- * `embedMany` automatically splits large requests into smaller chunks if the model
30
- * has a limit on how many embeddings can be generated in a single call.
30
+ * `embedMany` automatically splits large requests into smaller chunks when the
31
+ * model has a limit on either the number of embeddings or the UTF-8 input bytes
32
+ * that can be processed in a single call.
31
33
  *
32
34
  * @param model - The embedding model to use.
33
35
  * @param values - The values that should be embedded.
@@ -197,11 +199,22 @@ export async function embedMany({
197
199
  });
198
200
 
199
201
  try {
200
- const [maxEmbeddingsPerCall, supportsParallelCalls] = await Promise.all(
201
- [model.maxEmbeddingsPerCall, model.supportsParallelCalls],
202
- );
203
-
204
- if (maxEmbeddingsPerCall == null || maxEmbeddingsPerCall === Infinity) {
202
+ const [
203
+ maxEmbeddingsPerCall,
204
+ maxInputBytesPerCall,
205
+ supportsParallelCalls,
206
+ ] = await Promise.all([
207
+ model.maxEmbeddingsPerCall,
208
+ getEmbeddingModelMaxInputBytesPerCall(model),
209
+ model.supportsParallelCalls,
210
+ ]);
211
+
212
+ const hasEmbeddingLimit =
213
+ maxEmbeddingsPerCall != null && maxEmbeddingsPerCall !== Infinity;
214
+ const hasInputByteLimit =
215
+ maxInputBytesPerCall != null && maxInputBytesPerCall !== Infinity;
216
+
217
+ if (!hasEmbeddingLimit && !hasInputByteLimit) {
205
218
  const { embeddings, usage, warnings, response, providerMetadata } =
206
219
  await retry(async () => {
207
220
  const embedCallId = generateCallId();
@@ -283,7 +296,15 @@ export async function embedMany({
283
296
  });
284
297
  }
285
298
 
286
- const valueChunks = splitArray(values, maxEmbeddingsPerCall);
299
+ const valueChunks = splitByEmbeddingLimits({
300
+ values,
301
+ maxEmbeddingsPerCall: hasEmbeddingLimit
302
+ ? maxEmbeddingsPerCall
303
+ : Infinity,
304
+ maxInputBytesPerCall: hasInputByteLimit
305
+ ? maxInputBytesPerCall
306
+ : Infinity,
307
+ });
287
308
 
288
309
  const embeddings: Array<Embedding> = [];
289
310
  const warnings: Array<Warning> = [];
@@ -415,6 +436,55 @@ export async function embedMany({
415
436
  });
416
437
  }
417
438
 
439
+ const textEncoder = new TextEncoder();
440
+
441
+ function splitByEmbeddingLimits({
442
+ values,
443
+ maxEmbeddingsPerCall,
444
+ maxInputBytesPerCall,
445
+ }: {
446
+ values: Array<string>;
447
+ maxEmbeddingsPerCall: number;
448
+ maxInputBytesPerCall: number;
449
+ }): Array<Array<string>> {
450
+ if (maxEmbeddingsPerCall <= 0) {
451
+ throw new Error('maxEmbeddingsPerCall must be greater than 0');
452
+ }
453
+
454
+ if (maxInputBytesPerCall <= 0) {
455
+ throw new Error('maxInputBytesPerCall must be greater than 0');
456
+ }
457
+
458
+ if (values.length === 0) {
459
+ return [];
460
+ }
461
+
462
+ const chunks: Array<Array<string>> = [];
463
+ let currentChunk: Array<string> = [];
464
+ let currentInputBytes = 0;
465
+
466
+ for (const value of values) {
467
+ const inputBytes = textEncoder.encode(value).length;
468
+
469
+ if (
470
+ currentChunk.length > 0 &&
471
+ (currentChunk.length >= maxEmbeddingsPerCall ||
472
+ currentInputBytes + inputBytes > maxInputBytesPerCall)
473
+ ) {
474
+ chunks.push(currentChunk);
475
+ currentChunk = [];
476
+ currentInputBytes = 0;
477
+ }
478
+
479
+ currentChunk.push(value);
480
+ currentInputBytes += inputBytes;
481
+ }
482
+
483
+ chunks.push(currentChunk);
484
+
485
+ return chunks;
486
+ }
487
+
418
488
  class DefaultEmbedManyResult implements EmbedManyResult {
419
489
  readonly values: EmbedManyResult['values'];
420
490
  readonly embeddings: EmbedManyResult['embeddings'];
@@ -30,6 +30,7 @@ export { NoTranscriptGeneratedError } from './no-transcript-generated-error';
30
30
  export { NoTranslationGeneratedError } from './no-translation-generated-error';
31
31
  export { NoVideoGeneratedError } from './no-video-generated-error';
32
32
  export { NoSuchToolError } from './no-such-tool-error';
33
+ export { StreamProviderError } from './stream-provider-error';
33
34
  export { ToolCallRepairError } from './tool-call-repair-error';
34
35
  export { UnsupportedModelVersionError } from './unsupported-model-version-error';
35
36
  export { UIMessageStreamError } from './ui-message-stream-error';
@@ -0,0 +1,78 @@
1
+ import { AISDKError } from '@ai-sdk/provider';
2
+
3
+ const name = 'AI_StreamProviderError';
4
+ const marker = `vercel.ai.error.${name}`;
5
+ const symbol = Symbol.for(marker);
6
+
7
+ /**
8
+ * Error reported by a provider after a model response stream has started.
9
+ */
10
+ export class StreamProviderError extends AISDKError {
11
+ private readonly [symbol] = true; // used in isInstance
12
+
13
+ /**
14
+ * Provider-defined error type, when supplied by the provider.
15
+ */
16
+ readonly type?: string;
17
+
18
+ /**
19
+ * Provider-defined error code, when supplied by the provider.
20
+ */
21
+ readonly code?: string | number;
22
+
23
+ /**
24
+ * HTTP-equivalent status code, when supplied by or inferable from the
25
+ * provider error metadata.
26
+ */
27
+ readonly statusCode?: number;
28
+
29
+ /**
30
+ * Whether retrying the model call may succeed.
31
+ */
32
+ readonly isRetryable: boolean;
33
+
34
+ /**
35
+ * Original provider error payload.
36
+ */
37
+ readonly data?: unknown;
38
+
39
+ constructor({
40
+ message,
41
+ type,
42
+ code,
43
+ statusCode,
44
+ isRetryable = isRetryableStatusCode(statusCode),
45
+ data,
46
+ cause,
47
+ }: {
48
+ message: string;
49
+ type?: string;
50
+ code?: string | number;
51
+ statusCode?: number;
52
+ isRetryable?: boolean;
53
+ data?: unknown;
54
+ cause?: unknown;
55
+ }) {
56
+ super({ name, message, cause });
57
+
58
+ this.type = type;
59
+ this.code = code;
60
+ this.statusCode = statusCode;
61
+ this.isRetryable = isRetryable;
62
+ this.data = data;
63
+ }
64
+
65
+ static isInstance(error: unknown): error is StreamProviderError {
66
+ return AISDKError.hasMarker(error, marker);
67
+ }
68
+ }
69
+
70
+ function isRetryableStatusCode(statusCode: number | undefined): boolean {
71
+ return (
72
+ statusCode != null &&
73
+ (statusCode === 408 ||
74
+ statusCode === 409 ||
75
+ statusCode === 429 ||
76
+ statusCode >= 500)
77
+ );
78
+ }
@@ -141,6 +141,9 @@ export function executeToolsFromStream<
141
141
  type: 'tool-approval-request',
142
142
  approvalId,
143
143
  toolCall: chunk,
144
+ ...(toolApprovalStatus.reason != null
145
+ ? { reason: toolApprovalStatus.reason }
146
+ : {}),
144
147
  ...(signature != null ? { signature } : {}),
145
148
  });
146
149
 
@@ -175,7 +175,8 @@ export interface GenerateTextResult<
175
175
  * The generated output according to the `output` specification.
176
176
  *
177
177
  * @throws {NoOutputGeneratedError} When no output is available, for example
178
- * when the final step does not finish with a `stop` reason.
178
+ * when the final step finishes with a `tool-calls` reason, or when it
179
+ * contains no text and does not finish with a `stop` reason.
179
180
  */
180
181
  readonly output: InferCompleteOutput<OUTPUT>;
181
182
  }
@@ -1201,6 +1201,9 @@ export async function generateText<
1201
1201
  type: 'tool-approval-request',
1202
1202
  approvalId,
1203
1203
  toolCall,
1204
+ ...(toolApprovalStatus.reason != null
1205
+ ? { reason: toolApprovalStatus.reason }
1206
+ : {}),
1204
1207
  ...(signature != null ? { signature } : {}),
1205
1208
  };
1206
1209
  blockedToolCallIds.add(toolCall.toolCallId);
@@ -1538,9 +1541,13 @@ export async function generateText<
1538
1541
  callbacks: [onEnd, telemetryDispatcher.onEnd],
1539
1542
  });
1540
1543
 
1541
- // parse output only if the last step was finished with "stop":
1544
+ // parse output for stop responses and non-empty responses that are not
1545
+ // tool calls:
1542
1546
  let resolvedOutput;
1543
- if (lastStep.finishReason === 'stop') {
1547
+ if (
1548
+ lastStep.finishReason === 'stop' ||
1549
+ (lastStep.finishReason !== 'tool-calls' && lastStep.text.length > 0)
1550
+ ) {
1544
1551
  const outputSpecification = output ?? text();
1545
1552
  resolvedOutput = await outputSpecification.parseCompleteOutput(
1546
1553
  { text: lastStep.text },
@@ -20,6 +20,7 @@ import { getOwn } from '../util/get-own';
20
20
  import type { Instructions, Prompt } from '../prompt';
21
21
  import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt';
22
22
  import type { LanguageModelCallOptions } from '../prompt/language-model-call-options';
23
+ import { normalizeStreamProviderError } from '../prompt/normalize-stream-provider-error';
23
24
  import { prepareToolChoice } from '../prompt/prepare-tool-choice';
24
25
  import { prepareTools } from '../prompt/prepare-tools';
25
26
  import { standardizePrompt } from '../prompt/standardize-prompt';
@@ -447,6 +448,13 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
447
448
  }
448
449
 
449
450
  switch (chunk.type) {
451
+ case 'error':
452
+ controller.enqueue({
453
+ type: 'error',
454
+ error: normalizeStreamProviderError(chunk.error),
455
+ });
456
+ break;
457
+
450
458
  case 'text-start':
451
459
  upsertTextContentPart({
452
460
  content: modelCallContent,
@@ -133,6 +133,7 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
133
133
  type: 'tool-approval-request',
134
134
  approvalId: part.approvalId,
135
135
  toolCallId: part.toolCall.toolCallId,
136
+ ...(part.reason != null ? { reason: part.reason } : {}),
136
137
  isAutomatic: part.isAutomatic,
137
138
  ...(part.signature != null ? { signature: part.signature } : {}),
138
139
  });
@@ -19,6 +19,8 @@ import type { TypedToolCall } from './tool-call';
19
19
  * - 'user-approval': The tool requires user approval.
20
20
  *
21
21
  * In addition to the string statuses, you can also use object statuses with a reason property.
22
+ * For approved and denied statuses, the reason is emitted on the approval response.
23
+ * For user-approval statuses, the reason is emitted on the approval request so it can be shown to the approver.
22
24
  *
23
25
  * `undefined` is treated as the `not-applicable` status.
24
26
  */
@@ -31,7 +33,7 @@ export type ToolApprovalStatus =
31
33
  | { type: 'not-applicable'; reason?: never }
32
34
  | { type: 'approved'; reason?: string }
33
35
  | { type: 'denied'; reason?: string }
34
- | { type: 'user-approval'; reason?: never };
36
+ | { type: 'user-approval'; reason?: string };
35
37
 
36
38
  /**
37
39
  * Function that is called to determine if the tool needs approval before it can be executed.
@@ -107,6 +109,8 @@ export type GenericToolApprovalFunction<
107
109
  * - 'user-approval': The tool requires user approval.
108
110
  *
109
111
  * In addition to the string statuses, you can also use object statuses with a reason property.
112
+ * For approved and denied statuses, the reason is emitted on the approval response.
113
+ * For user-approval statuses, the reason is emitted on the approval request so it can be shown to the approver.
110
114
  */
111
115
  export type ToolApprovalConfiguration<
112
116
  TOOLS extends ToolSet,
@@ -19,6 +19,11 @@ export type ToolApprovalRequestOutput<TOOLS extends ToolSet> = {
19
19
  */
20
20
  toolCall: TypedToolCall<TOOLS>;
21
21
 
22
+ /**
23
+ * Reason why the tool call requires approval.
24
+ */
25
+ reason?: string;
26
+
22
27
  /**
23
28
  * Flag indicating whether the tool was automatically approved or denied.
24
29
  *
package/src/index.ts CHANGED
@@ -2,7 +2,13 @@
2
2
  import './global';
3
3
 
4
4
  // re-exports:
5
- export { createGateway, gateway, type GatewayModelId } from '@ai-sdk/gateway';
5
+ export {
6
+ createGateway,
7
+ gateway,
8
+ type GatewayAsyncJobMetadata,
9
+ type GatewayModelId,
10
+ type GatewayProviderMetadata,
11
+ } from '@ai-sdk/gateway';
6
12
  export {
7
13
  asSchema,
8
14
  createIdGenerator,