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.
- package/CHANGELOG.md +41 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +345 -222
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal/index.js +125 -31
- package/dist/internal/index.js.map +1 -1
- package/docs/03-agents/06-tool-approvals.mdx +16 -0
- package/docs/03-ai-sdk-core/37-speech.mdx +2 -0
- package/docs/03-ai-sdk-core/40-middleware.mdx +92 -7
- package/docs/06-advanced/02-stopping-streams.mdx +8 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +3 -3
- package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +18 -0
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +9 -3
- package/package.json +12 -12
- package/src/embed/embed-many.ts +18 -2
- package/src/generate-speech/generate-speech.ts +15 -4
- package/src/generate-speech/generated-audio-file.ts +0 -8
- package/src/generate-text/execute-tools-from-stream.ts +0 -2
- package/src/generate-text/generate-text.ts +1 -0
- package/src/generate-text/generated-file.ts +0 -8
- package/src/generate-text/invoke-tool-callbacks-from-stream.ts +9 -8
- package/src/generate-text/output.ts +0 -2
- package/src/generate-text/parse-tool-call.ts +38 -25
- package/src/generate-text/prune-messages.ts +3 -1
- package/src/generate-text/stream-text.ts +1 -0
- package/src/generate-text/to-response-messages.ts +7 -0
- package/src/generate-text/tool-call.ts +26 -0
- package/src/generate-text/validate-tool-approvals.ts +39 -3
- package/src/generate-video/generate-video.ts +0 -2
- package/src/middleware/extract-reasoning-middleware.ts +1 -1
- package/src/middleware/wrap-embedding-model.ts +9 -1
- package/src/model/get-embedding-model-provider-options-transformer.ts +17 -0
- package/src/prompt/content-part.ts +3 -0
- package/src/prompt/convert-to-language-model-prompt.ts +12 -2
- package/src/prompt/file-part-data.ts +11 -1
- package/src/registry/custom-provider.ts +12 -5
- package/src/ui/chat.ts +108 -17
- package/src/ui/convert-to-model-messages.ts +8 -0
- package/src/ui/direct-chat-transport.ts +2 -0
- package/src/ui/process-ui-message-stream.ts +6 -0
- package/src/ui/ui-messages.ts +10 -0
- package/src/ui/validate-ui-messages.ts +100 -134
- package/src/ui-message-stream/handle-ui-message-stream-finish.ts +5 -3
- package/src/ui-message-stream/to-ui-message-chunk.ts +7 -0
- package/src/ui-message-stream/ui-message-chunks.ts +2 -0
- package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +8 -0
- package/src/ui-message-stream/ui-message-stream-outcome.ts +4 -0
- 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 {
|
|
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:
|
|
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:
|
|
138
|
+
cause: validationError,
|
|
103
139
|
}),
|
|
104
140
|
});
|
|
105
141
|
continue;
|
|
@@ -24,7 +24,7 @@ export function extractReasoningMiddleware({
|
|
|
24
24
|
startWithReasoning?: boolean;
|
|
25
25
|
}): LanguageModelMiddleware {
|
|
26
26
|
const openingTag = `<${tagName}>`;
|
|
27
|
-
const closingTag =
|
|
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: {
|
|
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: {
|
|
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
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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(
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
: {}),
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -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
|
);
|