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
@@ -6,8 +6,10 @@ import type {
6
6
  } from '../prompt';
7
7
  import { createToolModelOutput } from '../prompt/create-tool-model-output';
8
8
  import { getOwn } from '../util/get-own';
9
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
9
10
  import type { ContentPart } from './content-part';
10
11
  import type { ToolSet } from '@ai-sdk/provider-utils';
12
+ import { getToolCallInputSchemaInput } from './tool-call';
11
13
 
12
14
  /**
13
15
  * Converts the result of a `generateText` or `streamText` call to a list of response messages.
@@ -129,6 +131,7 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
129
131
  break;
130
132
  }
131
133
  case 'tool-approval-request':
134
+ const inputSchemaInput = getToolCallInputSchemaInput(part.toolCall);
132
135
  content.push({
133
136
  type: 'tool-approval-request',
134
137
  approvalId: part.approvalId,
@@ -136,6 +139,10 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
136
139
  ...(part.reason != null ? { reason: part.reason } : {}),
137
140
  isAutomatic: part.isAutomatic,
138
141
  ...(part.signature != null ? { signature: part.signature } : {}),
142
+ ...(inputSchemaInput != null &&
143
+ !isDeepEqualData(inputSchemaInput.value, part.toolCall.input)
144
+ ? { inputSchemaInput: inputSchemaInput.value }
145
+ : {}),
139
146
  });
140
147
  break;
141
148
  }
@@ -3,6 +3,8 @@ import type { InferToolInput, ToolSet } from '@ai-sdk/provider-utils';
3
3
  import type { ProviderMetadata } from '../types';
4
4
  import type { ValueOf } from '../util/value-of';
5
5
 
6
+ const inputSchemaInputSymbol = Symbol('ai-sdk-tool-call-input-schema-input');
7
+
6
8
  type BaseToolCall = {
7
9
  type: 'tool-call';
8
10
  toolCallId: string;
@@ -58,3 +60,27 @@ export type DynamicToolCall = BaseToolCall & {
58
60
  export type TypedToolCall<TOOLS extends ToolSet> =
59
61
  | StaticToolCall<TOOLS>
60
62
  | DynamicToolCall;
63
+
64
+ export function setToolCallInputSchemaInput<TOOLS extends ToolSet>(
65
+ toolCall: TypedToolCall<TOOLS>,
66
+ inputSchemaInput: unknown,
67
+ ): TypedToolCall<TOOLS> {
68
+ Object.defineProperty(toolCall, inputSchemaInputSymbol, {
69
+ value: inputSchemaInput,
70
+ });
71
+ return toolCall;
72
+ }
73
+
74
+ export function getToolCallInputSchemaInput<TOOLS extends ToolSet>(
75
+ toolCall: TypedToolCall<TOOLS>,
76
+ ): { value: unknown } | undefined {
77
+ return inputSchemaInputSymbol in toolCall
78
+ ? {
79
+ value: (
80
+ toolCall as TypedToolCall<TOOLS> & {
81
+ [inputSchemaInputSymbol]: unknown;
82
+ }
83
+ )[inputSchemaInputSymbol],
84
+ }
85
+ : undefined;
86
+ }
@@ -10,10 +10,13 @@ import {
10
10
  import { InvalidToolApprovalSignatureError } from '../error/invalid-tool-approval-signature-error';
11
11
  import { InvalidToolInputError } from '../error/invalid-tool-input-error';
12
12
  import { getOwn } from '../util/get-own';
13
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
13
14
  import type { CollectedToolApprovals } from './collect-tool-approvals';
15
+ import { refineParsedToolCallInput } from './parse-tool-call';
14
16
  import { resolveToolApproval } from './resolve-tool-approval';
15
17
  import { verifyToolApprovalSignature } from './tool-approval-signature';
16
18
  import type { ToolApprovalConfiguration } from './tool-approval-configuration';
19
+ import type { ToolInputRefinement } from './tool-input-refinement';
17
20
 
18
21
  /**
19
22
  * Re-validates approved tool approvals reconstructed from client-supplied
@@ -31,6 +34,7 @@ export async function validateApprovedToolApprovals<
31
34
  toolsContext,
32
35
  runtimeContext,
33
36
  toolApprovalSecret,
37
+ refineToolInput,
34
38
  }: {
35
39
  approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
36
40
  tools: TOOLS | undefined;
@@ -39,6 +43,7 @@ export async function validateApprovedToolApprovals<
39
43
  toolsContext: InferToolSetContext<TOOLS>;
40
44
  runtimeContext: RUNTIME_CONTEXT;
41
45
  toolApprovalSecret?: string | Uint8Array;
46
+ refineToolInput?: ToolInputRefinement<TOOLS>;
42
47
  }): Promise<{
43
48
  approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
44
49
  deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
@@ -53,7 +58,7 @@ export async function validateApprovedToolApprovals<
53
58
  > = [];
54
59
 
55
60
  for (const approval of approvedToolApprovals) {
56
- const { toolCall, approvalRequest } = approval;
61
+ const { approvalRequest, toolCall } = approval;
57
62
  // Look up the tool by own property only: `toolName` comes from
58
63
  // client-supplied history, so a name matching an inherited object property
59
64
  // (e.g. `constructor`, `toString`) must resolve to "no such tool" rather
@@ -88,18 +93,49 @@ export async function validateApprovedToolApprovals<
88
93
  }
89
94
 
90
95
  if (isExecutableTool(tool) && tool.inputSchema != null) {
96
+ const hasInputSchemaInput = Object.prototype.hasOwnProperty.call(
97
+ approvalRequest,
98
+ 'inputSchemaInput',
99
+ );
91
100
  const validation = await safeValidateTypes({
92
- value: toolCall.input,
101
+ value: hasInputSchemaInput
102
+ ? approvalRequest.inputSchemaInput
103
+ : toolCall.input,
93
104
  schema: asSchema(tool.inputSchema),
94
105
  });
95
106
 
107
+ let validationError: unknown;
96
108
  if (!validation.success) {
109
+ validationError = validation.error;
110
+ } else {
111
+ try {
112
+ const revalidatedToolCall = await refineParsedToolCallInput({
113
+ toolCall: {
114
+ ...toolCall,
115
+ input: validation.value,
116
+ },
117
+ refineToolInput,
118
+ });
119
+
120
+ // Revalidation must never change the operation that was approved,
121
+ // including when older or projected history omits the schema input.
122
+ if (!isDeepEqualData(revalidatedToolCall.input, toolCall.input)) {
123
+ validationError = new Error(
124
+ 'Approved tool input does not match the validated schema output.',
125
+ );
126
+ }
127
+ } catch (error) {
128
+ validationError = error;
129
+ }
130
+ }
131
+
132
+ if (validationError != null) {
97
133
  invalid.push({
98
134
  ...approval,
99
135
  error: new InvalidToolInputError({
100
136
  toolName: toolCall.toolName,
101
137
  toolInput: JSON.stringify(toolCall.input),
102
- cause: validation.error,
138
+ cause: validationError,
103
139
  }),
104
140
  });
105
141
  continue;
@@ -890,8 +890,6 @@ function normalizeImageData(
890
890
  data: bytes,
891
891
  };
892
892
  }
893
-
894
- return undefined;
895
893
  }
896
894
 
897
895
  /**
@@ -24,7 +24,7 @@ export function extractReasoningMiddleware({
24
24
  startWithReasoning?: boolean;
25
25
  }): LanguageModelMiddleware {
26
26
  const openingTag = `<${tagName}>`;
27
- const closingTag = `<\/${tagName}>`;
27
+ const closingTag = `</${tagName}>`;
28
28
 
29
29
  return {
30
30
  specificationVersion: 'v4',
@@ -7,12 +7,17 @@ import type {
7
7
  import {
8
8
  asArray,
9
9
  EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL,
10
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
10
11
  } from '@ai-sdk/provider-utils';
11
12
  import { asEmbeddingModelV4 } from '../model/as-embedding-model-v4';
12
13
  import {
13
14
  getEmbeddingModelMaxInputBytesPerCall,
14
15
  type EmbeddingModelWithMaxInputBytesPerCall,
15
16
  } from '../model/get-embedding-model-max-input-bytes-per-call';
17
+ import {
18
+ getEmbeddingModelProviderOptionsTransformer,
19
+ type EmbeddingModelWithProviderOptionsTransformer,
20
+ } from '../model/get-embedding-model-provider-options-transformer';
16
21
  import type { EmbeddingModelMiddleware } from '../types';
17
22
 
18
23
  /**
@@ -63,7 +68,8 @@ const doWrap = ({
63
68
  middleware: EmbeddingModelMiddleware;
64
69
  modelId?: string;
65
70
  providerId?: string;
66
- }): EmbeddingModelWithMaxInputBytesPerCall => {
71
+ }): EmbeddingModelWithMaxInputBytesPerCall &
72
+ EmbeddingModelWithProviderOptionsTransformer => {
67
73
  async function doTransform({
68
74
  params,
69
75
  }: {
@@ -80,6 +86,8 @@ const doWrap = ({
80
86
  overrideMaxEmbeddingsPerCall?.({ model }) ?? model.maxEmbeddingsPerCall,
81
87
  [EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL]:
82
88
  getEmbeddingModelMaxInputBytesPerCall(model),
89
+ [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]:
90
+ getEmbeddingModelProviderOptionsTransformer(model),
83
91
  supportsParallelCalls:
84
92
  overrideSupportsParallelCalls?.({ model }) ?? model.supportsParallelCalls,
85
93
  async doEmbed(
@@ -0,0 +1,17 @@
1
+ import type { EmbeddingModelV4 } from '@ai-sdk/provider';
2
+ import {
3
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
4
+ type EmbeddingModelProviderOptionsTransformer,
5
+ } from '@ai-sdk/provider-utils';
6
+
7
+ export type EmbeddingModelWithProviderOptionsTransformer = EmbeddingModelV4 & {
8
+ readonly [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]?: EmbeddingModelProviderOptionsTransformer;
9
+ };
10
+
11
+ export function getEmbeddingModelProviderOptionsTransformer(
12
+ model: EmbeddingModelV4,
13
+ ) {
14
+ return (model as EmbeddingModelWithProviderOptionsTransformer)[
15
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER
16
+ ];
17
+ }
@@ -284,6 +284,9 @@ export const toolApprovalRequestSchema: ZodType<ToolApprovalRequest> = z.object(
284
284
  approvalId: z.string(),
285
285
  toolCallId: z.string(),
286
286
  reason: z.string().optional(),
287
+ isAutomatic: z.boolean().optional(),
288
+ signature: z.string().optional(),
289
+ inputSchemaInput: z.unknown().optional(),
287
290
  },
288
291
  );
289
292
 
@@ -661,6 +661,7 @@ export function mapToolResultOutput({
661
661
  }
662
662
  case 'file-url': {
663
663
  const mediaType = item.mediaType ?? getMediaTypeFromUrl(item.url);
664
+ const url = new URL(item.url);
664
665
  let message = `The "file-url" type for tool result content is deprecated. Use the "file" type with mediaType and { type: 'url', url } instead.`;
665
666
  if (!item.mediaType) {
666
667
  const inferenceSuffix =
@@ -676,7 +677,11 @@ export function mapToolResultOutput({
676
677
  });
677
678
  return {
678
679
  type: 'file' as const,
679
- data: { type: 'url' as const, url: new URL(item.url) },
680
+ data: {
681
+ type: 'url' as const,
682
+ url,
683
+ ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
684
+ },
680
685
  mediaType,
681
686
  providerOptions: item.providerOptions,
682
687
  };
@@ -732,6 +737,7 @@ export function mapToolResultOutput({
732
737
  };
733
738
  }
734
739
  case 'image-url': {
740
+ const url = new URL(item.url);
735
741
  warnings.push({
736
742
  type: 'deprecated',
737
743
  setting: '"tool-result" content of type "image-url"',
@@ -739,7 +745,11 @@ export function mapToolResultOutput({
739
745
  });
740
746
  return {
741
747
  type: 'file' as const,
742
- data: { type: 'url' as const, url: new URL(item.url) },
748
+ data: {
749
+ type: 'url' as const,
750
+ url,
751
+ ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
752
+ },
743
753
  mediaType: 'image',
744
754
  providerOptions: item.providerOptions,
745
755
  };
@@ -41,6 +41,16 @@ function convertUrlToFilePartData(url: URL): ConvertResult {
41
41
  return { data: { type: 'url', url }, mediaType: undefined };
42
42
  }
43
43
 
44
+ function convertUrlStringToFilePartData(content: string): ConvertResult {
45
+ const result = convertUrlToFilePartData(new URL(content));
46
+
47
+ if (result.data.type === 'url' && result.data.url.toString() !== content) {
48
+ result.data.originalUrl = content;
49
+ }
50
+
51
+ return result;
52
+ }
53
+
44
54
  function convertInlineDataToFilePartData(content: DataContent): ConvertResult {
45
55
  if (content instanceof Uint8Array) {
46
56
  return { data: { type: 'data', data: content }, mediaType: undefined };
@@ -108,7 +118,7 @@ export function convertToLanguageModelV4FilePart(
108
118
 
109
119
  if (typeof content === 'string') {
110
120
  try {
111
- return convertUrlToFilePartData(new URL(content));
121
+ return convertUrlStringToFilePartData(content);
112
122
  } catch {
113
123
  return convertInlineDataToFilePartData(content);
114
124
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type Experimental_EvaluationModelV4 as EvaluationModelV4,
3
3
  type EmbeddingModelV4,
4
+ type Experimental_VideoModelV3,
4
5
  type Experimental_VideoModelV4,
5
6
  type FilesV4,
6
7
  type ImageModelV4,
@@ -35,6 +36,12 @@ import type { SpeechModel } from '../types/speech-model';
35
36
  import type { TranscriptionModel } from '../types/transcription-model';
36
37
  import type { VideoModel } from '../types/video-model';
37
38
 
39
+ type ProviderWithOptionalVideoModel = {
40
+ videoModel?: (
41
+ modelId: string,
42
+ ) => Experimental_VideoModelV3 | Experimental_VideoModelV4;
43
+ };
44
+
38
45
  /**
39
46
  * Creates a custom provider with specified language models, text embedding models, image models, transcription models, speech models, file APIs, skill APIs, and an optional fallback provider.
40
47
  *
@@ -247,11 +254,11 @@ export function customProvider<
247
254
  return resolveVideoModel(videoModels[modelId]);
248
255
  }
249
256
 
250
- // TODO AI SDK v7
251
- // @ts-expect-error - videoModel support is experimental
252
- const videoModel = fallbackProvider?.videoModel;
253
- if (videoModel) {
254
- return videoModel(modelId);
257
+ const provider = fallbackProviderArg as
258
+ | ProviderWithOptionalVideoModel
259
+ | undefined;
260
+ if (provider?.videoModel) {
261
+ return resolveVideoModel(provider.videoModel(modelId));
255
262
  }
256
263
 
257
264
  throw new NoSuchModelError({ modelId, modelType: 'videoModel' });
package/src/ui/chat.ts CHANGED
@@ -260,6 +260,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
260
260
  private sendAutomaticallyWhen?: ChatInit<UI_MESSAGE>['sendAutomaticallyWhen'];
261
261
 
262
262
  private pendingMessagePreparations = new Set<AbortController>();
263
+ private pendingApprovalMessageId: string | undefined;
263
264
  private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
264
265
  private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
265
266
  private jobExecutor = new SerialJobExecutor();
@@ -362,9 +363,37 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
362
363
  options?: ChatRequestOptions,
363
364
  ): Promise<void> => {
364
365
  if (message == null) {
365
- await this.makeRequest({
366
- trigger: 'submit-message',
367
- messageId: this.lastMessage?.id,
366
+ let messageId = this.pendingApprovalMessageId;
367
+
368
+ if (messageId == null) {
369
+ messageId = this.lastMessage?.id;
370
+
371
+ // When hydrating a chat with an already-responded approval, continue
372
+ // the most recent matching assistant message so result chunks can
373
+ // resolve its tool invocation.
374
+ for (let i = this.state.messages.length - 1; i >= 0; i--) {
375
+ const candidate = this.state.messages[i];
376
+ if (
377
+ candidate.role === 'assistant' &&
378
+ candidate.parts.some(
379
+ part => isToolUIPart(part) && part.state === 'approval-responded',
380
+ )
381
+ ) {
382
+ messageId = candidate.id;
383
+ break;
384
+ }
385
+ }
386
+ }
387
+
388
+ const consumesPendingApproval =
389
+ messageId != null && messageId === this.pendingApprovalMessageId;
390
+ const pendingApprovalMessageIndex = consumesPendingApproval
391
+ ? this.state.messages.findIndex(message => message.id === messageId)
392
+ : -1;
393
+
394
+ await this.makeRequestForToolApproval({
395
+ messageId,
396
+ messageIndex: pendingApprovalMessageIndex,
368
397
  ...options,
369
398
  });
370
399
  return;
@@ -512,7 +541,6 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
512
541
  }) =>
513
542
  this.jobExecutor.run(async () => {
514
543
  const messages = this.state.messages;
515
- const lastMessage = messages[messages.length - 1];
516
544
 
517
545
  const updatePart = (
518
546
  part: UIMessagePart<UIDataTypes, UITools>,
@@ -527,11 +555,25 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
527
555
  }
528
556
  : part;
529
557
 
530
- // update the message to trigger an immediate UI update
531
- this.state.replaceMessage(messages.length - 1, {
532
- ...lastMessage,
533
- parts: lastMessage.parts.map(updatePart),
534
- });
558
+ const messageIndex = messages.findIndex(message =>
559
+ message.parts.some(
560
+ part =>
561
+ isToolUIPart(part) &&
562
+ part.state === 'approval-requested' &&
563
+ part.approval.id === id,
564
+ ),
565
+ );
566
+
567
+ if (messageIndex !== -1) {
568
+ const message = messages[messageIndex];
569
+
570
+ // update the message to trigger an immediate UI update
571
+ this.state.replaceMessage(messageIndex, {
572
+ ...message,
573
+ parts: message.parts.map(updatePart),
574
+ });
575
+ this.pendingApprovalMessageId = message.id;
576
+ }
535
577
 
536
578
  // update the active response if it exists
537
579
  if (this.activeResponse) {
@@ -548,9 +590,14 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
548
590
  this.shouldSendAutomatically().then(shouldSend => {
549
591
  if (shouldSend) {
550
592
  // no await to avoid deadlocking
551
- this.makeRequest({
552
- trigger: 'submit-message',
553
- messageId: this.lastMessage?.id,
593
+ const messageId =
594
+ messageIndex === -1
595
+ ? this.lastMessage?.id
596
+ : messages[messageIndex].id;
597
+
598
+ this.makeRequestForToolApproval({
599
+ messageId,
600
+ messageIndex,
554
601
  ...options,
555
602
  });
556
603
  }
@@ -636,6 +683,36 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
636
683
  return result as boolean;
637
684
  }
638
685
 
686
+ private async makeRequestForToolApproval({
687
+ messageId,
688
+ messageIndex,
689
+ ...options
690
+ }: {
691
+ messageId?: string;
692
+ messageIndex: number;
693
+ } & ChatRequestOptions) {
694
+ const consumesPendingApproval =
695
+ messageId != null && messageId === this.pendingApprovalMessageId;
696
+ if (consumesPendingApproval) {
697
+ this.pendingApprovalMessageId = undefined;
698
+ }
699
+
700
+ await this.makeRequest({
701
+ trigger: 'submit-message',
702
+ messageId,
703
+ ...options,
704
+ });
705
+
706
+ if (
707
+ consumesPendingApproval &&
708
+ this.status === 'error' &&
709
+ this.pendingApprovalMessageId == null
710
+ ) {
711
+ this.pendingApprovalMessageId =
712
+ this.state.messages[messageIndex]?.id ?? messageId;
713
+ }
714
+ }
715
+
639
716
  private async makeRequest({
640
717
  trigger,
641
718
  metadata,
@@ -723,6 +800,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
723
800
  this.setStatus({ status: 'submitted', error: undefined });
724
801
 
725
802
  const lastMessage = this.lastMessage;
803
+ const responseMessageIndex =
804
+ trigger === 'submit-message' && messageId != null
805
+ ? this.state.messages.findIndex(message => message.id === messageId)
806
+ : this.state.messages.length - 1;
807
+ const responseMessage =
808
+ responseMessageIndex === -1
809
+ ? lastMessage
810
+ : this.state.messages[responseMessageIndex];
811
+ const usesEarlierAssistantMessage =
812
+ responseMessageIndex !== -1 &&
813
+ responseMessageIndex < this.state.messages.length - 1 &&
814
+ responseMessage?.role === 'assistant';
726
815
 
727
816
  let isAbort = false;
728
817
  let isDisconnect = false;
@@ -735,7 +824,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
735
824
  lastMessage:
736
825
  trigger === 'resume-stream' || trigger === 'regenerate-message'
737
826
  ? undefined
738
- : this.state.snapshot(lastMessage),
827
+ : this.state.snapshot(responseMessage),
739
828
  messageId: this.generateId(),
740
829
  }),
741
830
  abortController,
@@ -789,10 +878,12 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
789
878
  this.setStatus({ status: 'streaming' });
790
879
  }
791
880
 
792
- const replaceLastMessage =
793
- response.state.message.id === this.lastMessage?.id;
794
-
795
- if (replaceLastMessage) {
881
+ if (usesEarlierAssistantMessage) {
882
+ this.state.replaceMessage(
883
+ responseMessageIndex,
884
+ response.state.message,
885
+ );
886
+ } else if (response.state.message.id === this.lastMessage?.id) {
796
887
  this.state.replaceMessage(
797
888
  this.state.messages.length - 1,
798
889
  response.state.message,
@@ -245,6 +245,14 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
245
245
  ...(part.approval.requestReason != null
246
246
  ? { reason: part.approval.requestReason }
247
247
  : {}),
248
+ ...(Object.prototype.hasOwnProperty.call(
249
+ part.approval,
250
+ 'inputSchemaInput',
251
+ )
252
+ ? {
253
+ inputSchemaInput: part.approval.inputSchemaInput,
254
+ }
255
+ : {}),
248
256
  ...(part.approval.signature != null
249
257
  ? { signature: part.approval.signature }
250
258
  : {}),
@@ -124,6 +124,8 @@ export class DirectChatTransport<
124
124
  // Return the UI message stream
125
125
  return toUIMessageStream({
126
126
  ...this.uiMessageStreamOptions,
127
+ originalMessages:
128
+ this.uiMessageStreamOptions.originalMessages ?? validatedMessages,
127
129
  stream: result.stream,
128
130
  tools: this.agent.tools,
129
131
  });
@@ -755,6 +755,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
755
755
  ...(chunk.approvalDescriptor != null
756
756
  ? { descriptor: chunk.approvalDescriptor }
757
757
  : {}),
758
+ ...(Object.prototype.hasOwnProperty.call(
759
+ chunk,
760
+ 'inputSchemaInput',
761
+ )
762
+ ? { inputSchemaInput: chunk.inputSchemaInput }
763
+ : {}),
758
764
  ...(chunk.reason != null
759
765
  ? { requestReason: chunk.reason }
760
766
  : {}),
@@ -324,6 +324,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
324
324
  reason?: never;
325
325
  isAutomatic?: boolean;
326
326
  signature?: string;
327
+ inputSchemaInput?: unknown;
327
328
  };
328
329
  }
329
330
  | {
@@ -340,6 +341,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
340
341
  reason?: string;
341
342
  isAutomatic?: boolean;
342
343
  signature?: string;
344
+ inputSchemaInput?: unknown;
343
345
  };
344
346
  }
345
347
  | {
@@ -358,6 +360,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
358
360
  reason?: string;
359
361
  isAutomatic?: boolean;
360
362
  signature?: string;
363
+ inputSchemaInput?: unknown;
361
364
  };
362
365
  }
363
366
  | {
@@ -380,6 +383,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
380
383
  reason?: string;
381
384
  isAutomatic?: boolean;
382
385
  signature?: string;
386
+ inputSchemaInput?: unknown;
383
387
  };
384
388
  }
385
389
  | {
@@ -396,6 +400,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
396
400
  reason?: string;
397
401
  isAutomatic?: boolean;
398
402
  signature?: string;
403
+ inputSchemaInput?: unknown;
399
404
  };
400
405
  }
401
406
  );
@@ -456,6 +461,7 @@ export type DynamicToolUIPart = {
456
461
  reason?: never;
457
462
  isAutomatic?: boolean;
458
463
  signature?: string;
464
+ inputSchemaInput?: unknown;
459
465
  };
460
466
  }
461
467
  | {
@@ -472,6 +478,7 @@ export type DynamicToolUIPart = {
472
478
  reason?: string;
473
479
  isAutomatic?: boolean;
474
480
  signature?: string;
481
+ inputSchemaInput?: unknown;
475
482
  };
476
483
  }
477
484
  | {
@@ -490,6 +497,7 @@ export type DynamicToolUIPart = {
490
497
  reason?: string;
491
498
  isAutomatic?: boolean;
492
499
  signature?: string;
500
+ inputSchemaInput?: unknown;
493
501
  };
494
502
  }
495
503
  | {
@@ -507,6 +515,7 @@ export type DynamicToolUIPart = {
507
515
  reason?: string;
508
516
  isAutomatic?: boolean;
509
517
  signature?: string;
518
+ inputSchemaInput?: unknown;
510
519
  };
511
520
  }
512
521
  | {
@@ -523,6 +532,7 @@ export type DynamicToolUIPart = {
523
532
  reason?: string;
524
533
  isAutomatic?: boolean;
525
534
  signature?: string;
535
+ inputSchemaInput?: unknown;
526
536
  };
527
537
  }
528
538
  );