ai 7.0.92 → 7.0.93

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 (34) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.d.ts +176 -152
  3. package/dist/index.js +132 -16
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.js +2 -2
  6. package/dist/internal/index.js.map +1 -1
  7. package/docs/02-foundations/02-providers-and-models.mdx +1 -0
  8. package/docs/03-agents/04-loop-control.mdx +5 -3
  9. package/docs/03-agents/07-workflow-agent.mdx +27 -6
  10. package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +15 -3
  11. package/docs/03-ai-sdk-core/16-mcp-tools.mdx +64 -1
  12. package/docs/03-ai-sdk-core/35-image-generation.mdx +7 -0
  13. package/docs/03-ai-sdk-core/36-transcription.mdx +36 -35
  14. package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +36 -0
  15. package/docs/04-ai-sdk-ui/20-streaming-data.mdx +11 -6
  16. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +14 -0
  17. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +15 -1
  18. package/docs/07-reference/01-ai-sdk-core/28-output.mdx +27 -1
  19. package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
  20. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +4 -0
  21. package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +6 -1
  22. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +42 -28
  23. package/docs/07-reference/05-ai-sdk-errors/ai-no-image-generated-error.mdx +7 -0
  24. package/package.json +6 -6
  25. package/src/agent/tool-loop-agent-settings.ts +15 -0
  26. package/src/embed/embed-many.ts +27 -2
  27. package/src/error/no-image-generated-error.ts +9 -0
  28. package/src/generate-image/generate-image.ts +1 -1
  29. package/src/generate-text/output.ts +111 -1
  30. package/src/ui/chat.ts +1 -1
  31. package/src/ui/convert-to-model-messages.ts +8 -2
  32. package/src/ui/validate-ui-messages.ts +14 -0
  33. package/src/util/data-url.ts +1 -1
  34. package/src/util/merge-abort-signals.ts +1 -1
@@ -99,7 +99,7 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
99
99
  type: 'StopCondition | StopCondition[]',
100
100
  isOptional: true,
101
101
  description:
102
- 'Default stop condition for the agent loop. Per-stream values override this default. Use `isLoopFinished()` to let the agent run until all tool calls have completed, but beware of potential runaway loops. See https://ai-sdk.dev/v7/docs/reference/ai-sdk-core/loop-finished#isloopfinished.',
102
+ 'Default stop condition for the agent loop. When omitted, WorkflowAgent has no maximum step count and continues until natural completion. Use `isStepCount()` to bound execution. Per-stream values override this default.',
103
103
  },
104
104
  {
105
105
  name: 'activeTools',
@@ -268,24 +268,24 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
268
268
  },
269
269
  {
270
270
  name: 'onToolExecutionStart',
271
- type: 'WorkflowAgentonToolExecutionStartCallback',
271
+ type: 'WorkflowAgentOnToolExecutionStartCallback',
272
272
  isOptional: true,
273
273
  description:
274
274
  "Callback called right before a tool's execute function runs. If also specified in `stream()`, both callbacks fire (constructor first). Experimental (can break in patch releases).",
275
275
  properties: [
276
276
  {
277
- type: 'ToolExecutionStartEvent',
277
+ type: 'WorkflowAgentToolExecutionStartEvent',
278
278
  parameters: [
279
279
  {
280
- name: 'callId',
281
- type: 'string',
280
+ name: 'toolCall',
281
+ type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
282
282
  description:
283
- 'Unique identifier for this generation call, used to correlate events.',
283
+ 'The tool call being executed. For concrete tool sets, the tool name and input are correlated.',
284
284
  },
285
285
  {
286
- name: 'toolCall',
287
- type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
288
- description: 'The tool call being executed.',
286
+ name: 'stepNumber',
287
+ type: 'number',
288
+ description: 'The current step number, starting at zero.',
289
289
  },
290
290
  {
291
291
  name: 'messages',
@@ -295,9 +295,9 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
295
295
  },
296
296
  {
297
297
  name: 'toolContext',
298
- type: 'InferToolContext<TOOLS[toolName]>',
298
+ type: 'InferToolContext<TOOLS[NAME]> | undefined',
299
299
  description:
300
- 'Tool-specific context object for the tool call that is about to execute. Narrowed to the context type of the individual tool, not the entire tool set.',
300
+ 'The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.',
301
301
  },
302
302
  ],
303
303
  },
@@ -305,24 +305,24 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
305
305
  },
306
306
  {
307
307
  name: 'onToolExecutionEnd',
308
- type: 'WorkflowAgentonToolExecutionEndCallback',
308
+ type: 'WorkflowAgentOnToolExecutionEndCallback',
309
309
  isOptional: true,
310
310
  description:
311
- "Callback called right after a tool's execute function completes or errors. The `toolOutput` field is a discriminated union: check `toolOutput.type` to determine whether the result is `'tool-result'` or `'tool-error'`. If also specified in `stream()`, both callbacks fire (constructor first). Experimental (can break in patch releases).",
311
+ "Callback called right after a tool's execute function completes or errors. Check `success` to determine whether `output` or `error` is available. If also specified in `stream()`, both callbacks fire (constructor first). Experimental (can break in patch releases).",
312
312
  properties: [
313
313
  {
314
- type: 'ToolExecutionEndEvent',
314
+ type: 'WorkflowAgentToolExecutionEndEvent',
315
315
  parameters: [
316
316
  {
317
- name: 'callId',
318
- type: 'string',
317
+ name: 'toolCall',
318
+ type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
319
319
  description:
320
- 'Unique identifier for this generation call, used to correlate events.',
320
+ 'The tool call that was executed. For concrete tool sets, the tool name and input are correlated.',
321
321
  },
322
322
  {
323
- name: 'toolCall',
324
- type: '{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }',
325
- description: 'The tool call that was executed.',
323
+ name: 'stepNumber',
324
+ type: 'number',
325
+ description: 'The current step number, starting at zero.',
326
326
  },
327
327
  {
328
328
  name: 'durationMs',
@@ -338,15 +338,29 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
338
338
  },
339
339
  {
340
340
  name: 'toolContext',
341
- type: 'InferToolContext<TOOLS[toolName]>',
341
+ type: 'InferToolContext<TOOLS[NAME]> | undefined',
342
+ description:
343
+ 'The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.',
344
+ },
345
+ {
346
+ name: 'success',
347
+ type: 'boolean',
348
+ description:
349
+ 'Whether the tool execution succeeded. Discriminates between the output and error event variants.',
350
+ },
351
+ {
352
+ name: 'output',
353
+ type: 'InferToolOutput<TOOLS[NAME]>',
354
+ isOptional: true,
342
355
  description:
343
- 'Tool-specific context object for the tool call that just completed. Narrowed to the context type of the individual tool, not the entire tool set.',
356
+ 'The tool output. Available when `success` is `true` and correlated with the configured tool.',
344
357
  },
345
358
  {
346
- name: 'toolOutput',
347
- type: 'ToolOutput<TOOLS>',
359
+ name: 'error',
360
+ type: 'unknown',
361
+ isOptional: true,
348
362
  description:
349
- "Discriminated union representing the tool execution result. When `type` is `'tool-result'`, the `output` field contains the tool's return value. When `type` is `'tool-error'`, the `error` field contains the error.",
363
+ 'The tool execution error. Available when `success` is `false`.',
350
364
  },
351
365
  ],
352
366
  },
@@ -509,7 +523,7 @@ const result = await agent.stream({
509
523
  name: 'stopWhen',
510
524
  type: 'StopCondition | StopCondition[]',
511
525
  isOptional: true,
512
- description: 'Condition(s) for ending the agent loop. Use `isLoopFinished()` to let the agent run until all tool calls have completed, but beware of potential runaway loops. See https://ai-sdk.dev/v7/docs/reference/ai-sdk-core/loop-finished#isloopfinished.',
526
+ description: 'Condition(s) for ending the agent loop. When omitted and no constructor-level condition is configured, WorkflowAgent has no maximum step count and continues until natural completion. Use `isStepCount()` to bound execution.',
513
527
  },
514
528
 
515
529
  {
@@ -644,14 +658,14 @@ const result = await agent.stream({
644
658
  },
645
659
  {
646
660
  name: 'onToolExecutionStart',
647
- type: 'WorkflowAgentonToolExecutionStartCallback',
661
+ type: 'WorkflowAgentOnToolExecutionStartCallback',
648
662
  isOptional: true,
649
663
  description:
650
664
  'Per-call onToolExecutionStart callback. If also specified in the constructor, both fire (constructor first).',
651
665
  },
652
666
  {
653
667
  name: 'onToolExecutionEnd',
654
- type: 'WorkflowAgentonToolExecutionEndCallback',
668
+ type: 'WorkflowAgentOnToolExecutionEndCallback',
655
669
  isOptional: true,
656
670
  description:
657
671
  'Per-call onToolExecutionEnd callback. If also specified in the constructor, both fire (constructor first).',
@@ -14,6 +14,7 @@ It can arise due to the following reasons:
14
14
  ## Properties
15
15
 
16
16
  - `message`: The error message (optional, defaults to `'No image generated.'`).
17
+ - `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage (optional).
17
18
  - `responses`: Metadata about the image model responses, including timestamp, model, and headers (optional).
18
19
  - `cause`: The cause of the error. You can use this for more detailed error handling (optional).
19
20
 
@@ -31,6 +32,12 @@ try {
31
32
  console.log('NoImageGeneratedError');
32
33
  console.log('Cause:', error.cause);
33
34
  console.log('Responses:', error.responses);
35
+
36
+ for (const call of error.calls ?? []) {
37
+ console.log('Provider metadata:', call.providerMetadata);
38
+ console.log('Warnings:', call.warnings);
39
+ console.log('Usage:', call.usage);
40
+ }
34
41
  }
35
42
  }
36
43
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.92",
3
+ "version": "7.0.93",
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,18 +42,18 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.74",
45
+ "@ai-sdk/gateway": "4.0.75",
46
46
  "@ai-sdk/provider": "4.0.10",
47
47
  "@ai-sdk/provider-utils": "5.0.36"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.74",
50
+ "@ai-sdk/amazon-bedrock": "5.0.75",
51
51
  "@ai-sdk/deepseek": "3.0.39",
52
- "@ai-sdk/google": "4.0.63",
52
+ "@ai-sdk/google": "4.0.64",
53
53
  "@ai-sdk/groq": "4.0.37",
54
- "@ai-sdk/huggingface": "2.0.43",
54
+ "@ai-sdk/huggingface": "2.0.44",
55
55
  "@ai-sdk/moonshotai": "3.0.45",
56
- "@ai-sdk/openai": "4.0.58",
56
+ "@ai-sdk/openai": "4.0.59",
57
57
  "@ai-sdk/test-server": "2.0.1",
58
58
  "@ai-sdk/xai": "4.0.54",
59
59
  "@edge-runtime/vm": "^5.0.0",
@@ -17,6 +17,10 @@ import type {
17
17
  GenerateTextOnStepStartCallback,
18
18
  } from '../generate-text/generate-text-events';
19
19
  import type { GenerateTextInclude } from '../generate-text/generate-text';
20
+ import type {
21
+ OnLanguageModelCallEndCallback,
22
+ OnLanguageModelCallStartCallback,
23
+ } from '../generate-text/language-model-events';
20
24
  import type { Output } from '../generate-text/output';
21
25
  import type { PrepareStepFunction } from '../generate-text/prepare-step';
22
26
  import type { StopCondition } from '../generate-text/stop-condition';
@@ -212,6 +216,17 @@ export type ToolLoopAgentSettings<
212
216
  NoInfer<OUTPUT>
213
217
  >;
214
218
 
219
+ /**
220
+ * Callback that is called immediately before the provider model call begins.
221
+ */
222
+ onLanguageModelCallStart?: OnLanguageModelCallStartCallback;
223
+
224
+ /**
225
+ * Callback that is called after the model response has been normalized and parsed,
226
+ * but before any client-side tool execution begins.
227
+ */
228
+ onLanguageModelCallEnd?: OnLanguageModelCallEndCallback<NoInfer<TOOLS>>;
229
+
215
230
  /**
216
231
  * Callback that is called before each tool execution begins.
217
232
  */
@@ -1,3 +1,4 @@
1
+ import { InvalidResponseDataError } from '@ai-sdk/provider';
1
2
  import {
2
3
  createIdGenerator,
3
4
  withUserAgentSuffix,
@@ -264,6 +265,8 @@ export async function embedMany({
264
265
  };
265
266
  });
266
267
 
268
+ validateEmbeddingCount({ embeddings, values });
269
+
267
270
  logWarnings({
268
271
  warnings,
269
272
  provider: model.provider,
@@ -325,8 +328,8 @@ export async function embedMany({
325
328
 
326
329
  for (const parallelChunk of parallelChunks) {
327
330
  const results = await Promise.all(
328
- parallelChunk.map(chunk => {
329
- return retry(async () => {
331
+ parallelChunk.map(async chunk => {
332
+ const result = await retry(async () => {
330
333
  const embedCallId = generateCallId();
331
334
 
332
335
  await notify({
@@ -373,6 +376,13 @@ export async function embedMany({
373
376
  response: modelResponse.response,
374
377
  };
375
378
  });
379
+
380
+ validateEmbeddingCount({
381
+ embeddings: result.embeddings,
382
+ values: chunk,
383
+ });
384
+
385
+ return result;
376
386
  }),
377
387
  );
378
388
 
@@ -436,6 +446,21 @@ export async function embedMany({
436
446
  });
437
447
  }
438
448
 
449
+ function validateEmbeddingCount({
450
+ embeddings,
451
+ values,
452
+ }: {
453
+ embeddings: Array<Embedding>;
454
+ values: Array<string>;
455
+ }) {
456
+ if (embeddings.length !== values.length) {
457
+ throw new InvalidResponseDataError({
458
+ data: embeddings,
459
+ message: `Expected ${values.length} embeddings, but received ${embeddings.length}.`,
460
+ });
461
+ }
462
+ }
463
+
439
464
  const textEncoder = new TextEncoder();
440
465
 
441
466
  function splitByEmbeddingLimits({
@@ -1,4 +1,5 @@
1
1
  import { AISDKError } from '@ai-sdk/provider';
2
+ import type { GenerateImageCall } from '../generate-image/generate-image-result';
2
3
  import type { ImageModelResponseMetadata } from '../types/image-model-response-metadata';
3
4
 
4
5
  const name = 'AI_NoImageGeneratedError';
@@ -14,6 +15,11 @@ const symbol = Symbol.for(marker);
14
15
  export class NoImageGeneratedError extends AISDKError {
15
16
  private readonly [symbol] = true; // used in isInstance
16
17
 
18
+ /**
19
+ * The results of the underlying image model calls.
20
+ */
21
+ readonly calls: Array<GenerateImageCall> | undefined;
22
+
17
23
  /**
18
24
  * The response metadata for each call.
19
25
  */
@@ -22,14 +28,17 @@ export class NoImageGeneratedError extends AISDKError {
22
28
  constructor({
23
29
  message = 'No image generated.',
24
30
  cause,
31
+ calls,
25
32
  responses,
26
33
  }: {
27
34
  message?: string;
28
35
  cause?: Error;
36
+ calls?: Array<GenerateImageCall>;
29
37
  responses?: Array<ImageModelResponseMetadata>;
30
38
  }) {
31
39
  super({ name, message, cause });
32
40
 
41
+ this.calls = calls;
33
42
  this.responses = responses;
34
43
  }
35
44
 
@@ -295,7 +295,7 @@ export async function generateImage({
295
295
  logWarnings({ warnings, provider: model.provider, model: model.modelId });
296
296
 
297
297
  if (!images.length) {
298
- throw new NoImageGeneratedError({ responses });
298
+ throw new NoImageGeneratedError({ calls, responses });
299
299
  }
300
300
 
301
301
  return new DefaultGenerateImageResult({
@@ -11,6 +11,7 @@ import {
11
11
  safeValidateTypes,
12
12
  type FlexibleSchema,
13
13
  } from '@ai-sdk/provider-utils';
14
+ import { InvalidArgumentError } from '../error/invalid-argument-error';
14
15
  import { NoObjectGeneratedError } from '../error/no-object-generated-error';
15
16
  import type { FinishReason } from '../types/language-model';
16
17
  import type { LanguageModelResponseMetadata } from '../types/language-model-response-metadata';
@@ -189,6 +190,8 @@ export const object = <OBJECT>({
189
190
  * When the model generates a text response, it will return an array of elements.
190
191
  *
191
192
  * @param element - The schema of the array elements to generate.
193
+ * @param minItems - Optional minimum number of elements to generate.
194
+ * @param maxItems - Optional maximum number of elements to generate.
192
195
  * @param name - Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name.
193
196
  * @param description - Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description.
194
197
  *
@@ -196,10 +199,20 @@ export const object = <OBJECT>({
196
199
  */
197
200
  export const array = <ELEMENT>({
198
201
  element: inputElementSchema,
202
+ minItems,
203
+ maxItems,
199
204
  name,
200
205
  description,
201
206
  }: {
202
207
  element: FlexibleSchema<ELEMENT>;
208
+ /**
209
+ * Optional minimum number of elements to generate.
210
+ */
211
+ minItems?: number;
212
+ /**
213
+ * Optional maximum number of elements to generate.
214
+ */
215
+ maxItems?: number;
203
216
  /**
204
217
  * Optional name of the output that should be generated.
205
218
  * Used by some providers for additional LLM guidance, e.g. via tool or schema name.
@@ -211,6 +224,17 @@ export const array = <ELEMENT>({
211
224
  */
212
225
  description?: string;
213
226
  }): Output<Array<ELEMENT>, Array<ELEMENT>, ELEMENT> => {
227
+ validateArrayBound({ name: 'minItems', value: minItems });
228
+ validateArrayBound({ name: 'maxItems', value: maxItems });
229
+
230
+ if (minItems != null && maxItems != null && minItems > maxItems) {
231
+ throw new InvalidArgumentError({
232
+ parameter: 'minItems',
233
+ value: minItems,
234
+ message: 'minItems must be less than or equal to maxItems',
235
+ });
236
+ }
237
+
214
238
  const elementSchema = asSchema(inputElementSchema);
215
239
 
216
240
  return {
@@ -236,7 +260,12 @@ export const array = <ELEMENT>({
236
260
  ...($defs != null && { $defs }),
237
261
  type: 'object',
238
262
  properties: {
239
- elements: { type: 'array', items: itemSchema },
263
+ elements: {
264
+ type: 'array',
265
+ items: itemSchema,
266
+ ...(minItems != null && { minItems }),
267
+ ...(maxItems != null && { maxItems }),
268
+ },
240
269
  },
241
270
  required: ['elements'],
242
271
  additionalProperties: false,
@@ -288,6 +317,23 @@ export const array = <ELEMENT>({
288
317
  });
289
318
  }
290
319
 
320
+ const lengthValidationError = getArrayLengthValidationError({
321
+ value: outerValue.elements,
322
+ minItems,
323
+ maxItems,
324
+ });
325
+
326
+ if (lengthValidationError != null) {
327
+ throw new NoObjectGeneratedError({
328
+ message: 'No object generated: response did not match schema.',
329
+ cause: lengthValidationError,
330
+ text,
331
+ response: context.response,
332
+ usage: context.usage,
333
+ finishReason: context.finishReason,
334
+ });
335
+ }
336
+
291
337
  const validatedElements: Array<ELEMENT> = [];
292
338
  for (const element of outerValue.elements) {
293
339
  const validationResult = await safeValidateTypes({
@@ -372,6 +418,16 @@ export const array = <ELEMENT>({
372
418
  publishedElements < partialOutput.length;
373
419
  publishedElements++
374
420
  ) {
421
+ if (maxItems != null && publishedElements >= maxItems) {
422
+ controller.error(
423
+ getArrayLengthValidationError({
424
+ value: partialOutput,
425
+ maxItems,
426
+ }),
427
+ );
428
+ return;
429
+ }
430
+
375
431
  controller.enqueue(partialOutput[publishedElements]);
376
432
  }
377
433
  }
@@ -381,6 +437,60 @@ export const array = <ELEMENT>({
381
437
  };
382
438
  };
383
439
 
440
+ function validateArrayBound({
441
+ name,
442
+ value,
443
+ }: {
444
+ name: 'minItems' | 'maxItems';
445
+ value: number | undefined;
446
+ }) {
447
+ if (value == null) {
448
+ return;
449
+ }
450
+
451
+ if (!Number.isInteger(value)) {
452
+ throw new InvalidArgumentError({
453
+ parameter: name,
454
+ value,
455
+ message: `${name} must be an integer`,
456
+ });
457
+ }
458
+
459
+ if (value < 0) {
460
+ throw new InvalidArgumentError({
461
+ parameter: name,
462
+ value,
463
+ message: `${name} must be greater than or equal to 0`,
464
+ });
465
+ }
466
+ }
467
+
468
+ function getArrayLengthValidationError({
469
+ value,
470
+ minItems,
471
+ maxItems,
472
+ }: {
473
+ value: Array<unknown>;
474
+ minItems?: number;
475
+ maxItems?: number;
476
+ }): TypeValidationError | undefined {
477
+ if (minItems != null && value.length < minItems) {
478
+ return new TypeValidationError({
479
+ value,
480
+ cause: `elements array must contain at least ${minItems} items`,
481
+ });
482
+ }
483
+
484
+ if (maxItems != null && value.length > maxItems) {
485
+ return new TypeValidationError({
486
+ value,
487
+ cause: `elements array must contain at most ${maxItems} items`,
488
+ });
489
+ }
490
+
491
+ return undefined;
492
+ }
493
+
384
494
  /**
385
495
  * Output specification for choice generation.
386
496
  * When the model generates a text response, it will return a one of the choice options.
package/src/ui/chat.ts CHANGED
@@ -420,8 +420,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
420
420
 
421
421
  // update the message with the new content
422
422
  this.state.replaceMessage(messageIndex, {
423
- ...uiMessage,
424
423
  id: message.messageId,
424
+ ...uiMessage,
425
425
  role: uiMessage.role ?? 'user',
426
426
  metadata: message.metadata,
427
427
  } as UI_MESSAGE);
@@ -212,6 +212,12 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
212
212
  const toolName = getToolName(part);
213
213
 
214
214
  if (part.state !== 'input-streaming') {
215
+ const callProviderMetadata =
216
+ part.callProviderMetadata ??
217
+ (part.state === 'output-error'
218
+ ? part.resultProviderMetadata
219
+ : undefined);
220
+
215
221
  content.push({
216
222
  type: 'tool-call' as const,
217
223
  toolCallId: part.toolCallId,
@@ -222,8 +228,8 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
222
228
  ('rawInput' in part ? part.rawInput : undefined))
223
229
  : part.input,
224
230
  providerExecuted: part.providerExecuted,
225
- ...(part.callProviderMetadata != null
226
- ? { providerOptions: part.callProviderMetadata }
231
+ ...(callProviderMetadata != null
232
+ ? { providerOptions: callProviderMetadata }
227
233
  : {}),
228
234
  });
229
235
 
@@ -117,6 +117,7 @@ const uiMessagesSchema = lazySchema(() =>
117
117
  type: z.literal('dynamic-tool'),
118
118
  toolName: z.string(),
119
119
  toolCallId: z.string(),
120
+ title: z.string().optional(),
120
121
  toolMetadata: toolMetadataSchema.optional(),
121
122
  state: z.literal('input-streaming'),
122
123
  input: z.unknown().optional(),
@@ -130,6 +131,7 @@ const uiMessagesSchema = lazySchema(() =>
130
131
  type: z.literal('dynamic-tool'),
131
132
  toolName: z.string(),
132
133
  toolCallId: z.string(),
134
+ title: z.string().optional(),
133
135
  toolMetadata: toolMetadataSchema.optional(),
134
136
  state: z.literal('input-available'),
135
137
  input: z.unknown(),
@@ -143,6 +145,7 @@ const uiMessagesSchema = lazySchema(() =>
143
145
  type: z.literal('dynamic-tool'),
144
146
  toolName: z.string(),
145
147
  toolCallId: z.string(),
148
+ title: z.string().optional(),
146
149
  toolMetadata: toolMetadataSchema.optional(),
147
150
  state: z.literal('approval-requested'),
148
151
  input: z.unknown(),
@@ -164,6 +167,7 @@ const uiMessagesSchema = lazySchema(() =>
164
167
  type: z.literal('dynamic-tool'),
165
168
  toolName: z.string(),
166
169
  toolCallId: z.string(),
170
+ title: z.string().optional(),
167
171
  toolMetadata: toolMetadataSchema.optional(),
168
172
  state: z.literal('approval-responded'),
169
173
  input: z.unknown(),
@@ -185,6 +189,7 @@ const uiMessagesSchema = lazySchema(() =>
185
189
  type: z.literal('dynamic-tool'),
186
190
  toolName: z.string(),
187
191
  toolCallId: z.string(),
192
+ title: z.string().optional(),
188
193
  toolMetadata: toolMetadataSchema.optional(),
189
194
  state: z.literal('output-available'),
190
195
  input: z.unknown(),
@@ -210,6 +215,7 @@ const uiMessagesSchema = lazySchema(() =>
210
215
  type: z.literal('dynamic-tool'),
211
216
  toolName: z.string(),
212
217
  toolCallId: z.string(),
218
+ title: z.string().optional(),
213
219
  toolMetadata: toolMetadataSchema.optional(),
214
220
  state: z.literal('output-error'),
215
221
  input: z.unknown().optional(),
@@ -235,6 +241,7 @@ const uiMessagesSchema = lazySchema(() =>
235
241
  type: z.literal('dynamic-tool'),
236
242
  toolName: z.string(),
237
243
  toolCallId: z.string(),
244
+ title: z.string().optional(),
238
245
  toolMetadata: toolMetadataSchema.optional(),
239
246
  state: z.literal('output-denied'),
240
247
  input: z.unknown(),
@@ -255,6 +262,7 @@ const uiMessagesSchema = lazySchema(() =>
255
262
  z.object({
256
263
  type: z.string().startsWith('tool-'),
257
264
  toolCallId: z.string(),
265
+ title: z.string().optional(),
258
266
  toolMetadata: toolMetadataSchema.optional(),
259
267
  state: z.literal('input-streaming'),
260
268
  providerExecuted: z.boolean().optional(),
@@ -267,6 +275,7 @@ const uiMessagesSchema = lazySchema(() =>
267
275
  z.object({
268
276
  type: z.string().startsWith('tool-'),
269
277
  toolCallId: z.string(),
278
+ title: z.string().optional(),
270
279
  toolMetadata: toolMetadataSchema.optional(),
271
280
  state: z.literal('input-available'),
272
281
  providerExecuted: z.boolean().optional(),
@@ -279,6 +288,7 @@ const uiMessagesSchema = lazySchema(() =>
279
288
  z.object({
280
289
  type: z.string().startsWith('tool-'),
281
290
  toolCallId: z.string(),
291
+ title: z.string().optional(),
282
292
  toolMetadata: toolMetadataSchema.optional(),
283
293
  state: z.literal('approval-requested'),
284
294
  input: z.unknown(),
@@ -299,6 +309,7 @@ const uiMessagesSchema = lazySchema(() =>
299
309
  z.object({
300
310
  type: z.string().startsWith('tool-'),
301
311
  toolCallId: z.string(),
312
+ title: z.string().optional(),
302
313
  toolMetadata: toolMetadataSchema.optional(),
303
314
  state: z.literal('approval-responded'),
304
315
  input: z.unknown(),
@@ -319,6 +330,7 @@ const uiMessagesSchema = lazySchema(() =>
319
330
  z.object({
320
331
  type: z.string().startsWith('tool-'),
321
332
  toolCallId: z.string(),
333
+ title: z.string().optional(),
322
334
  toolMetadata: toolMetadataSchema.optional(),
323
335
  state: z.literal('output-available'),
324
336
  providerExecuted: z.boolean().optional(),
@@ -343,6 +355,7 @@ const uiMessagesSchema = lazySchema(() =>
343
355
  z.object({
344
356
  type: z.string().startsWith('tool-'),
345
357
  toolCallId: z.string(),
358
+ title: z.string().optional(),
346
359
  toolMetadata: toolMetadataSchema.optional(),
347
360
  state: z.literal('output-error'),
348
361
  providerExecuted: z.boolean().optional(),
@@ -367,6 +380,7 @@ const uiMessagesSchema = lazySchema(() =>
367
380
  z.object({
368
381
  type: z.string().startsWith('tool-'),
369
382
  toolCallId: z.string(),
383
+ title: z.string().optional(),
370
384
  toolMetadata: toolMetadataSchema.optional(),
371
385
  state: z.literal('output-denied'),
372
386
  providerExecuted: z.boolean().optional(),
@@ -10,7 +10,7 @@ export function getTextFromDataUrl(dataUrl: string): string {
10
10
  }
11
11
 
12
12
  try {
13
- return window.atob(base64Content);
13
+ return globalThis.atob(base64Content);
14
14
  } catch {
15
15
  throw new Error(`Error decoding data URL`);
16
16
  }