ai 7.0.92 → 7.0.94

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 (44) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/index.d.ts +239 -214
  3. package/dist/index.js +391 -138
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +3 -2
  6. package/dist/internal/index.js +8 -5
  7. package/dist/internal/index.js.map +1 -1
  8. package/docs/02-foundations/02-providers-and-models.mdx +1 -1
  9. package/docs/03-agents/04-loop-control.mdx +5 -3
  10. package/docs/03-agents/07-workflow-agent.mdx +27 -6
  11. package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +15 -3
  12. package/docs/03-ai-sdk-core/16-mcp-tools.mdx +64 -1
  13. package/docs/03-ai-sdk-core/35-image-generation.mdx +9 -0
  14. package/docs/03-ai-sdk-core/36-transcription.mdx +36 -35
  15. package/docs/03-ai-sdk-core/37-speech.mdx +0 -17
  16. package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +36 -0
  17. package/docs/04-ai-sdk-ui/20-streaming-data.mdx +11 -6
  18. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +14 -0
  19. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +15 -1
  20. package/docs/07-reference/01-ai-sdk-core/10-generate-image.mdx +2 -1
  21. package/docs/07-reference/01-ai-sdk-core/28-output.mdx +27 -1
  22. package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
  23. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +4 -0
  24. package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +6 -1
  25. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +42 -28
  26. package/docs/07-reference/05-ai-sdk-errors/ai-no-image-generated-error.mdx +7 -0
  27. package/package.json +12 -12
  28. package/src/agent/tool-loop-agent-settings.ts +15 -0
  29. package/src/batch/batch-types.ts +76 -68
  30. package/src/batch/batch.ts +169 -101
  31. package/src/batch/index.ts +9 -7
  32. package/src/embed/embed-many.ts +27 -2
  33. package/src/error/no-image-generated-error.ts +9 -0
  34. package/src/generate-image/generate-image.ts +51 -10
  35. package/src/generate-text/output.ts +111 -1
  36. package/src/generate-text/stream-language-model-call.ts +82 -0
  37. package/src/generate-text/stream-text.ts +5 -1
  38. package/src/ui/chat.ts +1 -1
  39. package/src/ui/convert-to-model-messages.ts +8 -2
  40. package/src/ui/validate-ui-messages.ts +14 -0
  41. package/src/util/data-url.ts +1 -1
  42. package/src/util/merge-abort-signals.ts +1 -1
  43. package/src/util/prepare-retries.ts +7 -1
  44. package/src/util/retry-with-exponential-backoff.ts +9 -4
@@ -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.
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  getErrorMessage,
3
+ type LanguageModelV4Content,
3
4
  type LanguageModelV4Prompt,
4
5
  type LanguageModelV4StreamPart,
6
+ type LanguageModelV4ToolChoice,
5
7
  type SharedV4Headers,
6
8
  } from '@ai-sdk/provider';
7
9
  import {
@@ -40,6 +42,7 @@ import {
40
42
  import type { DownloadFunction } from '../util/download/download-function';
41
43
  import { notify } from '../util/notify';
42
44
  import { now as originalNow } from '../util/now';
45
+ import { ToolChoiceViolationError } from '../error';
43
46
  import { calculateTokensPerSecond } from './calculate-tokens-per-second';
44
47
  import type { ContentPart } from './content-part';
45
48
  import { DefaultGeneratedFileWithType } from './generated-file';
@@ -372,6 +375,7 @@ export async function streamLanguageModelCall<
372
375
  callId: effectiveCallId,
373
376
  provider: resolvedModel.provider,
374
377
  modelId: resolvedModel.modelId,
378
+ toolChoice: stepToolChoice,
375
379
  generateId,
376
380
  now,
377
381
  callStartTimestampMs,
@@ -398,6 +402,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
398
402
  callId,
399
403
  provider,
400
404
  modelId,
405
+ toolChoice,
401
406
  generateId,
402
407
  now,
403
408
  callStartTimestampMs,
@@ -411,6 +416,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
411
416
  callId: string;
412
417
  provider: string;
413
418
  modelId: string;
419
+ toolChoice: LanguageModelV4ToolChoice;
414
420
  generateId: IdGenerator;
415
421
  now: () => number;
416
422
  callStartTimestampMs: number;
@@ -420,8 +426,11 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
420
426
  // keep track of tool inputs for provider-side tool results
421
427
  const toolCallsByToolCallId = new Map<string, TypedToolCall<TOOLS>>();
422
428
  const modelCallContent: Array<ContentPart<TOOLS>> = [];
429
+ const rawModelCallContent: Array<LanguageModelV4Content> = [];
423
430
  const textPartIndexes = new Map<string, number>();
424
431
  const reasoningPartIndexes = new Map<string, number>();
432
+ const rawTextPartIndexes = new Map<string, number>();
433
+ const rawReasoningPartIndexes = new Map<string, number>();
425
434
  let responseId = generateId();
426
435
  let responseModelId = modelId;
427
436
  let timeToFirstOutputMs: number | undefined;
@@ -458,7 +467,9 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
458
467
  case 'text-start':
459
468
  upsertTextContentPart({
460
469
  content: modelCallContent,
470
+ rawContent: rawModelCallContent,
461
471
  partIndexes: textPartIndexes,
472
+ rawPartIndexes: rawTextPartIndexes,
462
473
  id: chunk.id,
463
474
  type: 'text',
464
475
  providerMetadata: chunk.providerMetadata,
@@ -469,7 +480,9 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
469
480
  case 'text-delta':
470
481
  upsertTextContentPart({
471
482
  content: modelCallContent,
483
+ rawContent: rawModelCallContent,
472
484
  partIndexes: textPartIndexes,
485
+ rawPartIndexes: rawTextPartIndexes,
473
486
  id: chunk.id,
474
487
  type: 'text',
475
488
  textDelta: chunk.delta,
@@ -486,19 +499,24 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
486
499
  case 'text-end':
487
500
  upsertTextContentPart({
488
501
  content: modelCallContent,
502
+ rawContent: rawModelCallContent,
489
503
  partIndexes: textPartIndexes,
504
+ rawPartIndexes: rawTextPartIndexes,
490
505
  id: chunk.id,
491
506
  type: 'text',
492
507
  providerMetadata: chunk.providerMetadata,
493
508
  });
494
509
  textPartIndexes.delete(chunk.id);
510
+ rawTextPartIndexes.delete(chunk.id);
495
511
  controller.enqueue(chunk);
496
512
  break;
497
513
 
498
514
  case 'reasoning-start':
499
515
  upsertTextContentPart({
500
516
  content: modelCallContent,
517
+ rawContent: rawModelCallContent,
501
518
  partIndexes: reasoningPartIndexes,
519
+ rawPartIndexes: rawReasoningPartIndexes,
502
520
  id: chunk.id,
503
521
  type: 'reasoning',
504
522
  providerMetadata: chunk.providerMetadata,
@@ -509,7 +527,9 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
509
527
  case 'reasoning-delta':
510
528
  upsertTextContentPart({
511
529
  content: modelCallContent,
530
+ rawContent: rawModelCallContent,
512
531
  partIndexes: reasoningPartIndexes,
532
+ rawPartIndexes: rawReasoningPartIndexes,
513
533
  id: chunk.id,
514
534
  type: 'reasoning',
515
535
  textDelta: chunk.delta,
@@ -526,12 +546,15 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
526
546
  case 'reasoning-end':
527
547
  upsertTextContentPart({
528
548
  content: modelCallContent,
549
+ rawContent: rawModelCallContent,
529
550
  partIndexes: reasoningPartIndexes,
551
+ rawPartIndexes: rawReasoningPartIndexes,
530
552
  id: chunk.id,
531
553
  type: 'reasoning',
532
554
  providerMetadata: chunk.providerMetadata,
533
555
  });
534
556
  reasoningPartIndexes.delete(chunk.id);
557
+ rawReasoningPartIndexes.delete(chunk.id);
535
558
  controller.enqueue(chunk);
536
559
  break;
537
560
 
@@ -552,6 +575,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
552
575
  ? { providerMetadata: chunk.providerMetadata }
553
576
  : {}),
554
577
  });
578
+ rawModelCallContent.push(chunk);
555
579
 
556
580
  controller.enqueue({
557
581
  type: chunk.type,
@@ -612,6 +636,9 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
612
636
  callbacks: onLanguageModelCallEnd,
613
637
  });
614
638
 
639
+ // Preserve the completed model call's usage, metadata, and
640
+ // performance even when response validation below surfaces a
641
+ // semantic error.
615
642
  controller.enqueue({
616
643
  type: 'model-call-end',
617
644
  finishReason: chunk.finishReason.unified,
@@ -620,10 +647,38 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
620
647
  providerMetadata: chunk.providerMetadata,
621
648
  performance,
622
649
  });
650
+
651
+ const enforcedToolChoice =
652
+ toolChoice.type === 'required' || toolChoice.type === 'tool'
653
+ ? toolChoice
654
+ : undefined;
655
+
656
+ if (
657
+ enforcedToolChoice != null &&
658
+ ![...toolCallsByToolCallId.values()].some(
659
+ toolCall =>
660
+ enforcedToolChoice.type === 'required' ||
661
+ toolCall.toolName === enforcedToolChoice.toolName,
662
+ )
663
+ ) {
664
+ controller.enqueue({
665
+ type: 'error',
666
+ error: new ToolChoiceViolationError({
667
+ toolChoice: enforcedToolChoice,
668
+ finishReason: chunk.finishReason.unified,
669
+ provider,
670
+ modelId,
671
+ content: rawModelCallContent,
672
+ }),
673
+ });
674
+ break;
675
+ }
623
676
  break;
624
677
  }
625
678
 
626
679
  case 'tool-call': {
680
+ rawModelCallContent.push(chunk);
681
+
627
682
  try {
628
683
  const toolCall = await parseToolCall({
629
684
  toolCall: chunk,
@@ -663,6 +718,8 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
663
718
  }
664
719
 
665
720
  case 'tool-approval-request': {
721
+ rawModelCallContent.push(chunk);
722
+
666
723
  const toolCall = toolCallsByToolCallId.get(chunk.toolCallId);
667
724
 
668
725
  if (toolCall == null) {
@@ -688,6 +745,8 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
688
745
  }
689
746
 
690
747
  case 'tool-result': {
748
+ rawModelCallContent.push(chunk);
749
+
691
750
  const toolName = chunk.toolName as keyof TOOLS & string;
692
751
  const toolCall = toolCallsByToolCallId.get(chunk.toolCallId);
693
752
 
@@ -765,6 +824,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
765
824
  default:
766
825
  if (chunk.type === 'custom' || chunk.type === 'source') {
767
826
  modelCallContent.push(chunk);
827
+ rawModelCallContent.push(chunk);
768
828
  }
769
829
 
770
830
  controller.enqueue(chunk);
@@ -818,14 +878,18 @@ function calculateNearestRankPercentile(
818
878
  */
819
879
  function upsertTextContentPart<TOOLS extends ToolSet>({
820
880
  content,
881
+ rawContent,
821
882
  partIndexes,
883
+ rawPartIndexes,
822
884
  id,
823
885
  type,
824
886
  textDelta,
825
887
  providerMetadata,
826
888
  }: {
827
889
  content: Array<ContentPart<TOOLS>>;
890
+ rawContent: Array<LanguageModelV4Content>;
828
891
  partIndexes: Map<string, number>;
892
+ rawPartIndexes: Map<string, number>;
829
893
  id: string;
830
894
  type: 'text' | 'reasoning';
831
895
  textDelta?: string;
@@ -843,16 +907,34 @@ function upsertTextContentPart<TOOLS extends ToolSet>({
843
907
  partIndexes.set(id, partIndex);
844
908
  }
845
909
 
910
+ let rawPartIndex = rawPartIndexes.get(id);
911
+
912
+ if (rawPartIndex == null) {
913
+ rawPartIndex =
914
+ rawContent.push({
915
+ type,
916
+ text: '',
917
+ ...(providerMetadata != null ? { providerMetadata } : {}),
918
+ }) - 1;
919
+ rawPartIndexes.set(id, rawPartIndex);
920
+ }
921
+
846
922
  const part = content[partIndex] as {
847
923
  text: string;
848
924
  providerMetadata?: ProviderMetadata;
849
925
  };
926
+ const rawPart = rawContent[rawPartIndex] as {
927
+ text: string;
928
+ providerMetadata?: ProviderMetadata;
929
+ };
850
930
 
851
931
  if (textDelta != null) {
852
932
  part.text += textDelta;
933
+ rawPart.text += textDelta;
853
934
  }
854
935
 
855
936
  if (providerMetadata != null) {
856
937
  part.providerMetadata = providerMetadata;
938
+ rawPart.providerMetadata = providerMetadata;
857
939
  }
858
940
  }
@@ -22,7 +22,7 @@ import {
22
22
  type ToolSet,
23
23
  } from '@ai-sdk/provider-utils';
24
24
  import type { ServerResponse } from 'node:http';
25
- import { NoOutputGeneratedError } from '../error';
25
+ import { NoOutputGeneratedError, ToolChoiceViolationError } from '../error';
26
26
  import { logWarnings } from '../logger/log-warnings';
27
27
  import { resolveLanguageModel } from '../model/resolve-model';
28
28
  import { cloneModelMessages } from '../prompt/clone-model-message';
@@ -2573,6 +2573,8 @@ class DefaultStreamTextResult<
2573
2573
  callbacks: onChunk,
2574
2574
  });
2575
2575
  const error = wrapGatewayError(value.error);
2576
+ const isToolChoiceViolation =
2577
+ ToolChoiceViolationError.isInstance(error);
2576
2578
  let onErrorResult: unknown;
2577
2579
  try {
2578
2580
  onErrorResult = await onError({ error });
@@ -2584,8 +2586,10 @@ class DefaultStreamTextResult<
2584
2586
  'retry' in onErrorResult &&
2585
2587
  onErrorResult.retry === true;
2586
2588
  const automaticRetry =
2589
+ !isToolChoiceViolation &&
2587
2590
  automaticStreamRetryCount < streamRetries;
2588
2591
  const callbackRetry =
2592
+ !isToolChoiceViolation &&
2589
2593
  !automaticRetry &&
2590
2594
  callbackRequestedRetry &&
2591
2595
  callbackStreamRetryCount < 1;
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
  }
@@ -14,7 +14,7 @@ export function mergeAbortSignals(
14
14
  ...signals: (AbortSignal | null | undefined | number)[]
15
15
  ): AbortSignal | undefined {
16
16
  const validSignals = filterNullable(...signals).map(signal =>
17
- signal instanceof AbortSignal ? signal : AbortSignal.timeout(signal),
17
+ typeof signal === 'number' ? AbortSignal.timeout(signal) : signal,
18
18
  );
19
19
 
20
20
  return validSignals.length === 0
@@ -1,5 +1,8 @@
1
1
  import { InvalidArgumentError } from '../error/invalid-argument-error';
2
- import type { RetryFunction } from '@ai-sdk/provider-utils';
2
+ import type {
3
+ RetryFunction,
4
+ ShouldRetryFunction,
5
+ } from '@ai-sdk/provider-utils';
3
6
  import { retryWithExponentialBackoffRespectingRetryHeaders } from '../util/retry-with-exponential-backoff';
4
7
  /**
5
8
  * Validate and prepare retries.
@@ -7,11 +10,13 @@ import { retryWithExponentialBackoffRespectingRetryHeaders } from '../util/retry
7
10
  export function prepareRetries({
8
11
  maxRetries,
9
12
  abortSignal,
13
+ additionalRetryableError,
10
14
  parameter = 'maxRetries',
11
15
  defaultMaxRetries = 2,
12
16
  }: {
13
17
  maxRetries: number | undefined;
14
18
  abortSignal: AbortSignal | undefined;
19
+ additionalRetryableError?: ShouldRetryFunction;
15
20
  parameter?: string;
16
21
  defaultMaxRetries?: number;
17
22
  }): {
@@ -43,6 +48,7 @@ export function prepareRetries({
43
48
  retry: retryWithExponentialBackoffRespectingRetryHeaders({
44
49
  maxRetries: maxRetriesResult,
45
50
  abortSignal,
51
+ additionalRetryableError,
46
52
  }),
47
53
  };
48
54
  }
@@ -3,6 +3,7 @@ import { GatewayError } from '@ai-sdk/gateway';
3
3
  import {
4
4
  retryWithExponentialBackoff,
5
5
  type RetryFunction,
6
+ type ShouldRetryFunction,
6
7
  } from '@ai-sdk/provider-utils';
7
8
  import { RetryError } from './retry-error';
8
9
 
@@ -66,21 +67,25 @@ export const retryWithExponentialBackoffRespectingRetryHeaders = ({
66
67
  initialDelayInMs = 2000,
67
68
  backoffFactor = 2,
68
69
  abortSignal,
70
+ additionalRetryableError,
69
71
  }: {
70
72
  maxRetries?: number;
71
73
  initialDelayInMs?: number;
72
74
  backoffFactor?: number;
73
75
  abortSignal?: AbortSignal;
76
+ additionalRetryableError?: ShouldRetryFunction;
74
77
  } = {}): RetryFunction =>
75
78
  retryWithExponentialBackoff({
76
79
  maxRetries,
77
80
  initialDelayInMs,
78
81
  backoffFactor,
79
82
  abortSignal,
80
- shouldRetry: error =>
81
- error instanceof Error &&
82
- ((APICallError.isInstance(error) && error.isRetryable === true) ||
83
- (GatewayError.isInstance(error) && error.isRetryable === true)),
83
+ shouldRetry: async error =>
84
+ (error instanceof Error &&
85
+ ((APICallError.isInstance(error) && error.isRetryable === true) ||
86
+ (GatewayError.isInstance(error) && error.isRetryable === true))) ||
87
+ (additionalRetryableError != null &&
88
+ (await additionalRetryableError(error))),
84
89
  getDelayInMs: ({ error, exponentialBackoffDelay }) =>
85
90
  getRetryDelayInMs({
86
91
  error: error as APICallError | GatewayError,