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.
@@ -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 = { id: chunk.approvalId };
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
  }
@@ -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';