ai 7.0.111 → 7.0.113

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 (50) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.d.ts +27 -0
  3. package/dist/index.js +345 -222
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +2 -1
  6. package/dist/internal/index.js +125 -31
  7. package/dist/internal/index.js.map +1 -1
  8. package/docs/03-agents/06-tool-approvals.mdx +16 -0
  9. package/docs/03-ai-sdk-core/37-speech.mdx +2 -0
  10. package/docs/03-ai-sdk-core/40-middleware.mdx +92 -7
  11. package/docs/06-advanced/02-stopping-streams.mdx +8 -0
  12. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +3 -3
  13. package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +1 -1
  14. package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +18 -0
  15. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +9 -3
  16. package/package.json +12 -12
  17. package/src/embed/embed-many.ts +18 -2
  18. package/src/generate-speech/generate-speech.ts +15 -4
  19. package/src/generate-speech/generated-audio-file.ts +0 -8
  20. package/src/generate-text/execute-tools-from-stream.ts +0 -2
  21. package/src/generate-text/generate-text.ts +1 -0
  22. package/src/generate-text/generated-file.ts +0 -8
  23. package/src/generate-text/invoke-tool-callbacks-from-stream.ts +9 -8
  24. package/src/generate-text/output.ts +0 -2
  25. package/src/generate-text/parse-tool-call.ts +38 -25
  26. package/src/generate-text/prune-messages.ts +3 -1
  27. package/src/generate-text/stream-text.ts +1 -0
  28. package/src/generate-text/to-response-messages.ts +7 -0
  29. package/src/generate-text/tool-call.ts +26 -0
  30. package/src/generate-text/validate-tool-approvals.ts +39 -3
  31. package/src/generate-video/generate-video.ts +0 -2
  32. package/src/middleware/extract-reasoning-middleware.ts +1 -1
  33. package/src/middleware/wrap-embedding-model.ts +9 -1
  34. package/src/model/get-embedding-model-provider-options-transformer.ts +17 -0
  35. package/src/prompt/content-part.ts +3 -0
  36. package/src/prompt/convert-to-language-model-prompt.ts +12 -2
  37. package/src/prompt/file-part-data.ts +11 -1
  38. package/src/registry/custom-provider.ts +12 -5
  39. package/src/ui/chat.ts +108 -17
  40. package/src/ui/convert-to-model-messages.ts +8 -0
  41. package/src/ui/direct-chat-transport.ts +2 -0
  42. package/src/ui/process-ui-message-stream.ts +6 -0
  43. package/src/ui/ui-messages.ts +10 -0
  44. package/src/ui/validate-ui-messages.ts +100 -134
  45. package/src/ui-message-stream/handle-ui-message-stream-finish.ts +5 -3
  46. package/src/ui-message-stream/to-ui-message-chunk.ts +7 -0
  47. package/src/ui-message-stream/ui-message-chunks.ts +2 -0
  48. package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +8 -0
  49. package/src/ui-message-stream/ui-message-stream-outcome.ts +4 -0
  50. package/src/util/write-to-server-response.ts +0 -2
@@ -5,11 +5,13 @@ import {
5
5
  validateTypes,
6
6
  zodSchema,
7
7
  type FlexibleSchema,
8
+ type MaybePromiseLike,
8
9
  type Tool,
9
10
  } from '@ai-sdk/provider-utils';
10
11
  import { InvalidArgumentError } from '../error';
11
12
  import { jsonValueSchema } from '../types/json-value';
12
13
  import { getOwn } from '../util/get-own';
14
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
13
15
  import { providerMetadataSchema } from '../types/provider-metadata';
14
16
  import { z, type ZodType } from '../util/zod';
15
17
  import type {
@@ -48,8 +50,38 @@ function asDynamicToolPart(toolPart: ToolUIPart): DynamicToolUIPart {
48
50
  } as DynamicToolUIPart;
49
51
  }
50
52
 
51
- const uiMessagesSchema = lazySchema(() =>
52
- zodSchema(
53
+ function getToolPartInputSchemaInput(
54
+ toolPart: ToolUIPart,
55
+ ): { value: unknown } | undefined {
56
+ return toolPart.approval != null &&
57
+ Object.prototype.hasOwnProperty.call(toolPart.approval, 'inputSchemaInput')
58
+ ? { value: toolPart.approval.inputSchemaInput }
59
+ : undefined;
60
+ }
61
+
62
+ const uiMessagesSchema = lazySchema(() => {
63
+ const approvalRequestedSchema = z.object({
64
+ id: z.string(),
65
+ approved: z.never().optional(),
66
+ descriptor: z.unknown().optional(),
67
+ requestReason: z.string().optional(),
68
+ reason: z.never().optional(),
69
+ isAutomatic: z.boolean().optional(),
70
+ signature: z.string().optional(),
71
+ inputSchemaInput: z.unknown().optional(),
72
+ });
73
+ const approvalRespondedSchema = approvalRequestedSchema.extend({
74
+ approved: z.boolean(),
75
+ reason: z.string().optional(),
76
+ });
77
+ const approvalGrantedSchema = approvalRespondedSchema.extend({
78
+ approved: z.literal(true),
79
+ });
80
+ const approvalDeniedSchema = approvalRespondedSchema.extend({
81
+ approved: z.literal(false),
82
+ });
83
+
84
+ return zodSchema(
53
85
  z
54
86
  .array(
55
87
  z
@@ -154,15 +186,7 @@ const uiMessagesSchema = lazySchema(() =>
154
186
  output: z.never().optional(),
155
187
  errorText: z.never().optional(),
156
188
  callProviderMetadata: providerMetadataSchema.optional(),
157
- approval: z.object({
158
- id: z.string(),
159
- approved: z.never().optional(),
160
- descriptor: z.unknown().optional(),
161
- requestReason: z.string().optional(),
162
- reason: z.never().optional(),
163
- isAutomatic: z.boolean().optional(),
164
- signature: z.string().optional(),
165
- }),
189
+ approval: approvalRequestedSchema,
166
190
  }),
167
191
  z.object({
168
192
  type: z.literal('dynamic-tool'),
@@ -176,15 +200,7 @@ const uiMessagesSchema = lazySchema(() =>
176
200
  output: z.never().optional(),
177
201
  errorText: z.never().optional(),
178
202
  callProviderMetadata: providerMetadataSchema.optional(),
179
- approval: z.object({
180
- id: z.string(),
181
- approved: z.boolean(),
182
- descriptor: z.unknown().optional(),
183
- requestReason: z.string().optional(),
184
- reason: z.string().optional(),
185
- isAutomatic: z.boolean().optional(),
186
- signature: z.string().optional(),
187
- }),
203
+ approval: approvalRespondedSchema,
188
204
  }),
189
205
  z.object({
190
206
  type: z.literal('dynamic-tool'),
@@ -200,17 +216,7 @@ const uiMessagesSchema = lazySchema(() =>
200
216
  callProviderMetadata: providerMetadataSchema.optional(),
201
217
  resultProviderMetadata: providerMetadataSchema.optional(),
202
218
  preliminary: z.boolean().optional(),
203
- approval: z
204
- .object({
205
- id: z.string(),
206
- approved: z.literal(true),
207
- descriptor: z.unknown().optional(),
208
- requestReason: z.string().optional(),
209
- reason: z.string().optional(),
210
- isAutomatic: z.boolean().optional(),
211
- signature: z.string().optional(),
212
- })
213
- .optional(),
219
+ approval: approvalGrantedSchema.optional(),
214
220
  }),
215
221
  z.object({
216
222
  type: z.literal('dynamic-tool'),
@@ -226,17 +232,7 @@ const uiMessagesSchema = lazySchema(() =>
226
232
  errorText: z.string(),
227
233
  callProviderMetadata: providerMetadataSchema.optional(),
228
234
  resultProviderMetadata: providerMetadataSchema.optional(),
229
- approval: z
230
- .object({
231
- id: z.string(),
232
- approved: z.literal(true),
233
- descriptor: z.unknown().optional(),
234
- requestReason: z.string().optional(),
235
- reason: z.string().optional(),
236
- isAutomatic: z.boolean().optional(),
237
- signature: z.string().optional(),
238
- })
239
- .optional(),
235
+ approval: approvalGrantedSchema.optional(),
240
236
  }),
241
237
  z.object({
242
238
  type: z.literal('dynamic-tool'),
@@ -250,15 +246,7 @@ const uiMessagesSchema = lazySchema(() =>
250
246
  output: z.never().optional(),
251
247
  errorText: z.never().optional(),
252
248
  callProviderMetadata: providerMetadataSchema.optional(),
253
- approval: z.object({
254
- id: z.string(),
255
- approved: z.literal(false),
256
- descriptor: z.unknown().optional(),
257
- requestReason: z.string().optional(),
258
- reason: z.string().optional(),
259
- isAutomatic: z.boolean().optional(),
260
- signature: z.string().optional(),
261
- }),
249
+ approval: approvalDeniedSchema,
262
250
  }),
263
251
  z.object({
264
252
  type: z.string().startsWith('tool-'),
@@ -297,15 +285,7 @@ const uiMessagesSchema = lazySchema(() =>
297
285
  output: z.never().optional(),
298
286
  errorText: z.never().optional(),
299
287
  callProviderMetadata: providerMetadataSchema.optional(),
300
- approval: z.object({
301
- id: z.string(),
302
- approved: z.never().optional(),
303
- descriptor: z.unknown().optional(),
304
- requestReason: z.string().optional(),
305
- reason: z.never().optional(),
306
- isAutomatic: z.boolean().optional(),
307
- signature: z.string().optional(),
308
- }),
288
+ approval: approvalRequestedSchema,
309
289
  }),
310
290
  z.object({
311
291
  type: z.string().startsWith('tool-'),
@@ -318,15 +298,7 @@ const uiMessagesSchema = lazySchema(() =>
318
298
  output: z.never().optional(),
319
299
  errorText: z.never().optional(),
320
300
  callProviderMetadata: providerMetadataSchema.optional(),
321
- approval: z.object({
322
- id: z.string(),
323
- approved: z.boolean(),
324
- descriptor: z.unknown().optional(),
325
- requestReason: z.string().optional(),
326
- reason: z.string().optional(),
327
- isAutomatic: z.boolean().optional(),
328
- signature: z.string().optional(),
329
- }),
301
+ approval: approvalRespondedSchema,
330
302
  }),
331
303
  z.object({
332
304
  type: z.string().startsWith('tool-'),
@@ -341,17 +313,7 @@ const uiMessagesSchema = lazySchema(() =>
341
313
  callProviderMetadata: providerMetadataSchema.optional(),
342
314
  resultProviderMetadata: providerMetadataSchema.optional(),
343
315
  preliminary: z.boolean().optional(),
344
- approval: z
345
- .object({
346
- id: z.string(),
347
- approved: z.literal(true),
348
- descriptor: z.unknown().optional(),
349
- requestReason: z.string().optional(),
350
- reason: z.string().optional(),
351
- isAutomatic: z.boolean().optional(),
352
- signature: z.string().optional(),
353
- })
354
- .optional(),
316
+ approval: approvalGrantedSchema.optional(),
355
317
  }),
356
318
  z.object({
357
319
  type: z.string().startsWith('tool-'),
@@ -366,17 +328,7 @@ const uiMessagesSchema = lazySchema(() =>
366
328
  errorText: z.string(),
367
329
  callProviderMetadata: providerMetadataSchema.optional(),
368
330
  resultProviderMetadata: providerMetadataSchema.optional(),
369
- approval: z
370
- .object({
371
- id: z.string(),
372
- approved: z.literal(true),
373
- descriptor: z.unknown().optional(),
374
- requestReason: z.string().optional(),
375
- reason: z.string().optional(),
376
- isAutomatic: z.boolean().optional(),
377
- signature: z.string().optional(),
378
- })
379
- .optional(),
331
+ approval: approvalGrantedSchema.optional(),
380
332
  }),
381
333
  z.object({
382
334
  type: z.string().startsWith('tool-'),
@@ -389,15 +341,7 @@ const uiMessagesSchema = lazySchema(() =>
389
341
  output: z.never().optional(),
390
342
  errorText: z.never().optional(),
391
343
  callProviderMetadata: providerMetadataSchema.optional(),
392
- approval: z.object({
393
- id: z.string(),
394
- approved: z.literal(false),
395
- descriptor: z.unknown().optional(),
396
- requestReason: z.string().optional(),
397
- reason: z.string().optional(),
398
- isAutomatic: z.boolean().optional(),
399
- signature: z.string().optional(),
400
- }),
344
+ approval: approvalDeniedSchema,
401
345
  }),
402
346
  ]),
403
347
  ),
@@ -417,8 +361,8 @@ const uiMessagesSchema = lazySchema(() =>
417
361
  }),
418
362
  )
419
363
  .nonempty('Messages array must not be empty'),
420
- ),
421
- );
364
+ );
365
+ });
422
366
 
423
367
  export type SafeValidateUIMessagesResult<UI_MESSAGE extends UIMessage> =
424
368
  | {
@@ -453,6 +397,11 @@ type ValidateUIMessagesOptions<UI_MESSAGE extends UIMessage> = {
453
397
  >;
454
398
  };
455
399
  };
400
+ experimental_refineToolInput?: {
401
+ [NAME in keyof InferUIMessageTools<UI_MESSAGE> & string]?: (
402
+ input: InferUIMessageTools<UI_MESSAGE>[NAME]['input'],
403
+ ) => MaybePromiseLike<InferUIMessageTools<UI_MESSAGE>[NAME]['input']>;
404
+ };
456
405
  };
457
406
 
458
407
  async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
@@ -461,6 +410,7 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
461
410
  metadataSchema,
462
411
  dataSchemas,
463
412
  tools,
413
+ experimental_refineToolInput,
464
414
  }: ValidateUIMessagesOptions<UI_MESSAGE>,
465
415
  {
466
416
  convertMissingTerminalToolsToDynamic,
@@ -489,7 +439,7 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
489
439
 
490
440
  if (metadataSchema) {
491
441
  for (const [msgIdx, message] of validatedMessages.entries()) {
492
- await validateTypes({
442
+ message.metadata = await validateTypes({
493
443
  value: message.metadata,
494
444
  schema: metadataSchema,
495
445
  context: {
@@ -527,7 +477,7 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
527
477
  };
528
478
  }
529
479
 
530
- await validateTypes({
480
+ dataPart.data = await validateTypes({
531
481
  value: dataPart.data,
532
482
  schema: dataSchema,
533
483
  context: {
@@ -583,49 +533,65 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
583
533
  entityName: toolName,
584
534
  entityId: toolPart.toolCallId,
585
535
  };
536
+ const inputSchemaInput = getToolPartInputSchemaInput(toolPart);
537
+ const inputToValidate =
538
+ inputSchemaInput == null
539
+ ? toolPart.input
540
+ : inputSchemaInput.value;
586
541
  let convertToDynamic = false;
587
542
 
588
543
  // Tool input validation
589
- if (toolPart.state === 'output-error') {
590
- // Failed calls can retain invalid input. Keep them loadable, but
591
- // expose incompatible input as unknown instead of the current
592
- // static tool input type.
593
- if (toolPart.input !== undefined) {
594
- const result = await safeValidateTypes({
595
- value: toolPart.input,
596
- schema: tool.inputSchema,
597
- context: inputValidationContext,
598
- });
599
- convertToDynamic = !result.success;
600
- }
601
- } else if (toolPart.state === 'output-available') {
544
+ if (
545
+ toolPart.state !== 'input-streaming' &&
546
+ (toolPart.state !== 'output-error' ||
547
+ inputSchemaInput != null ||
548
+ toolPart.input !== undefined)
549
+ ) {
602
550
  const result = await safeValidateTypes({
603
- value: toolPart.input,
551
+ value: inputToValidate,
604
552
  schema: tool.inputSchema,
605
553
  context: inputValidationContext,
606
554
  });
607
555
 
556
+ let inputError: TypeValidationError | undefined;
608
557
  if (!result.success) {
609
- // Empty terminal input can represent aborted or incomplete
610
- // history whose input was never streamed. Preserve it without
611
- // claiming that it matches the current static input type.
612
- if (isEmptyObject(toolPart.input)) {
558
+ inputError = result.error;
559
+ } else if (inputSchemaInput != null) {
560
+ try {
561
+ const refine = getOwn(experimental_refineToolInput, toolName);
562
+ const reconstructedInput =
563
+ refine == null ? result.value : await refine(result.value);
564
+
565
+ if (!isDeepEqualData(reconstructedInput, toolPart.input)) {
566
+ inputError = new TypeValidationError({
567
+ value: toolPart.input,
568
+ cause:
569
+ 'Tool input does not match the output reconstructed from inputSchemaInput.',
570
+ context: inputValidationContext,
571
+ });
572
+ }
573
+ } catch (error) {
574
+ inputError = new TypeValidationError({
575
+ value: inputToValidate,
576
+ cause: error,
577
+ context: inputValidationContext,
578
+ });
579
+ }
580
+ }
581
+
582
+ if (inputError != null) {
583
+ // Failed calls and empty terminal inputs must remain loadable,
584
+ // but must not claim to match the current static input type.
585
+ if (
586
+ toolPart.state === 'output-error' ||
587
+ (toolPart.state === 'output-available' &&
588
+ isEmptyObject(toolPart.input))
589
+ ) {
613
590
  convertToDynamic = true;
614
591
  } else {
615
- throw result.error;
592
+ throw inputError;
616
593
  }
617
594
  }
618
- } else if (
619
- toolPart.state === 'input-available' ||
620
- toolPart.state === 'approval-requested' ||
621
- toolPart.state === 'approval-responded' ||
622
- toolPart.state === 'output-denied'
623
- ) {
624
- await validateTypes({
625
- value: toolPart.input,
626
- schema: tool.inputSchema,
627
- context: inputValidationContext,
628
- });
629
595
  }
630
596
 
631
597
  // Tool output validation
@@ -147,7 +147,7 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
147
147
 
148
148
  let finishCalled = false;
149
149
 
150
- const callOnEnd = async () => {
150
+ const callOnEnd = async ({ isCancelled }: { isCancelled: boolean }) => {
151
151
  if (finishCalled || !resolvedOnEnd) {
152
152
  return;
153
153
  }
@@ -160,9 +160,11 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
160
160
  : declaredOutcome.status === 'unknown' && isAborted
161
161
  ? { status: 'aborted' }
162
162
  : declaredOutcome;
163
+ const isConsumerCancellation = isCancelled && outcome.status === 'unknown';
163
164
 
164
165
  await resolvedOnEnd({
165
166
  isAborted: isAborted || outcome.status === 'aborted',
167
+ ...(isConsumerCancellation ? { isCancelled: true as const } : {}),
166
168
  isContinuation,
167
169
  outcome,
168
170
  responseMessage: state.message as UI_MESSAGE,
@@ -215,11 +217,11 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
215
217
  },
216
218
  // @ts-expect-error cancel is still new and missing from types https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility
217
219
  async cancel() {
218
- await callOnEnd();
220
+ await callOnEnd({ isCancelled: true });
219
221
  },
220
222
 
221
223
  async flush() {
222
- await callOnEnd();
224
+ await callOnEnd({ isCancelled: false });
223
225
  },
224
226
  }),
225
227
  );
@@ -1,10 +1,12 @@
1
1
  import type { ToolSet } from '@ai-sdk/provider-utils';
2
2
  import type { TextStreamPart } from '../generate-text/stream-text-result';
3
+ import { getToolCallInputSchemaInput } from '../generate-text/tool-call';
3
4
  import type {
4
5
  InferUIMessageData,
5
6
  InferUIMessageMetadata,
6
7
  UIMessage,
7
8
  } from '../ui/ui-messages';
9
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
8
10
  import type { InferUIMessageChunk, UIMessageChunk } from './ui-message-chunks';
9
11
 
10
12
  export type ToUIMessageChunkOptions<
@@ -250,10 +252,15 @@ export function toUIMessageChunk<
250
252
  }
251
253
 
252
254
  case 'tool-approval-request': {
255
+ const inputSchemaInput = getToolCallInputSchemaInput(part.toolCall);
253
256
  return {
254
257
  type: 'tool-approval-request',
255
258
  approvalId: part.approvalId,
256
259
  toolCallId: part.toolCall.toolCallId,
260
+ ...(inputSchemaInput != null &&
261
+ !isDeepEqualData(inputSchemaInput.value, part.toolCall.input)
262
+ ? { inputSchemaInput: inputSchemaInput.value }
263
+ : {}),
257
264
  ...(part.reason != null ? { reason: part.reason } : {}),
258
265
  ...(part.isAutomatic != null ? { isAutomatic: part.isAutomatic } : {}),
259
266
  ...(part.signature != null ? { signature: part.signature } : {}),
@@ -86,6 +86,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
86
86
  approvalId: z.string(),
87
87
  toolCallId: z.string(),
88
88
  approvalDescriptor: z.unknown().optional(),
89
+ inputSchemaInput: z.unknown().optional(),
89
90
  reason: z.string().optional(),
90
91
  isAutomatic: z.boolean().optional(),
91
92
  signature: z.string().optional(),
@@ -301,6 +302,7 @@ export type UIMessageChunk<
301
302
  approvalId: string;
302
303
  toolCallId: string;
303
304
  approvalDescriptor?: unknown;
305
+ inputSchemaInput?: unknown;
304
306
  reason?: string;
305
307
  isAutomatic?: boolean;
306
308
  signature?: string;
@@ -20,6 +20,14 @@ export type UIMessageStreamOnEndCallback<UI_MESSAGE extends UIMessage> =
20
20
  */
21
21
  isAborted: boolean;
22
22
 
23
+ /**
24
+ * Indicates that the consumer cancelled the stream before an outcome was
25
+ * declared, for example because the client disconnected.
26
+ *
27
+ * This property is only present when it is `true`.
28
+ */
29
+ isCancelled?: true;
30
+
23
31
  /**
24
32
  * The operation-level outcome of the stream. Fatal stream-processing
25
33
  * failures override outcomes declared by the stream owner.
@@ -4,6 +4,10 @@
4
4
  * This is separate from model finish reasons and individual stream chunks.
5
5
  * Fatal stream-processing failures override outcomes declared by the stream
6
6
  * owner.
7
+ *
8
+ * Consumer cancellation before an outcome is declared keeps the `unknown`
9
+ * status and is reported separately through the end callback's `isCancelled`
10
+ * property.
7
11
  */
8
12
  export type UIMessageStreamOutcome =
9
13
  | { status: 'completed' }
@@ -51,8 +51,6 @@ export function writeToServerResponse({
51
51
  });
52
52
  }
53
53
  }
54
- } catch (error) {
55
- throw error;
56
54
  } finally {
57
55
  response.end();
58
56
  }