ai 7.0.35 → 7.0.37

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.
@@ -95,6 +95,7 @@ The open-source community has created the following providers:
95
95
  - [Zhipu (Z.AI) Provider](/providers/community-providers/zhipu) (`zhipu-ai-provider`)
96
96
  - [OLLM Provider](/providers/community-providers/ollm) (`@ofoundation/ollm`)
97
97
  - [ZeroEntropy Provider](/providers/community-providers/zeroentropy) (`zeroentropy-ai-provider`)
98
+ - [Neon AI Gateway Provider](/providers/community-providers/neon-ai-gateway) (`@neon/ai-sdk-provider`)
98
99
 
99
100
  ## Self-Hosted Models
100
101
 
@@ -128,6 +128,11 @@ There are three things worth mentioning:
128
128
  It asks the user for confirmation and displays the result once the user confirms or denies the execution.
129
129
  The result is added to the chat using `addToolOutput` with the `tool` parameter for type safety.
130
130
 
131
+ Typed tool parts also include the `approval-requested`, `approval-responded`,
132
+ and `output-denied` states. Include these states when handling `part.state`
133
+ exhaustively, even when a tool does not require approval. See
134
+ [Tool execution approval](#tool-execution-approval) for a complete approval UI.
135
+
131
136
  ```tsx filename='app/page.tsx' highlight="6,11,16,19-23,25-26,28-33,51,66-70,77-81"
132
137
  'use client';
133
138
 
@@ -217,6 +222,10 @@ export default function Chat() {
217
222
  </div>
218
223
  </div>
219
224
  );
225
+ case 'approval-requested':
226
+ return <div key={callId}>Approval requested.</div>;
227
+ case 'approval-responded':
228
+ return <div key={callId}>Approval response received.</div>;
220
229
  case 'output-available':
221
230
  return (
222
231
  <div key={callId}>
@@ -225,6 +234,8 @@ export default function Chat() {
225
234
  );
226
235
  case 'output-error':
227
236
  return <div key={callId}>Error: {part.errorText}</div>;
237
+ case 'output-denied':
238
+ return <div key={callId}>Tool call denied.</div>;
228
239
  }
229
240
  break;
230
241
  }
@@ -239,6 +250,10 @@ export default function Chat() {
239
250
  );
240
251
  case 'input-available':
241
252
  return <div key={callId}>Getting location...</div>;
253
+ case 'approval-requested':
254
+ return <div key={callId}>Approval requested.</div>;
255
+ case 'approval-responded':
256
+ return <div key={callId}>Approval response received.</div>;
242
257
  case 'output-available':
243
258
  return <div key={callId}>Location: {part.output}</div>;
244
259
  case 'output-error':
@@ -247,6 +262,8 @@ export default function Chat() {
247
262
  Error getting location: {part.errorText}
248
263
  </div>
249
264
  );
265
+ case 'output-denied':
266
+ return <div key={callId}>Location request denied.</div>;
250
267
  }
251
268
  break;
252
269
  }
@@ -266,6 +283,10 @@ export default function Chat() {
266
283
  Getting weather information for {part.input.city}...
267
284
  </div>
268
285
  );
286
+ case 'approval-requested':
287
+ return <div key={callId}>Approval requested.</div>;
288
+ case 'approval-responded':
289
+ return <div key={callId}>Approval response received.</div>;
269
290
  case 'output-available':
270
291
  return (
271
292
  <div key={callId}>
@@ -279,6 +300,8 @@ export default function Chat() {
279
300
  {part.errorText}
280
301
  </div>
281
302
  );
303
+ case 'output-denied':
304
+ return <div key={callId}>Weather request denied.</div>;
282
305
  }
283
306
  break;
284
307
  }
@@ -631,10 +654,16 @@ export default function Chat() {
631
654
  return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
632
655
  case 'input-available':
633
656
  return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
657
+ case 'approval-requested':
658
+ return <div>Approval requested.</div>;
659
+ case 'approval-responded':
660
+ return <div>Approval response received.</div>;
634
661
  case 'output-available':
635
662
  return <pre>{JSON.stringify(part.output, null, 2)}</pre>;
636
663
  case 'output-error':
637
664
  return <div>Error: {part.errorText}</div>;
665
+ case 'output-denied':
666
+ return <div>Tool call denied.</div>;
638
667
  }
639
668
  }
640
669
  })}
@@ -111,11 +111,13 @@ export default function Chat() {
111
111
  }
112
112
  ```
113
113
 
114
- #### Alternative: replace last message
114
+ #### Alternative: replace the failed message
115
115
 
116
- Alternatively you can write a custom submit handler that replaces the last message when an error is present.
116
+ Alternatively, you can write a custom submit handler that replaces the failed
117
+ user message with new input. If the assistant response started streaming before
118
+ the error, remove both the partial assistant response and its user message.
117
119
 
118
- ```tsx file="app/page.tsx" highlight="13-15,17-18,35"
120
+ ```tsx file="app/page.tsx" highlight="13-19,21-22,39"
119
121
  'use client';
120
122
 
121
123
  import { useChat } from '@ai-sdk/react';
@@ -129,7 +131,11 @@ export default function Chat() {
129
131
  event.preventDefault();
130
132
 
131
133
  if (error != null) {
132
- setMessages(messages.slice(0, -1)); // remove last message
134
+ setMessages(messages =>
135
+ messages.at(-1)?.role === 'assistant'
136
+ ? messages.slice(0, -2)
137
+ : messages.slice(0, -1),
138
+ );
133
139
  }
134
140
 
135
141
  sendMessage({ text: input });
@@ -525,6 +525,34 @@ The `unknown` finish reason has been removed. It is now returned as `other`.
525
525
 
526
526
  ## AI SDK UI
527
527
 
528
+ ### Tool UI Part Approval States
529
+
530
+ AI SDK 6 adds `approval-requested`, `approval-responded`, and `output-denied`
531
+ to the tool UI part `state` union. Update exhaustive `switch` statements and
532
+ other state handling to cover the three approval states.
533
+
534
+ ```tsx filename="AI SDK 6"
535
+ switch (part.state) {
536
+ case 'input-streaming':
537
+ return 'Loading input';
538
+ case 'input-available':
539
+ return 'Input ready';
540
+ case 'approval-requested':
541
+ return 'Approval requested';
542
+ case 'approval-responded':
543
+ return 'Approval response received';
544
+ case 'output-available':
545
+ return 'Output ready';
546
+ case 'output-error':
547
+ return part.errorText;
548
+ case 'output-denied':
549
+ return 'Tool call denied';
550
+ }
551
+ ```
552
+
553
+ See [Tool execution approval](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval)
554
+ for handling approval requests and responses in a chat UI.
555
+
528
556
  ### Tool UI Part Helper Functions Rename
529
557
 
530
558
  The tool UI part helper functions have been renamed to better reflect their purpose and to accommodate both static and dynamic tool parts ([PR #XXXX](https://github.com/vercel/ai/pull/XXXX)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.35",
3
+ "version": "7.0.37",
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,7 +42,7 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.27",
45
+ "@ai-sdk/gateway": "4.0.28",
46
46
  "@ai-sdk/provider": "4.0.3",
47
47
  "@ai-sdk/provider-utils": "5.0.12"
48
48
  },
@@ -18,11 +18,37 @@ async function importKey(secret: string | Uint8Array): Promise<CryptoKey> {
18
18
  );
19
19
  }
20
20
 
21
+ // Serialize with JSON so the encoding is injective: fields may contain any
22
+ // character (including newlines), and escaping + array structure keeps field
23
+ // boundaries unambiguous. The version prefix provides domain separation.
21
24
  function buildPayload(
22
25
  approvalId: string,
23
26
  toolCallId: string,
24
27
  toolName: string,
25
28
  inputDigest: string,
29
+ ): Uint8Array {
30
+ return encoder.encode(
31
+ JSON.stringify([
32
+ 'ai-sdk-tool-approval-v1',
33
+ approvalId,
34
+ toolCallId,
35
+ toolName,
36
+ inputDigest,
37
+ ]),
38
+ );
39
+ }
40
+
41
+ // Legacy newline-joined payload. Ambiguous when any field contains the `\n`
42
+ // delimiter, which is why it was replaced; only used as a verify-time fallback
43
+ // guarded on delimiter-free fields.
44
+ // TODO(#17494): remove in v8 when backwards compatibility with pre-JSON
45
+ // signatures (approvals signed before the injective format) is no longer
46
+ // needed.
47
+ function buildLegacyPayload(
48
+ approvalId: string,
49
+ toolCallId: string,
50
+ toolName: string,
51
+ inputDigest: string,
26
52
  ): Uint8Array {
27
53
  return encoder.encode(
28
54
  `${approvalId}\n${toolCallId}\n${toolName}\n${inputDigest}`,
@@ -66,9 +92,35 @@ export async function verifyToolApprovalSignature({
66
92
  }): Promise<boolean> {
67
93
  const key = await importKey(secret);
68
94
  const inputDigest = await hashCanonical(input);
69
- const payload = buildPayload(approvalId, toolCallId, toolName, inputDigest);
70
95
  const sigBytes = fromBase64url(signature);
71
- return crypto.subtle.verify('HMAC', key, sigBytes, payload);
96
+
97
+ const payload = buildPayload(approvalId, toolCallId, toolName, inputDigest);
98
+ if (await crypto.subtle.verify('HMAC', key, sigBytes, payload)) {
99
+ return true;
100
+ }
101
+
102
+ // Backwards compatibility: accept a signature produced by the legacy
103
+ // newline-joined format, but only when no field contains the `\n` delimiter
104
+ // — the exact condition that made that format ambiguous. This keeps the
105
+ // retupling collision closed (the attack requires a newline in a field)
106
+ // while still verifying benign approvals signed by an older version, e.g. a
107
+ // pending approval that straddles an upgrade.
108
+ // TODO(#17494): remove in v8 (drop buildLegacyPayload and this fallback).
109
+ if (
110
+ !approvalId.includes('\n') &&
111
+ !toolCallId.includes('\n') &&
112
+ !toolName.includes('\n')
113
+ ) {
114
+ const legacyPayload = buildLegacyPayload(
115
+ approvalId,
116
+ toolCallId,
117
+ toolName,
118
+ inputDigest,
119
+ );
120
+ return crypto.subtle.verify('HMAC', key, sigBytes, legacyPayload);
121
+ }
122
+
123
+ return false;
72
124
  }
73
125
 
74
126
  export async function maybeSignApproval({