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.
- package/CHANGELOG.md +26 -0
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +26 -3
- package/dist/internal/index.js.map +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +1 -0
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +29 -0
- package/docs/04-ai-sdk-ui/21-error-handling.mdx +10 -4
- package/docs/08-migration-guides/24-migration-guide-6-0.mdx +28 -0
- package/package.json +2 -2
- package/src/generate-text/tool-approval-signature.ts +54 -2
|
@@ -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
|
|
114
|
+
#### Alternative: replace the failed message
|
|
115
115
|
|
|
116
|
-
Alternatively you can write a custom submit handler that replaces the
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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({
|