ai 6.0.201 → 6.0.202
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 +14 -0
- package/dist/index.d.mts +58 -13
- package/dist/index.d.ts +58 -13
- package/dist/index.js +836 -575
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +674 -408
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.mjs +1 -1
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +32 -0
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +20 -2
- package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +1 -1
- package/docs/07-reference/05-ai-sdk-errors/ai-invalid-tool-approval-signature-error.mdx +31 -0
- package/package.json +6 -6
- package/src/error/index.ts +1 -0
- package/src/error/invalid-tool-approval-signature-error.ts +35 -0
- package/src/generate-text/generate-text.ts +48 -6
- package/src/generate-text/run-tools-transformation.ts +14 -1
- package/src/generate-text/stream-text.ts +41 -6
- package/src/generate-text/to-response-messages.ts +1 -0
- package/src/generate-text/tool-approval-request-output.ts +5 -0
- package/src/generate-text/tool-approval-signature.ts +125 -0
- package/src/generate-text/validate-tool-approvals.ts +124 -0
- package/src/ui/convert-to-model-messages.ts +3 -0
- package/src/ui/process-ui-message-stream.ts +6 -1
- package/src/ui/ui-messages.ts +10 -0
- package/src/ui/validate-ui-messages.ts +10 -0
- package/src/ui-message-stream/ui-message-chunks.ts +2 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asSchema,
|
|
3
|
+
safeValidateTypes,
|
|
4
|
+
type ModelMessage,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { InvalidToolApprovalSignatureError } from '../error/invalid-tool-approval-signature-error';
|
|
7
|
+
import { InvalidToolInputError } from '../error/invalid-tool-input-error';
|
|
8
|
+
import type { CollectedToolApprovals } from './collect-tool-approvals';
|
|
9
|
+
import { isApprovalNeeded } from './is-approval-needed';
|
|
10
|
+
import { verifyToolApprovalSignature } from './tool-approval-signature';
|
|
11
|
+
import type { ToolSet } from './tool-set';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Re-validates approved tool approvals reconstructed from client-supplied
|
|
15
|
+
* message history before they are executed. Checks the HMAC signature (when
|
|
16
|
+
* `experimental_toolApprovalSecret` is configured), re-validates the tool-call
|
|
17
|
+
* input against the tool's input schema, and re-resolves whether the tool
|
|
18
|
+
* actually requires approval.
|
|
19
|
+
*
|
|
20
|
+
* Approvals that fail signature or schema validation throw (fail-closed).
|
|
21
|
+
* Approvals for tools that no longer require approval are moved to the denied
|
|
22
|
+
* list, since the server would never have issued an approval request for them.
|
|
23
|
+
*/
|
|
24
|
+
export async function validateApprovedToolApprovals<TOOLS extends ToolSet>({
|
|
25
|
+
approvedToolApprovals,
|
|
26
|
+
tools,
|
|
27
|
+
messages,
|
|
28
|
+
experimental_context,
|
|
29
|
+
toolApprovalSecret,
|
|
30
|
+
}: {
|
|
31
|
+
approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
|
|
32
|
+
tools: TOOLS | undefined;
|
|
33
|
+
messages: ModelMessage[];
|
|
34
|
+
experimental_context: unknown;
|
|
35
|
+
toolApprovalSecret?: string | Uint8Array;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
|
|
38
|
+
deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
|
|
39
|
+
}> {
|
|
40
|
+
const approved: Array<CollectedToolApprovals<TOOLS>> = [];
|
|
41
|
+
const denied: Array<CollectedToolApprovals<TOOLS>> = [];
|
|
42
|
+
|
|
43
|
+
for (const approval of approvedToolApprovals) {
|
|
44
|
+
const { toolCall, approvalRequest } = approval;
|
|
45
|
+
const tool = tools?.[toolCall.toolName];
|
|
46
|
+
|
|
47
|
+
if (toolApprovalSecret != null) {
|
|
48
|
+
if (approvalRequest.signature == null) {
|
|
49
|
+
throw new InvalidToolApprovalSignatureError({
|
|
50
|
+
approvalId: approvalRequest.approvalId,
|
|
51
|
+
toolCallId: toolCall.toolCallId,
|
|
52
|
+
reason: 'missing signature',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const valid = await verifyToolApprovalSignature({
|
|
57
|
+
secret: toolApprovalSecret,
|
|
58
|
+
signature: approvalRequest.signature,
|
|
59
|
+
approvalId: approvalRequest.approvalId,
|
|
60
|
+
toolCallId: toolCall.toolCallId,
|
|
61
|
+
toolName: toolCall.toolName,
|
|
62
|
+
input: toolCall.input,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!valid) {
|
|
66
|
+
throw new InvalidToolApprovalSignatureError({
|
|
67
|
+
approvalId: approvalRequest.approvalId,
|
|
68
|
+
toolCallId: toolCall.toolCallId,
|
|
69
|
+
reason: 'invalid signature',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Re-validate the (client-supplied) input against the tool's input schema
|
|
75
|
+
// for tools that are executed on the server.
|
|
76
|
+
if (
|
|
77
|
+
tool != null &&
|
|
78
|
+
typeof tool.execute === 'function' &&
|
|
79
|
+
tool.inputSchema != null
|
|
80
|
+
) {
|
|
81
|
+
const validation = await safeValidateTypes({
|
|
82
|
+
value: toolCall.input,
|
|
83
|
+
schema: asSchema(tool.inputSchema),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!validation.success) {
|
|
87
|
+
throw new InvalidToolInputError({
|
|
88
|
+
toolName: toolCall.toolName,
|
|
89
|
+
toolInput: JSON.stringify(toolCall.input),
|
|
90
|
+
cause: validation.error,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Re-resolve whether the tool requires approval. A tool that does not
|
|
96
|
+
// require approval would never have had an approval request issued by the
|
|
97
|
+
// server, so any approval for it is fabricated and is denied.
|
|
98
|
+
const approvalNeeded =
|
|
99
|
+
tool != null &&
|
|
100
|
+
(await isApprovalNeeded({
|
|
101
|
+
tool,
|
|
102
|
+
toolCall,
|
|
103
|
+
messages,
|
|
104
|
+
experimental_context,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
if (approvalNeeded) {
|
|
108
|
+
approved.push(approval);
|
|
109
|
+
} else {
|
|
110
|
+
denied.push({
|
|
111
|
+
...approval,
|
|
112
|
+
approvalResponse: {
|
|
113
|
+
...approval.approvalResponse,
|
|
114
|
+
approved: false,
|
|
115
|
+
reason:
|
|
116
|
+
approval.approvalResponse.reason ??
|
|
117
|
+
`Tool "${toolCall.toolName}" does not require approval`,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { approvedToolApprovals: approved, deniedToolApprovals: denied };
|
|
124
|
+
}
|
|
@@ -196,6 +196,9 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
196
196
|
type: 'tool-approval-request' as const,
|
|
197
197
|
approvalId: part.approval.id,
|
|
198
198
|
toolCallId: part.toolCallId,
|
|
199
|
+
...(part.approval.signature != null
|
|
200
|
+
? { signature: part.approval.signature }
|
|
201
|
+
: {}),
|
|
199
202
|
});
|
|
200
203
|
}
|
|
201
204
|
|
|
@@ -675,7 +675,12 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
675
675
|
case 'tool-approval-request': {
|
|
676
676
|
const toolInvocation = getToolInvocation(chunk.toolCallId);
|
|
677
677
|
toolInvocation.state = 'approval-requested';
|
|
678
|
-
toolInvocation.approval = {
|
|
678
|
+
toolInvocation.approval = {
|
|
679
|
+
id: chunk.approvalId,
|
|
680
|
+
...(chunk.signature != null
|
|
681
|
+
? { signature: chunk.signature }
|
|
682
|
+
: {}),
|
|
683
|
+
};
|
|
679
684
|
write();
|
|
680
685
|
break;
|
|
681
686
|
}
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -256,6 +256,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
256
256
|
id: string;
|
|
257
257
|
approved?: never;
|
|
258
258
|
reason?: never;
|
|
259
|
+
signature?: string;
|
|
259
260
|
};
|
|
260
261
|
}
|
|
261
262
|
| {
|
|
@@ -268,6 +269,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
268
269
|
id: string;
|
|
269
270
|
approved: boolean;
|
|
270
271
|
reason?: string;
|
|
272
|
+
signature?: string;
|
|
271
273
|
};
|
|
272
274
|
}
|
|
273
275
|
| {
|
|
@@ -282,6 +284,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
282
284
|
id: string;
|
|
283
285
|
approved: true;
|
|
284
286
|
reason?: string;
|
|
287
|
+
signature?: string;
|
|
285
288
|
};
|
|
286
289
|
}
|
|
287
290
|
| {
|
|
@@ -296,6 +299,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
296
299
|
id: string;
|
|
297
300
|
approved: true;
|
|
298
301
|
reason?: string;
|
|
302
|
+
signature?: string;
|
|
299
303
|
};
|
|
300
304
|
}
|
|
301
305
|
| {
|
|
@@ -308,6 +312,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
308
312
|
id: string;
|
|
309
313
|
approved: false;
|
|
310
314
|
reason?: string;
|
|
315
|
+
signature?: string;
|
|
311
316
|
};
|
|
312
317
|
}
|
|
313
318
|
);
|
|
@@ -364,6 +369,7 @@ export type DynamicToolUIPart = {
|
|
|
364
369
|
id: string;
|
|
365
370
|
approved?: never;
|
|
366
371
|
reason?: never;
|
|
372
|
+
signature?: string;
|
|
367
373
|
};
|
|
368
374
|
}
|
|
369
375
|
| {
|
|
@@ -376,6 +382,7 @@ export type DynamicToolUIPart = {
|
|
|
376
382
|
id: string;
|
|
377
383
|
approved: boolean;
|
|
378
384
|
reason?: string;
|
|
385
|
+
signature?: string;
|
|
379
386
|
};
|
|
380
387
|
}
|
|
381
388
|
| {
|
|
@@ -390,6 +397,7 @@ export type DynamicToolUIPart = {
|
|
|
390
397
|
id: string;
|
|
391
398
|
approved: true;
|
|
392
399
|
reason?: string;
|
|
400
|
+
signature?: string;
|
|
393
401
|
};
|
|
394
402
|
}
|
|
395
403
|
| {
|
|
@@ -403,6 +411,7 @@ export type DynamicToolUIPart = {
|
|
|
403
411
|
id: string;
|
|
404
412
|
approved: true;
|
|
405
413
|
reason?: string;
|
|
414
|
+
signature?: string;
|
|
406
415
|
};
|
|
407
416
|
}
|
|
408
417
|
| {
|
|
@@ -415,6 +424,7 @@ export type DynamicToolUIPart = {
|
|
|
415
424
|
id: string;
|
|
416
425
|
approved: false;
|
|
417
426
|
reason?: string;
|
|
427
|
+
signature?: string;
|
|
418
428
|
};
|
|
419
429
|
}
|
|
420
430
|
);
|
|
@@ -122,6 +122,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
122
122
|
id: z.string(),
|
|
123
123
|
approved: z.never().optional(),
|
|
124
124
|
reason: z.never().optional(),
|
|
125
|
+
signature: z.string().optional(),
|
|
125
126
|
}),
|
|
126
127
|
}),
|
|
127
128
|
z.object({
|
|
@@ -139,6 +140,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
139
140
|
id: z.string(),
|
|
140
141
|
approved: z.boolean(),
|
|
141
142
|
reason: z.string().optional(),
|
|
143
|
+
signature: z.string().optional(),
|
|
142
144
|
}),
|
|
143
145
|
}),
|
|
144
146
|
z.object({
|
|
@@ -159,6 +161,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
159
161
|
id: z.string(),
|
|
160
162
|
approved: z.literal(true),
|
|
161
163
|
reason: z.string().optional(),
|
|
164
|
+
signature: z.string().optional(),
|
|
162
165
|
})
|
|
163
166
|
.optional(),
|
|
164
167
|
}),
|
|
@@ -180,6 +183,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
180
183
|
id: z.string(),
|
|
181
184
|
approved: z.literal(true),
|
|
182
185
|
reason: z.string().optional(),
|
|
186
|
+
signature: z.string().optional(),
|
|
183
187
|
})
|
|
184
188
|
.optional(),
|
|
185
189
|
}),
|
|
@@ -198,6 +202,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
198
202
|
id: z.string(),
|
|
199
203
|
approved: z.literal(false),
|
|
200
204
|
reason: z.string().optional(),
|
|
205
|
+
signature: z.string().optional(),
|
|
201
206
|
}),
|
|
202
207
|
}),
|
|
203
208
|
z.object({
|
|
@@ -238,6 +243,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
238
243
|
id: z.string(),
|
|
239
244
|
approved: z.never().optional(),
|
|
240
245
|
reason: z.never().optional(),
|
|
246
|
+
signature: z.string().optional(),
|
|
241
247
|
}),
|
|
242
248
|
}),
|
|
243
249
|
z.object({
|
|
@@ -254,6 +260,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
254
260
|
id: z.string(),
|
|
255
261
|
approved: z.boolean(),
|
|
256
262
|
reason: z.string().optional(),
|
|
263
|
+
signature: z.string().optional(),
|
|
257
264
|
}),
|
|
258
265
|
}),
|
|
259
266
|
z.object({
|
|
@@ -273,6 +280,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
273
280
|
id: z.string(),
|
|
274
281
|
approved: z.literal(true),
|
|
275
282
|
reason: z.string().optional(),
|
|
283
|
+
signature: z.string().optional(),
|
|
276
284
|
})
|
|
277
285
|
.optional(),
|
|
278
286
|
}),
|
|
@@ -293,6 +301,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
293
301
|
id: z.string(),
|
|
294
302
|
approved: z.literal(true),
|
|
295
303
|
reason: z.string().optional(),
|
|
304
|
+
signature: z.string().optional(),
|
|
296
305
|
})
|
|
297
306
|
.optional(),
|
|
298
307
|
}),
|
|
@@ -310,6 +319,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
310
319
|
id: z.string(),
|
|
311
320
|
approved: z.literal(false),
|
|
312
321
|
reason: z.string().optional(),
|
|
322
|
+
signature: z.string().optional(),
|
|
313
323
|
}),
|
|
314
324
|
}),
|
|
315
325
|
]),
|
|
@@ -85,6 +85,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
|
|
|
85
85
|
type: z.literal('tool-approval-request'),
|
|
86
86
|
approvalId: z.string(),
|
|
87
87
|
toolCallId: z.string(),
|
|
88
|
+
signature: z.string().optional(),
|
|
88
89
|
}),
|
|
89
90
|
z.strictObject({
|
|
90
91
|
type: z.literal('tool-output-available'),
|
|
@@ -269,6 +270,7 @@ export type UIMessageChunk<
|
|
|
269
270
|
type: 'tool-approval-request';
|
|
270
271
|
approvalId: string;
|
|
271
272
|
toolCallId: string;
|
|
273
|
+
signature?: string;
|
|
272
274
|
}
|
|
273
275
|
| {
|
|
274
276
|
type: 'tool-output-available';
|