ai 7.0.85 → 7.0.87

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.
@@ -319,9 +319,12 @@ Every serverless instance that might handle a request needs the same secret, sin
319
319
  - The secret is never sent to the client or included in the stream
320
320
 
321
321
  <Note>
322
- `experimental_toolApprovalSecret` is not yet supported on `WorkflowAgent`. For
323
- durable workflows, the workflow runtime provides its own persistence layer
324
- that can be used to verify approvals.
322
+ `WorkflowAgent` also supports `experimental_toolApprovalSecret`. It signs in
323
+ a workflow step before writing the durable approval request. Pass an
324
+ environment variable reference, such as
325
+ `{ environmentVariable: 'TOOL_APPROVAL_SECRET' }`, so the raw secret is read
326
+ only inside signing and verification steps. Only the signature is persisted
327
+ and sent to the client.
325
328
  </Note>
326
329
 
327
330
  ## Related APIs
@@ -354,6 +354,49 @@ const agent = new WorkflowAgent({
354
354
 
355
355
  Because the workflow is durable, the approval request survives process restarts — the user can approve hours later and the agent will resume.
356
356
 
357
+ ### Signed Tool Approvals
358
+
359
+ Client-supplied message history can be modified before it is replayed to the
360
+ workflow. For tools that perform sensitive operations, configure
361
+ `experimental_toolApprovalSecret` to authenticate approval requests:
362
+
363
+ ```ts highlight="4"
364
+ const agent = new WorkflowAgent({
365
+ model: 'anthropic/claude-sonnet-4-6',
366
+ experimental_toolApprovalSecret: {
367
+ environmentVariable: 'TOOL_APPROVAL_SECRET',
368
+ },
369
+ tools: {
370
+ bookFlight: tool({
371
+ description: 'Book a flight',
372
+ inputSchema: z.object({ flightId: z.string() }),
373
+ needsApproval: true,
374
+ execute: bookFlightStep,
375
+ }),
376
+ },
377
+ });
378
+ ```
379
+
380
+ The agent HMAC-signs the approval ID, tool call ID, tool name, and validated
381
+ input. When approved message history is replayed, a missing or invalid
382
+ signature prevents the tool from executing. The signature is preserved through
383
+ the durable model-call stream, `createModelCallToUIChunkTransform()`,
384
+ `addToolApprovalResponse()`, and `convertToModelMessages()`.
385
+
386
+ Use a high-entropy secret of at least 32 bytes and make the same secret
387
+ available to every worker that can issue or resume an approval. Keep old keys
388
+ available while approvals signed with them are pending; changing the secret
389
+ invalidates those pending approvals.
390
+
391
+ `WorkflowAgent` passes only the environment variable name into signing and
392
+ verification steps. Each step reads the secret from its local environment, and
393
+ the raw value is never included in step arguments, durable stream parts,
394
+ callbacks, or telemetry events. Configure the environment variable on every
395
+ worker, and do not put the secret value in `runtimeContext` or `toolsContext`.
396
+
397
+ You can also provide `experimental_toolApprovalSecret` to `agent.stream()`.
398
+ The stream-level value overrides the constructor default.
399
+
357
400
  ## Loop Control
358
401
 
359
402
  Control how many steps the agent can take:
@@ -526,6 +526,10 @@ console.log(preparation.identity);
526
526
  - `sandboxConfig`: sandbox working-directory and lifecycle hook configuration.
527
527
  - `telemetry`, `debug`, and `onLog`: observability and diagnostics.
528
528
 
529
+ Telemetry reports each turn's resolved model, instructions, and active
530
+ host-defined tools. Skills are adapter context and do not have a corresponding
531
+ AI SDK telemetry field, so they are not included in standard telemetry events.
532
+
529
533
  Adapter-specific settings belong on the adapter factory, for example
530
534
  `createCodex({ reasoningEffort: 'high' })`.
531
535
 
@@ -543,6 +543,13 @@ For manual approval requests, the reason for requiring approval is available as
543
543
  `part.approval.requestReason`. It remains separate from an optional response
544
544
  reason supplied to `addToolApprovalResponse`.
545
545
 
546
+ Approval request chunks can also include an `approvalDescriptor` with opaque
547
+ application-specific metadata. UI message processing exposes it as
548
+ `part.approval.descriptor` in the `approval-requested` state and preserves it in
549
+ subsequent approval-bearing states, including `approval-responded`. This lets
550
+ clients render or persist server-computed approval metadata without using it to
551
+ determine whether the tool was approved.
552
+
546
553
  ### Securing Approvals for Sensitive Tools
547
554
 
548
555
  In the `useChat` pattern, the client sends the full message history to the server each turn. Without additional protection, a modified client could fabricate an approval response. For tools that perform sensitive operations, add `experimental_toolApprovalSecret` to your `streamText` call so the server cryptographically verifies that it issued the approval:
@@ -367,11 +367,15 @@ Format: Server-Sent Event with JSON object
367
367
  Example:
368
368
 
369
369
  ```
370
- data: {"type":"tool-approval-request","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","approvalId":"approval_123","reason":"Requires operator review"}
370
+ data: {"type":"tool-approval-request","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","approvalId":"approval_123","approvalDescriptor":{"scope":"account:delete"},"reason":"Requires operator review"}
371
371
 
372
372
  ```
373
373
 
374
- When `isAutomatic` is omitted, the request expects an explicit approval response from the client. `reason` is optional and explains why the tool call requires approval.
374
+ When `isAutomatic` is omitted, the request expects an explicit approval response
375
+ from the client. `reason` is optional and explains why the tool call requires
376
+ approval. `approvalDescriptor` is optional opaque metadata for the approval.
377
+ When the stream is processed into UI messages, it is available as
378
+ `part.approval.descriptor` and is retained through subsequent approval states.
375
379
 
376
380
  ### Tool Approval Response Part
377
381
 
@@ -150,6 +150,36 @@ type ToolUIPart<TOOLS extends UITools = UITools> = ValueOf<{
150
150
  output?: never;
151
151
  errorText?: never;
152
152
  }
153
+ | {
154
+ state: 'approval-requested';
155
+ input: TOOLS[NAME]['input'];
156
+ output?: never;
157
+ errorText?: never;
158
+ approval: {
159
+ id: string;
160
+ approved?: never;
161
+ descriptor?: unknown;
162
+ requestReason?: string;
163
+ reason?: never;
164
+ isAutomatic?: boolean;
165
+ signature?: string;
166
+ };
167
+ }
168
+ | {
169
+ state: 'approval-responded';
170
+ input: TOOLS[NAME]['input'];
171
+ output?: never;
172
+ errorText?: never;
173
+ approval: {
174
+ id: string;
175
+ approved: boolean;
176
+ descriptor?: unknown;
177
+ requestReason?: string;
178
+ reason?: string;
179
+ isAutomatic?: boolean;
180
+ signature?: string;
181
+ };
182
+ }
153
183
  | {
154
184
  state: 'output-available';
155
185
  input: TOOLS[NAME]['input'];
@@ -168,6 +198,11 @@ type ToolUIPart<TOOLS extends UITools = UITools> = ValueOf<{
168
198
  }>;
169
199
  ```
170
200
 
201
+ `approval.descriptor` contains optional opaque metadata supplied as
202
+ `approvalDescriptor` on the approval request stream chunk. It is preserved when
203
+ the tool part transitions from `approval-requested` to `approval-responded` and
204
+ in later approval-bearing output states.
205
+
171
206
  ### `CustomContentUIPart`
172
207
 
173
208
  A provider-specific custom content part of a message.
@@ -136,6 +136,13 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
136
136
  description:
137
137
  'Default sandbox session passed to tool descriptions and execution as `experimental_sandbox`, and exposed to `prepareStep`. Per-stream values override this default.',
138
138
  },
139
+ {
140
+ name: 'experimental_toolApprovalSecret',
141
+ type: 'WorkflowToolApprovalSecret',
142
+ isOptional: true,
143
+ description:
144
+ 'Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Per-stream values override this default.',
145
+ },
139
146
  {
140
147
  name: 'prepareStep',
141
148
  type: 'PrepareStepCallback',
@@ -559,6 +566,13 @@ const result = await agent.stream({
559
566
  description:
560
567
  'Sandbox session passed to tool descriptions and execution as `experimental_sandbox`, and exposed to `prepareStep`. Overrides the constructor default.',
561
568
  },
569
+ {
570
+ name: 'experimental_toolApprovalSecret',
571
+ type: 'WorkflowToolApprovalSecret',
572
+ isOptional: true,
573
+ description:
574
+ 'Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Overrides the constructor default.',
575
+ },
562
576
  {
563
577
  name: 'telemetry',
564
578
  type: 'TelemetryOptions',
@@ -922,6 +936,9 @@ import { z } from 'zod';
922
936
 
923
937
  const agent = new WorkflowAgent({
924
938
  model: 'anthropic/claude-sonnet-4-6',
939
+ experimental_toolApprovalSecret: {
940
+ environmentVariable: 'TOOL_APPROVAL_SECRET',
941
+ },
925
942
  tools: {
926
943
  bookFlight: tool({
927
944
  description: 'Book a flight',
@@ -936,6 +953,14 @@ const agent = new WorkflowAgent({
936
953
  });
937
954
  ```
938
955
 
956
+ When `experimental_toolApprovalSecret` is configured, each approval request is
957
+ signed over its approval ID, tool call ID, tool name, and validated input.
958
+ Replayed approvals with a missing or invalid signature do not execute the tool.
959
+ The signature is preserved in the durable stream and UI message history, while
960
+ only the environment variable name crosses workflow boundaries. Signing and
961
+ verification steps read the raw secret from their local environment and do not
962
+ serialize it. A stream-level reference overrides the constructor value.
963
+
939
964
  ### Agent with Lifecycle Callbacks
940
965
 
941
966
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.85",
3
+ "version": "7.0.87",
4
4
  "type": "module",
5
5
  "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
6
6
  "license": "Apache-2.0",
@@ -42,18 +42,18 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.69",
45
+ "@ai-sdk/gateway": "4.0.70",
46
46
  "@ai-sdk/provider": "4.0.9",
47
47
  "@ai-sdk/provider-utils": "5.0.34"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.68",
50
+ "@ai-sdk/amazon-bedrock": "5.0.69",
51
51
  "@ai-sdk/deepseek": "3.0.37",
52
- "@ai-sdk/google": "4.0.58",
52
+ "@ai-sdk/google": "4.0.59",
53
53
  "@ai-sdk/groq": "4.0.35",
54
54
  "@ai-sdk/huggingface": "2.0.41",
55
55
  "@ai-sdk/moonshotai": "3.0.43",
56
- "@ai-sdk/openai": "4.0.52",
56
+ "@ai-sdk/openai": "4.0.53",
57
57
  "@ai-sdk/test-server": "2.0.1",
58
58
  "@ai-sdk/xai": "4.0.50",
59
59
  "@edge-runtime/vm": "^5.0.0",
@@ -748,6 +748,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
748
748
  toolInvocation.state = 'approval-requested';
749
749
  toolInvocation.approval = {
750
750
  id: chunk.approvalId,
751
+ ...(chunk.approvalDescriptor != null
752
+ ? { descriptor: chunk.approvalDescriptor }
753
+ : {}),
751
754
  ...(chunk.reason != null
752
755
  ? { requestReason: chunk.reason }
753
756
  : {}),
@@ -771,16 +774,10 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
771
774
 
772
775
  toolInvocation.state = 'approval-responded';
773
776
  toolInvocation.approval = {
777
+ ...approval,
774
778
  id: chunk.approvalId,
775
779
  approved: chunk.approved,
776
- ...(approval.requestReason != null
777
- ? { requestReason: approval.requestReason }
778
- : {}),
779
780
  ...(chunk.reason != null ? { reason: chunk.reason } : {}),
780
- ...(approval.isAutomatic === true ? { isAutomatic: true } : {}),
781
- ...(approval.signature != null
782
- ? { signature: approval.signature }
783
- : {}),
784
781
  };
785
782
  if (chunk.providerExecuted != null) {
786
783
  toolInvocation.providerExecuted = chunk.providerExecuted;
@@ -319,6 +319,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
319
319
  approval: {
320
320
  id: string;
321
321
  approved?: never;
322
+ descriptor?: unknown;
322
323
  requestReason?: string;
323
324
  reason?: never;
324
325
  isAutomatic?: boolean;
@@ -334,6 +335,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
334
335
  approval: {
335
336
  id: string;
336
337
  approved: boolean;
338
+ descriptor?: unknown;
337
339
  requestReason?: string;
338
340
  reason?: string;
339
341
  isAutomatic?: boolean;
@@ -351,6 +353,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
351
353
  approval?: {
352
354
  id: string;
353
355
  approved: true;
356
+ descriptor?: unknown;
354
357
  requestReason?: string;
355
358
  reason?: string;
356
359
  isAutomatic?: boolean;
@@ -368,6 +371,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
368
371
  approval?: {
369
372
  id: string;
370
373
  approved: true;
374
+ descriptor?: unknown;
371
375
  requestReason?: string;
372
376
  reason?: string;
373
377
  isAutomatic?: boolean;
@@ -383,6 +387,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
383
387
  approval: {
384
388
  id: string;
385
389
  approved: false;
390
+ descriptor?: unknown;
386
391
  requestReason?: string;
387
392
  reason?: string;
388
393
  isAutomatic?: boolean;
@@ -442,6 +447,7 @@ export type DynamicToolUIPart = {
442
447
  approval: {
443
448
  id: string;
444
449
  approved?: never;
450
+ descriptor?: unknown;
445
451
  requestReason?: string;
446
452
  reason?: never;
447
453
  isAutomatic?: boolean;
@@ -457,6 +463,7 @@ export type DynamicToolUIPart = {
457
463
  approval: {
458
464
  id: string;
459
465
  approved: boolean;
466
+ descriptor?: unknown;
460
467
  requestReason?: string;
461
468
  reason?: string;
462
469
  isAutomatic?: boolean;
@@ -474,6 +481,7 @@ export type DynamicToolUIPart = {
474
481
  approval?: {
475
482
  id: string;
476
483
  approved: true;
484
+ descriptor?: unknown;
477
485
  requestReason?: string;
478
486
  reason?: string;
479
487
  isAutomatic?: boolean;
@@ -490,6 +498,7 @@ export type DynamicToolUIPart = {
490
498
  approval?: {
491
499
  id: string;
492
500
  approved: true;
501
+ descriptor?: unknown;
493
502
  requestReason?: string;
494
503
  reason?: string;
495
504
  isAutomatic?: boolean;
@@ -505,6 +514,7 @@ export type DynamicToolUIPart = {
505
514
  approval: {
506
515
  id: string;
507
516
  approved: false;
517
+ descriptor?: unknown;
508
518
  requestReason?: string;
509
519
  reason?: string;
510
520
  isAutomatic?: boolean;
@@ -153,6 +153,7 @@ const uiMessagesSchema = lazySchema(() =>
153
153
  approval: z.object({
154
154
  id: z.string(),
155
155
  approved: z.never().optional(),
156
+ descriptor: z.unknown().optional(),
156
157
  requestReason: z.string().optional(),
157
158
  reason: z.never().optional(),
158
159
  isAutomatic: z.boolean().optional(),
@@ -173,6 +174,7 @@ const uiMessagesSchema = lazySchema(() =>
173
174
  approval: z.object({
174
175
  id: z.string(),
175
176
  approved: z.boolean(),
177
+ descriptor: z.unknown().optional(),
176
178
  requestReason: z.string().optional(),
177
179
  reason: z.string().optional(),
178
180
  isAutomatic: z.boolean().optional(),
@@ -196,6 +198,7 @@ const uiMessagesSchema = lazySchema(() =>
196
198
  .object({
197
199
  id: z.string(),
198
200
  approved: z.literal(true),
201
+ descriptor: z.unknown().optional(),
199
202
  requestReason: z.string().optional(),
200
203
  reason: z.string().optional(),
201
204
  isAutomatic: z.boolean().optional(),
@@ -220,6 +223,7 @@ const uiMessagesSchema = lazySchema(() =>
220
223
  .object({
221
224
  id: z.string(),
222
225
  approved: z.literal(true),
226
+ descriptor: z.unknown().optional(),
223
227
  requestReason: z.string().optional(),
224
228
  reason: z.string().optional(),
225
229
  isAutomatic: z.boolean().optional(),
@@ -241,6 +245,7 @@ const uiMessagesSchema = lazySchema(() =>
241
245
  approval: z.object({
242
246
  id: z.string(),
243
247
  approved: z.literal(false),
248
+ descriptor: z.unknown().optional(),
244
249
  requestReason: z.string().optional(),
245
250
  reason: z.string().optional(),
246
251
  isAutomatic: z.boolean().optional(),
@@ -284,6 +289,7 @@ const uiMessagesSchema = lazySchema(() =>
284
289
  approval: z.object({
285
290
  id: z.string(),
286
291
  approved: z.never().optional(),
292
+ descriptor: z.unknown().optional(),
287
293
  requestReason: z.string().optional(),
288
294
  reason: z.never().optional(),
289
295
  isAutomatic: z.boolean().optional(),
@@ -303,6 +309,7 @@ const uiMessagesSchema = lazySchema(() =>
303
309
  approval: z.object({
304
310
  id: z.string(),
305
311
  approved: z.boolean(),
312
+ descriptor: z.unknown().optional(),
306
313
  requestReason: z.string().optional(),
307
314
  reason: z.string().optional(),
308
315
  isAutomatic: z.boolean().optional(),
@@ -325,6 +332,7 @@ const uiMessagesSchema = lazySchema(() =>
325
332
  .object({
326
333
  id: z.string(),
327
334
  approved: z.literal(true),
335
+ descriptor: z.unknown().optional(),
328
336
  requestReason: z.string().optional(),
329
337
  reason: z.string().optional(),
330
338
  isAutomatic: z.boolean().optional(),
@@ -348,6 +356,7 @@ const uiMessagesSchema = lazySchema(() =>
348
356
  .object({
349
357
  id: z.string(),
350
358
  approved: z.literal(true),
359
+ descriptor: z.unknown().optional(),
351
360
  requestReason: z.string().optional(),
352
361
  reason: z.string().optional(),
353
362
  isAutomatic: z.boolean().optional(),
@@ -368,6 +377,7 @@ const uiMessagesSchema = lazySchema(() =>
368
377
  approval: z.object({
369
378
  id: z.string(),
370
379
  approved: z.literal(false),
380
+ descriptor: z.unknown().optional(),
371
381
  requestReason: z.string().optional(),
372
382
  reason: z.string().optional(),
373
383
  isAutomatic: z.boolean().optional(),
@@ -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
+ approvalDescriptor: z.unknown().optional(),
88
89
  reason: z.string().optional(),
89
90
  isAutomatic: z.boolean().optional(),
90
91
  signature: z.string().optional(),
@@ -299,6 +300,7 @@ export type UIMessageChunk<
299
300
  type: 'tool-approval-request';
300
301
  approvalId: string;
301
302
  toolCallId: string;
303
+ approvalDescriptor?: unknown;
302
304
  reason?: string;
303
305
  isAutomatic?: boolean;
304
306
  signature?: string;