ai 7.0.134 → 7.0.135
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 +16 -0
- package/dist/index.d.ts +232 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +592 -42
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +4 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-agents/04-loop-control.mdx +2 -0
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +1 -1
- package/docs/03-ai-sdk-core/30-embeddings.mdx +27 -0
- package/docs/04-ai-sdk-ui/21-transport.mdx +73 -0
- package/docs/07-reference/01-ai-sdk-core/05-embed.mdx +14 -0
- package/docs/07-reference/01-ai-sdk-core/06-embed-many.mdx +14 -0
- package/docs/07-reference/02-ai-sdk-ui/51-websocket-chat-transport.mdx +393 -0
- package/docs/07-reference/02-ai-sdk-ui/index.mdx +6 -0
- package/package.json +12 -12
- package/src/agent/default-stop-condition.ts +33 -0
- package/src/agent/tool-loop-agent.ts +2 -2
- package/src/embed/embed-events.ts +6 -0
- package/src/embed/embed-many.ts +18 -0
- package/src/embed/embed.ts +16 -0
- package/src/embed/validate-embedding-dimensions.ts +14 -0
- package/src/ui/chat-transport.ts +16 -0
- package/src/ui/chat.ts +123 -34
- package/src/ui/index.ts +13 -0
- package/src/ui/process-ui-message-stream.ts +6 -2
- package/src/ui/websocket-chat-transport.ts +987 -0
- package/src/util/serial-job-executor.ts +22 -5
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WebSocketChatTransport
|
|
3
|
+
description: API Reference for the WebSocketChatTransport class.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `WebSocketChatTransport`
|
|
7
|
+
|
|
8
|
+
A [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation that sends chat
|
|
9
|
+
requests and receives `UIMessageChunk` streams over one persistent WebSocket
|
|
10
|
+
connection.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { useChat } from '@ai-sdk/react';
|
|
14
|
+
import { WebSocketChatTransport } from 'ai';
|
|
15
|
+
import { useMemo } from 'react';
|
|
16
|
+
|
|
17
|
+
export default function Chat() {
|
|
18
|
+
const transport = useMemo(
|
|
19
|
+
() => new WebSocketChatTransport({ url: '/api/chat' }),
|
|
20
|
+
[],
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const { messages, sendMessage, stop } = useChat({ transport });
|
|
24
|
+
|
|
25
|
+
// ... render the chat
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Import
|
|
30
|
+
|
|
31
|
+
<Snippet text={`import { WebSocketChatTransport } from "ai"`} prompt={false} />
|
|
32
|
+
|
|
33
|
+
## Constructor
|
|
34
|
+
|
|
35
|
+
<PropertiesTable
|
|
36
|
+
content={[
|
|
37
|
+
{
|
|
38
|
+
name: 'url',
|
|
39
|
+
type: 'string',
|
|
40
|
+
isRequired: true,
|
|
41
|
+
description:
|
|
42
|
+
'The WebSocket endpoint. Relative URLs are resolved against the browser location. HTTP and HTTPS URLs are converted to WS and WSS.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'protocols',
|
|
46
|
+
type: 'string | string[]',
|
|
47
|
+
isOptional: true,
|
|
48
|
+
description: 'WebSocket subprotocols sent during the handshake.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'params',
|
|
52
|
+
type: 'Resolvable<Record<string, string>>',
|
|
53
|
+
isOptional: true,
|
|
54
|
+
description:
|
|
55
|
+
'Query parameters resolved and appended whenever a new connection is opened.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'headers',
|
|
59
|
+
type: 'Resolvable<Record<string, string> | Headers>',
|
|
60
|
+
isOptional: true,
|
|
61
|
+
description:
|
|
62
|
+
'Headers included inside each application-level send or resume frame. These are not WebSocket handshake headers.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'body',
|
|
66
|
+
type: 'Resolvable<object>',
|
|
67
|
+
isOptional: true,
|
|
68
|
+
description:
|
|
69
|
+
'Additional application data merged with per-request body data and included in send and resume frames.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'webSocket',
|
|
73
|
+
type: 'WebSocketConstructor',
|
|
74
|
+
isOptional: true,
|
|
75
|
+
description:
|
|
76
|
+
'A custom WebSocket constructor. Use this for Node.js WebSocket implementations or tests. Defaults to globalThis.WebSocket.',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'prepareSendMessagesRequest',
|
|
80
|
+
type: 'PrepareWebSocketChatTransportSendMessagesRequest',
|
|
81
|
+
isOptional: true,
|
|
82
|
+
description:
|
|
83
|
+
'Transforms the messages, headers, or body before a send frame is transmitted.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'prepareReconnectToStreamRequest',
|
|
87
|
+
type: 'PrepareWebSocketChatTransportReconnectToStreamRequest',
|
|
88
|
+
isOptional: true,
|
|
89
|
+
description:
|
|
90
|
+
'Transforms the headers or body before a resume frame is transmitted.',
|
|
91
|
+
},
|
|
92
|
+
]}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
`headers` supplied to `sendMessage`, `regenerate`, or an automatic tool
|
|
96
|
+
follow-up override transport-level headers with the same lower-cased name.
|
|
97
|
+
Per-request `body` properties override transport-level body properties.
|
|
98
|
+
Preparation callbacks receive the merged values; values they return replace
|
|
99
|
+
the corresponding merged value.
|
|
100
|
+
|
|
101
|
+
## Methods
|
|
102
|
+
|
|
103
|
+
### `sendMessages()`
|
|
104
|
+
|
|
105
|
+
Opens the socket when necessary, sends a `send` frame, and returns the
|
|
106
|
+
correlated `ReadableStream<UIMessageChunk>`. Multiple requests can share the
|
|
107
|
+
same connection and are routed by `requestId`.
|
|
108
|
+
|
|
109
|
+
### `reconnectToStream()`
|
|
110
|
+
|
|
111
|
+
Sends a `resume` frame. The promise resolves as follows:
|
|
112
|
+
|
|
113
|
+
- `start`, the first `chunk`, or `end` resolves to a readable stream.
|
|
114
|
+
- `no-active` resolves to `null`.
|
|
115
|
+
- `error`, cancellation, or a connection failure rejects the promise if no
|
|
116
|
+
stream has been returned yet.
|
|
117
|
+
|
|
118
|
+
Servers should send `start` as soon as they confirm that a resumable stream is
|
|
119
|
+
active, then replay the retained response from sequence zero. The transport
|
|
120
|
+
omits `lastSequence` because Chat starts a fresh message parser when resuming:
|
|
121
|
+
text, reasoning, and tool deltas need their preceding chunks. Retain and replay
|
|
122
|
+
the original UI `start` chunk with a stable `messageId` so Chat replaces the
|
|
123
|
+
partial assistant message instead of appending another one. The transport
|
|
124
|
+
rejects gaps and ignores duplicate sequence numbers within each response
|
|
125
|
+
stream, including the replay.
|
|
126
|
+
|
|
127
|
+
### `close()`
|
|
128
|
+
|
|
129
|
+
Closes the current socket and errors active streams. A later send or resume
|
|
130
|
+
request opens a new connection.
|
|
131
|
+
|
|
132
|
+
The code that creates a transport owns its lifecycle. React and Vue `useChat`
|
|
133
|
+
do not close caller-supplied transports during replacement or cleanup, because
|
|
134
|
+
one transport can be shared by multiple chats. Call `close()` after its final
|
|
135
|
+
consumer is gone.
|
|
136
|
+
|
|
137
|
+
If a directly managed `Chat` instance exclusively owns the transport, call
|
|
138
|
+
`dispose()`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
await chat.dispose();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For a shared transport, stop or discard individual chats without disposing
|
|
145
|
+
their shared transport, then call `transport.close()` after all consumers are
|
|
146
|
+
finished.
|
|
147
|
+
|
|
148
|
+
## Wire Protocol
|
|
149
|
+
|
|
150
|
+
Each frame is a JSON object with a `type` and a correlated `requestId`.
|
|
151
|
+
|
|
152
|
+
### Client-to-server frames
|
|
153
|
+
|
|
154
|
+
| Type | Purpose |
|
|
155
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `send` | Starts a new or regenerated turn and includes `id`, `messages`, `trigger`, `messageId`, request `headers`, `body`, and `metadata`. |
|
|
157
|
+
| `resume` | Requests the retained response for a chat `id`, with request `headers`, `body`, and `metadata`. `lastSequence` is omitted to request a complete replay. |
|
|
158
|
+
| `abort` | Cancels the operation identified by `requestId`. |
|
|
159
|
+
|
|
160
|
+
Use `WebSocketChatTransportRequest<UI_MESSAGE>`,
|
|
161
|
+
`WebSocketChatTransportSendRequest`, `WebSocketChatTransportResumeRequest`,
|
|
162
|
+
and `WebSocketChatTransportAbortRequest` to type these frames.
|
|
163
|
+
|
|
164
|
+
### Server-to-client frames
|
|
165
|
+
|
|
166
|
+
| Type | Purpose |
|
|
167
|
+
| ----------- | ----------------------------------------------------------------------------- |
|
|
168
|
+
| `start` | Confirms that a resume request has an active stream. |
|
|
169
|
+
| `chunk` | Carries a monotonic `sequence` and one valid `UIMessageChunk` in `chunk`. |
|
|
170
|
+
| `end` | Closes the correlated response stream. |
|
|
171
|
+
| `error` | Errors the correlated stream with the optional `errorText`. |
|
|
172
|
+
| `no-active` | Resolves a resume request to `null`; for a send request it closes the stream. |
|
|
173
|
+
|
|
174
|
+
Use `WebSocketChatTransportResponse` to type server frames.
|
|
175
|
+
|
|
176
|
+
Responses for unknown or completed request IDs are ignored. Malformed frames,
|
|
177
|
+
unknown response types, and invalid UI message chunks are treated as protocol
|
|
178
|
+
errors: the connection is closed and all active streams are errored.
|
|
179
|
+
|
|
180
|
+
Validate every untrusted client frame before using its fields:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const validated = await safeValidateWebSocketChatTransportRequest({
|
|
184
|
+
value: parsed.value,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!validated.success) {
|
|
188
|
+
socket.close(1008, 'Invalid frame');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const frame = validated.data;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Server Example
|
|
196
|
+
|
|
197
|
+
The server owns authentication, authorization, message validation, model
|
|
198
|
+
execution, resumable stream storage, and cancellation. The following Node.js
|
|
199
|
+
outline forwards a `streamText` UI message stream:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
203
|
+
import {
|
|
204
|
+
convertToModelMessages,
|
|
205
|
+
safeValidateWebSocketChatTransportRequest,
|
|
206
|
+
streamText,
|
|
207
|
+
toUIMessageStream,
|
|
208
|
+
validateUIMessages,
|
|
209
|
+
type UIMessage,
|
|
210
|
+
type UIMessageChunk,
|
|
211
|
+
type WebSocketChatTransportResponse,
|
|
212
|
+
} from 'ai';
|
|
213
|
+
import { generateId, safeParseJSON } from '@ai-sdk/provider-utils';
|
|
214
|
+
__PROVIDER_IMPORT__;
|
|
215
|
+
|
|
216
|
+
const server = new WebSocketServer({ port: 8080 });
|
|
217
|
+
|
|
218
|
+
type RetainedStream = {
|
|
219
|
+
abortController: AbortController;
|
|
220
|
+
chunks: Array<{ sequence: number; chunk: UIMessageChunk }>;
|
|
221
|
+
subscribers: Map<WebSocket, string>;
|
|
222
|
+
ended: boolean;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Scope retained streams to an authenticated user. Use durable storage when
|
|
226
|
+
// reconnects can land in another process.
|
|
227
|
+
const streamsByUser = new Map<string, Map<string, RetainedStream>>();
|
|
228
|
+
|
|
229
|
+
server.on('connection', (socket, request) => {
|
|
230
|
+
const userId = authenticateAndValidateOrigin(request);
|
|
231
|
+
const streams =
|
|
232
|
+
streamsByUser.get(userId) ?? new Map<string, RetainedStream>();
|
|
233
|
+
streamsByUser.set(userId, streams);
|
|
234
|
+
|
|
235
|
+
socket.on('message', async data => {
|
|
236
|
+
const parsed = await safeParseJSON({ text: data.toString() });
|
|
237
|
+
if (!parsed.success) {
|
|
238
|
+
socket.close(1008, 'Invalid frame');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const validated =
|
|
243
|
+
await safeValidateWebSocketChatTransportRequest<UIMessage>({
|
|
244
|
+
value: parsed.value,
|
|
245
|
+
});
|
|
246
|
+
if (!validated.success) {
|
|
247
|
+
socket.close(1008, 'Invalid frame');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const frame = validated.data;
|
|
251
|
+
|
|
252
|
+
const send = (response: WebSocketChatTransportResponse) => {
|
|
253
|
+
socket.send(JSON.stringify(response));
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
if (frame.type === 'abort') {
|
|
257
|
+
for (const stream of streams.values()) {
|
|
258
|
+
if (stream.subscribers.get(socket) === frame.requestId) {
|
|
259
|
+
stream.subscribers.delete(socket);
|
|
260
|
+
stream.abortController.abort();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (frame.type === 'resume') {
|
|
267
|
+
const stream = streams.get(frame.id);
|
|
268
|
+
if (stream == null) {
|
|
269
|
+
send({ type: 'no-active', requestId: frame.requestId });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
send({ type: 'start', requestId: frame.requestId });
|
|
274
|
+
if (!stream.ended) {
|
|
275
|
+
stream.subscribers.set(socket, frame.requestId);
|
|
276
|
+
}
|
|
277
|
+
for (const stored of stream.chunks) {
|
|
278
|
+
if (stored.sequence > (frame.lastSequence ?? -1)) {
|
|
279
|
+
send({
|
|
280
|
+
type: 'chunk',
|
|
281
|
+
requestId: frame.requestId,
|
|
282
|
+
...stored,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (stream.ended) {
|
|
287
|
+
send({ type: 'end', requestId: frame.requestId });
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (streams.get(frame.id)?.ended === false) {
|
|
293
|
+
send({
|
|
294
|
+
type: 'error',
|
|
295
|
+
requestId: frame.requestId,
|
|
296
|
+
errorText: 'A response is already active for this chat.',
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const abortController = new AbortController();
|
|
302
|
+
const stream: RetainedStream = {
|
|
303
|
+
abortController,
|
|
304
|
+
chunks: [],
|
|
305
|
+
subscribers: new Map([[socket, frame.requestId]]),
|
|
306
|
+
ended: false,
|
|
307
|
+
};
|
|
308
|
+
streams.set(frame.id, stream);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const messages = await validateUIMessages({
|
|
312
|
+
messages: frame.messages,
|
|
313
|
+
});
|
|
314
|
+
const result = streamText({
|
|
315
|
+
model: __MODEL__,
|
|
316
|
+
messages: await convertToModelMessages(messages),
|
|
317
|
+
abortSignal: abortController.signal,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
for await (const chunk of toUIMessageStream({
|
|
321
|
+
stream: result.stream,
|
|
322
|
+
originalMessages: messages,
|
|
323
|
+
generateMessageId: generateId,
|
|
324
|
+
})) {
|
|
325
|
+
const stored = { sequence: stream.chunks.length, chunk };
|
|
326
|
+
stream.chunks.push(stored);
|
|
327
|
+
for (const [subscriber, requestId] of stream.subscribers) {
|
|
328
|
+
subscriber.send(
|
|
329
|
+
JSON.stringify({ type: 'chunk', requestId, ...stored }),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
stream.ended = true;
|
|
334
|
+
for (const [subscriber, requestId] of stream.subscribers) {
|
|
335
|
+
subscriber.send(JSON.stringify({ type: 'end', requestId }));
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
// Failed responses cannot resume. Remove the retained entry so a retry
|
|
339
|
+
// can start a new response and resume requests receive no-active.
|
|
340
|
+
streams.delete(frame.id);
|
|
341
|
+
// Log the internal error server-side, but do not expose it to the client.
|
|
342
|
+
console.error(error);
|
|
343
|
+
for (const [subscriber, requestId] of stream.subscribers) {
|
|
344
|
+
subscriber.send(
|
|
345
|
+
JSON.stringify({
|
|
346
|
+
type: 'error',
|
|
347
|
+
requestId,
|
|
348
|
+
errorText: 'Chat request failed.',
|
|
349
|
+
}),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
} finally {
|
|
353
|
+
stream.subscribers.clear();
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
socket.on('close', () => {
|
|
358
|
+
for (const stream of streams.values()) {
|
|
359
|
+
stream.subscribers.delete(socket);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
This outline keeps completed chunks in memory to make the replay behavior
|
|
366
|
+
clear. Production servers should expire completed streams and store resumable
|
|
367
|
+
state durably when reconnects can reach another process. If response setup or
|
|
368
|
+
streaming throws, the outline removes that response from retained state before
|
|
369
|
+
reporting the error. A later resume returns `no-active`, and a new send can retry
|
|
370
|
+
the chat.
|
|
371
|
+
|
|
372
|
+
## Authentication and Deployment
|
|
373
|
+
|
|
374
|
+
- Use WSS in production.
|
|
375
|
+
- Browser WebSockets cannot set arbitrary handshake headers. Prefer secure
|
|
376
|
+
cookies, short-lived query tokens, or subprotocols.
|
|
377
|
+
- Validate the handshake origin, authorize chat IDs, validate every frame and
|
|
378
|
+
UI message, limit frame sizes, and rate-limit connections.
|
|
379
|
+
- Do not depend on process memory for reconnection when your deployment can
|
|
380
|
+
restart or move the connection to another process.
|
|
381
|
+
- The transport does not automatically replay interrupted `send` requests,
|
|
382
|
+
because replay can duplicate model output or tool side effects.
|
|
383
|
+
|
|
384
|
+
`WebSocketChatTransport` is separate from the experimental Realtime APIs.
|
|
385
|
+
Realtime transports implement provider realtime sessions; this transport
|
|
386
|
+
carries the provider-independent AI SDK UI message protocol used by `useChat`.
|
|
387
|
+
|
|
388
|
+
## Client-side Tool Follow-ups
|
|
389
|
+
|
|
390
|
+
When `sendAutomaticallyWhen` triggers after client-side tool output is added,
|
|
391
|
+
the next turn reuses the same WebSocket. This avoids another HTTP connection,
|
|
392
|
+
but the current `ChatTransport` contract remains turn-based: the tool output is
|
|
393
|
+
not injected into a model generation that is still active.
|
|
@@ -70,6 +70,12 @@ AI SDK UI contains the following hooks:
|
|
|
70
70
|
'Transform a stream of UIMessageChunk objects into an AsyncIterableStream of UIMessage objects.',
|
|
71
71
|
href: '/docs/reference/ai-sdk-ui/read-ui-message-stream',
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
title: 'WebSocketChatTransport',
|
|
75
|
+
description:
|
|
76
|
+
'Send correlated chat requests and UI message streams over a persistent WebSocket connection.',
|
|
77
|
+
href: '/docs/reference/ai-sdk-ui/websocket-chat-transport',
|
|
78
|
+
},
|
|
73
79
|
{
|
|
74
80
|
title: 'experimental_MCPAppRenderer',
|
|
75
81
|
description: 'Render interactive MCP App tool UIs in React.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.135",
|
|
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,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
46
|
-
"@ai-sdk/provider": "4.0.
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.109",
|
|
46
|
+
"@ai-sdk/provider": "4.0.26",
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.58"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.113",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.63",
|
|
52
|
+
"@ai-sdk/google": "4.0.93",
|
|
53
|
+
"@ai-sdk/groq": "4.0.59",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.67",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.67",
|
|
56
|
+
"@ai-sdk/openai": "4.0.91",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.4",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.20",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { logWarnings } from '../logger/log-warnings';
|
|
2
|
+
import type { StopCondition } from '../generate-text/stop-condition';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stop conditions are only evaluated when the tool loop can otherwise continue,
|
|
6
|
+
* so logging here avoids warning when a call finishes naturally at the limit.
|
|
7
|
+
*/
|
|
8
|
+
export function createDefaultStopCondition(
|
|
9
|
+
stepCount: number,
|
|
10
|
+
): StopCondition<any, any> {
|
|
11
|
+
return ({ steps }) => {
|
|
12
|
+
if (steps.length !== stepCount) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { provider, modelId } = steps[steps.length - 1].model;
|
|
17
|
+
logWarnings({
|
|
18
|
+
provider,
|
|
19
|
+
model: modelId,
|
|
20
|
+
warnings: [
|
|
21
|
+
{
|
|
22
|
+
type: 'other',
|
|
23
|
+
message:
|
|
24
|
+
`The tool loop stopped because it reached the default stopWhen condition, isStepCount(${stepCount}). ` +
|
|
25
|
+
'To allow more steps, set stopWhen to isStepCount(...) with a higher limit or provide a custom stop condition. ' +
|
|
26
|
+
'Learn more: https://ai-sdk.dev/docs/agents/loop-control',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
} from '../generate-text/generate-text-events';
|
|
14
14
|
import type { GenerateTextResult } from '../generate-text/generate-text-result';
|
|
15
15
|
import type { Output } from '../generate-text/output';
|
|
16
|
-
import { isStepCount } from '../generate-text/stop-condition';
|
|
17
16
|
import { streamText } from '../generate-text/stream-text';
|
|
18
17
|
import type { StreamTextResult } from '../generate-text/stream-text-result';
|
|
19
18
|
import type { Prompt } from '../prompt';
|
|
@@ -24,6 +23,7 @@ import type {
|
|
|
24
23
|
AgentStreamParameters,
|
|
25
24
|
} from './agent';
|
|
26
25
|
import type { ToolLoopAgentSettings } from './tool-loop-agent-settings';
|
|
26
|
+
import { createDefaultStopCondition } from './default-stop-condition';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* A tool loop agent is an agent that runs tools in a loop. In each step,
|
|
@@ -129,7 +129,7 @@ export class ToolLoopAgent<
|
|
|
129
129
|
|
|
130
130
|
const baseCallArgs = {
|
|
131
131
|
...settingsWithoutCallbacks,
|
|
132
|
-
stopWhen: this.settings.stopWhen ??
|
|
132
|
+
stopWhen: this.settings.stopWhen ?? createDefaultStopCondition(20),
|
|
133
133
|
...options,
|
|
134
134
|
};
|
|
135
135
|
|
|
@@ -27,6 +27,9 @@ export type EmbedStartEvent<RUNTIME_CONTEXT extends Context = Context> = {
|
|
|
27
27
|
/** The value(s) being embedded. A string for embed, an array for embedMany. */
|
|
28
28
|
readonly value: string | Array<string>;
|
|
29
29
|
|
|
30
|
+
/** Requested output dimensions, when specified. */
|
|
31
|
+
readonly dimensions?: number;
|
|
32
|
+
|
|
30
33
|
/** Maximum number of retries for failed requests. */
|
|
31
34
|
readonly maxRetries: number;
|
|
32
35
|
|
|
@@ -104,6 +107,9 @@ export type EmbeddingModelCallStartEvent = {
|
|
|
104
107
|
|
|
105
108
|
/** The values being embedded in this particular model call. */
|
|
106
109
|
readonly values: Array<string>;
|
|
110
|
+
|
|
111
|
+
/** Requested output dimensions, when specified. */
|
|
112
|
+
readonly dimensions?: number;
|
|
107
113
|
};
|
|
108
114
|
|
|
109
115
|
/**
|
package/src/embed/embed-many.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { notify } from '../util/notify';
|
|
|
20
20
|
import { prepareRetries } from '../util/prepare-retries';
|
|
21
21
|
import { splitArray } from '../util/split-array';
|
|
22
22
|
import type { EmbedEndEvent, EmbedStartEvent } from './embed-events';
|
|
23
|
+
import { validateEmbeddingDimensions } from './validate-embedding-dimensions';
|
|
23
24
|
import type { EmbedManyResult } from './embed-many-result';
|
|
24
25
|
import { VERSION } from '../version';
|
|
25
26
|
|
|
@@ -39,6 +40,8 @@ const originalGenerateCallId = createIdGenerator({
|
|
|
39
40
|
* @param model - The embedding model to use.
|
|
40
41
|
* @param values - The values that should be embedded.
|
|
41
42
|
*
|
|
43
|
+
* @param dimensions - Requested output dimensions. Must be a positive integer. Requires provider support.
|
|
44
|
+
*
|
|
42
45
|
* @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
|
|
43
46
|
* @param abortSignal - An optional abort signal that can be used to cancel the call.
|
|
44
47
|
* @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
|
|
@@ -59,6 +62,7 @@ const originalGenerateCallId = createIdGenerator({
|
|
|
59
62
|
export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
60
63
|
model: modelArg,
|
|
61
64
|
values,
|
|
65
|
+
dimensions,
|
|
62
66
|
maxParallelCalls = Infinity,
|
|
63
67
|
maxRetries: maxRetriesArg,
|
|
64
68
|
abortSignal,
|
|
@@ -83,6 +87,13 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
83
87
|
*/
|
|
84
88
|
values: Array<string>;
|
|
85
89
|
|
|
90
|
+
/**
|
|
91
|
+
* The requested number of dimensions for the output embeddings.
|
|
92
|
+
* Must be a positive integer. Support and allowed values depend on the model
|
|
93
|
+
* and provider implementation.
|
|
94
|
+
*/
|
|
95
|
+
dimensions?: number;
|
|
96
|
+
|
|
86
97
|
/**
|
|
87
98
|
* Maximum number of retries per embedding model call. Set to 0 to disable retries.
|
|
88
99
|
*
|
|
@@ -168,6 +179,8 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
168
179
|
generateCallId?: () => string;
|
|
169
180
|
};
|
|
170
181
|
}): Promise<EmbedManyResult> {
|
|
182
|
+
validateEmbeddingDimensions(dimensions);
|
|
183
|
+
|
|
171
184
|
const model = resolveEmbeddingModel(modelArg);
|
|
172
185
|
|
|
173
186
|
const { maxRetries, retry } = prepareRetries({
|
|
@@ -200,6 +213,7 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
200
213
|
provider: model.provider,
|
|
201
214
|
modelId: model.modelId,
|
|
202
215
|
value: values,
|
|
216
|
+
dimensions,
|
|
203
217
|
maxRetries,
|
|
204
218
|
headers: headersWithUserAgent,
|
|
205
219
|
providerOptions,
|
|
@@ -243,12 +257,14 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
243
257
|
provider: model.provider,
|
|
244
258
|
modelId: model.modelId,
|
|
245
259
|
values,
|
|
260
|
+
dimensions,
|
|
246
261
|
},
|
|
247
262
|
callbacks: [telemetryDispatcher.onEmbedStart],
|
|
248
263
|
});
|
|
249
264
|
|
|
250
265
|
const modelResponse = await model.doEmbed({
|
|
251
266
|
values,
|
|
267
|
+
dimensions,
|
|
252
268
|
abortSignal,
|
|
253
269
|
headers: headersWithUserAgent,
|
|
254
270
|
providerOptions,
|
|
@@ -371,12 +387,14 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
371
387
|
provider: model.provider,
|
|
372
388
|
modelId: model.modelId,
|
|
373
389
|
values: chunk,
|
|
390
|
+
dimensions,
|
|
374
391
|
},
|
|
375
392
|
callbacks: [telemetryDispatcher.onEmbedStart],
|
|
376
393
|
});
|
|
377
394
|
|
|
378
395
|
const modelResponse = await model.doEmbed({
|
|
379
396
|
values: chunk,
|
|
397
|
+
dimensions,
|
|
380
398
|
abortSignal,
|
|
381
399
|
headers: headersWithUserAgent,
|
|
382
400
|
providerOptions: chunkProviderOptions,
|
package/src/embed/embed.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { notify } from '../util/notify';
|
|
|
15
15
|
import { prepareRetries } from '../util/prepare-retries';
|
|
16
16
|
import { VERSION } from '../version';
|
|
17
17
|
import type { EmbedEndEvent, EmbedStartEvent } from './embed-events';
|
|
18
|
+
import { validateEmbeddingDimensions } from './validate-embedding-dimensions';
|
|
18
19
|
import type { EmbedResult } from './embed-result';
|
|
19
20
|
|
|
20
21
|
const originalGenerateCallId = createIdGenerator({
|
|
@@ -28,6 +29,8 @@ const originalGenerateCallId = createIdGenerator({
|
|
|
28
29
|
* @param model - The embedding model to use.
|
|
29
30
|
* @param value - The value that should be embedded.
|
|
30
31
|
*
|
|
32
|
+
* @param dimensions - Requested output dimensions. Must be a positive integer. Requires provider support.
|
|
33
|
+
*
|
|
31
34
|
* @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
|
|
32
35
|
* @param abortSignal - An optional abort signal that can be used to cancel the call.
|
|
33
36
|
* @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
|
|
@@ -44,6 +47,7 @@ const originalGenerateCallId = createIdGenerator({
|
|
|
44
47
|
export async function embed<RUNTIME_CONTEXT extends Context = Context>({
|
|
45
48
|
model: modelArg,
|
|
46
49
|
value,
|
|
50
|
+
dimensions,
|
|
47
51
|
providerOptions,
|
|
48
52
|
maxRetries: maxRetriesArg,
|
|
49
53
|
abortSignal,
|
|
@@ -67,6 +71,13 @@ export async function embed<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
67
71
|
*/
|
|
68
72
|
value: string;
|
|
69
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The requested number of dimensions for the output embeddings.
|
|
76
|
+
* Must be a positive integer. Support and allowed values depend on the model
|
|
77
|
+
* and provider implementation.
|
|
78
|
+
*/
|
|
79
|
+
dimensions?: number;
|
|
80
|
+
|
|
70
81
|
/**
|
|
71
82
|
* Maximum number of retries per embedding model call. Set to 0 to disable retries.
|
|
72
83
|
*
|
|
@@ -144,6 +155,8 @@ export async function embed<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
144
155
|
generateCallId?: () => string;
|
|
145
156
|
};
|
|
146
157
|
}): Promise<EmbedResult> {
|
|
158
|
+
validateEmbeddingDimensions(dimensions);
|
|
159
|
+
|
|
147
160
|
const model = resolveEmbeddingModel(modelArg);
|
|
148
161
|
|
|
149
162
|
const { maxRetries, retry } = prepareRetries({
|
|
@@ -176,6 +189,7 @@ export async function embed<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
176
189
|
provider: model.provider,
|
|
177
190
|
modelId: model.modelId,
|
|
178
191
|
value,
|
|
192
|
+
dimensions,
|
|
179
193
|
maxRetries,
|
|
180
194
|
headers: headersWithUserAgent,
|
|
181
195
|
providerOptions,
|
|
@@ -203,12 +217,14 @@ export async function embed<RUNTIME_CONTEXT extends Context = Context>({
|
|
|
203
217
|
provider: model.provider,
|
|
204
218
|
modelId: model.modelId,
|
|
205
219
|
values: [value],
|
|
220
|
+
dimensions,
|
|
206
221
|
},
|
|
207
222
|
callbacks: [telemetryDispatcher.onEmbedStart],
|
|
208
223
|
});
|
|
209
224
|
|
|
210
225
|
const modelResponse = await model.doEmbed({
|
|
211
226
|
values: [value],
|
|
227
|
+
dimensions,
|
|
212
228
|
abortSignal,
|
|
213
229
|
headers: headersWithUserAgent,
|
|
214
230
|
providerOptions,
|