ai 7.0.134 → 7.0.136

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.d.ts +234 -7
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +616 -57
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +6 -2
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +1 -1
  9. package/docs/03-agents/04-loop-control.mdx +2 -0
  10. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +2 -2
  11. package/docs/03-ai-sdk-core/17-mcp-events.mdx +6 -2
  12. package/docs/03-ai-sdk-core/25-settings.mdx +4 -2
  13. package/docs/03-ai-sdk-core/30-embeddings.mdx +27 -0
  14. package/docs/03-ai-sdk-core/32-decisions.mdx +3 -2
  15. package/docs/04-ai-sdk-ui/21-transport.mdx +73 -0
  16. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +2 -2
  17. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +2 -2
  18. package/docs/07-reference/01-ai-sdk-core/05-embed.mdx +14 -0
  19. package/docs/07-reference/01-ai-sdk-core/06-embed-many.mdx +14 -0
  20. package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +5 -5
  21. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +2 -2
  22. package/docs/07-reference/01-ai-sdk-core/24-mcp-events.mdx +123 -5
  23. package/docs/07-reference/02-ai-sdk-ui/51-websocket-chat-transport.mdx +393 -0
  24. package/docs/07-reference/02-ai-sdk-ui/index.mdx +6 -0
  25. package/package.json +12 -12
  26. package/src/agent/default-stop-condition.ts +33 -0
  27. package/src/agent/tool-loop-agent.ts +2 -2
  28. package/src/embed/embed-events.ts +6 -0
  29. package/src/embed/embed-many.ts +18 -0
  30. package/src/embed/embed.ts +16 -0
  31. package/src/embed/validate-embedding-dimensions.ts +14 -0
  32. package/src/generate-text/stream-text.ts +31 -24
  33. package/src/prompt/request-options.ts +2 -2
  34. package/src/ui/chat-transport.ts +16 -0
  35. package/src/ui/chat.ts +123 -34
  36. package/src/ui/index.ts +13 -0
  37. package/src/ui/process-ui-message-stream.ts +6 -2
  38. package/src/ui/websocket-chat-transport.ts +987 -0
  39. package/src/util/serial-job-executor.ts +22 -5
@@ -99,9 +99,11 @@ for the definition fields. Pagination is explicit; this method does not fetch al
99
99
  ## Managed Subscriptions
100
100
 
101
101
  Configure `experimental_events: { adapter }` to delegate subscription lifecycle
102
- operations to a backend. The adapter implements `Experimental_MCPEventsAdapter`
103
- from `@ai-sdk/mcp`; it is bound to the authorized account and destination by your
104
- application or integration package.
102
+ operations to a backend. The `adapter` configuration requires an
103
+ `Experimental_MCPEventAdapter` from `@ai-sdk/mcp`. Its
104
+ `createAdapter({ transport })` method returns `Experimental_MCPEventOperations` bound
105
+ to the authorized account, endpoint and destination by your application or
106
+ integration package.
105
107
 
106
108
  ```ts
107
109
  import { createMCPClient } from '@ai-sdk/mcp';
@@ -143,7 +145,123 @@ try {
143
145
  Use the same authorized account for discovery and subscription management.
144
146
  Never derive account identity, destination or authorization from model arguments.
145
147
 
146
- ### Adapter Contract
148
+ ### Using an Adapter Helper
149
+
150
+ A backend SDK or application helper can return `Experimental_MCPEventAdapter`
151
+ directly. Pass the helper's result as `adapter`; you do not need another object
152
+ wrapper or an inline `createAdapter` callback:
153
+
154
+ ```ts
155
+ import { createMCPClient } from '@ai-sdk/mcp';
156
+ import { createBackendEventsAdapter } from './my-events-adapter';
157
+
158
+ const client = await createMCPClient({
159
+ transport: { type: 'http', url: 'https://service.example/mcp' },
160
+ experimental_events: {
161
+ adapter: createBackendEventsAdapter({
162
+ accountId: authenticatedUser.id,
163
+ destination: { path: '/hooks' },
164
+ }),
165
+ },
166
+ });
167
+ ```
168
+
169
+ The helper holds the authorized account and delivery configuration. AI SDK supplies
170
+ the transport type and URL to its `createAdapter` method internally, so the application
171
+ configures that URL only once. When a backend SDK exports this helper, import it
172
+ from that SDK instead of an application module. Use the same authorized account
173
+ for the MCP transport and the backend's subscription operations.
174
+
175
+ ### Implementing an Adapter Helper
176
+
177
+ Backend integration authors implement `createAdapter` inside their helper:
178
+
179
+ ```ts filename="my-events-adapter.ts"
180
+ import type { Experimental_MCPEventAdapter } from '@ai-sdk/mcp';
181
+ import { createBackendEventOperations } from './my-events-backend';
182
+
183
+ type BackendEventsConfig = {
184
+ accountId: string;
185
+ destination: { path: string };
186
+ mcpUrl?: string;
187
+ };
188
+
189
+ export function createBackendEventsAdapter(
190
+ config: BackendEventsConfig,
191
+ ): Experimental_MCPEventAdapter {
192
+ return {
193
+ createAdapter({ transport }) {
194
+ return createBackendEventOperations({
195
+ ...config,
196
+ mcpUrl:
197
+ config.mcpUrl ??
198
+ (transport.type === 'custom' ? undefined : transport.url),
199
+ });
200
+ },
201
+ };
202
+ }
203
+ ```
204
+
205
+ These helpers and `BackendEventsConfig` are illustrative application or backend
206
+ SDK code, not AI SDK exports. `createBackendEventOperations` returns
207
+ `Experimental_MCPEventOperations` bound to the authorized account, endpoint
208
+ and destination. The helper above lets an explicit `mcpUrl` override the transport
209
+ URL for backend operations; it does not change the MCP client's transport or
210
+ catalog discovery. The backend still authorizes the selected endpoint.
211
+
212
+ The AI SDK adapter contract is:
213
+
214
+ ```ts
215
+ interface MCPEventAdapter {
216
+ createAdapter(context: {
217
+ transport: { type: 'http' | 'sse'; url: string } | { type: 'custom' };
218
+ }): MCPEventOperations;
219
+ }
220
+ ```
221
+
222
+ The interface is exported as `Experimental_MCPEventAdapter`. The client
223
+ calls `createAdapter` once, before starting the transport, and uses the returned
224
+ operations for managed subscriptions. The return value must be an object with
225
+ callable `subscribe`, `getSubscription`, `listSubscriptions`, and `unsubscribe`
226
+ methods. Nullish values, missing or non-callable methods, and Promise/thenable
227
+ results reject `createMCPClient` with `MCPClientError` before the transport starts.
228
+ Errors thrown by `createAdapter` also reject initialization without starting the
229
+ transport. Bound operations are no longer accepted directly in
230
+ `experimental_events.adapter`. To migrate existing subscription operations, wrap them explicitly:
231
+
232
+ ```ts
233
+ experimental_events: {
234
+ adapter: {
235
+ createAdapter: () => existingOperations,
236
+ },
237
+ }
238
+ ```
239
+
240
+ Only reuse bound operations when their account, endpoint and destination are correct
241
+ for every client using them. The former subscription operations interface is now
242
+ exported as `Experimental_MCPEventOperations`; `Experimental_MCPEventAdapter`
243
+ names the configured integration with `createAdapter`.
244
+
245
+ For HTTP and SSE transport configurations, the context contains a fresh
246
+ `transport: { type, url }` object. `url` is the configured string, including its
247
+ path and query, not a resolved redirect target. Custom `MCPTransport` instances
248
+ receive `transport: { type: 'custom' }`, with no `url` property; arbitrary
249
+ properties on the instance are not inspected.
250
+
251
+ This is a description of the transport, not the full configuration or live
252
+ instance. Headers, auth providers, fetch functions, session callbacks and
253
+ `start`/`send`/`close` methods are not passed to the adapter. The `transport`
254
+ grouping leaves room for other client context without mixing it with transport
255
+ metadata. No negotiated protocol version is supplied before initialization.
256
+
257
+ Return separately bound operations for each client instead of mutating shared
258
+ endpoint state. Adapters may prefer their own explicit endpoint override or
259
+ resolve a configured endpoint when `transport.type` is `'custom'`. That policy belongs to
260
+ the adapter; it does not change the client's transport or catalog discovery.
261
+ The managed backend must still authorize an endpoint before sending credentials
262
+ there. Receiving the transport URL does not grant that authorization.
263
+
264
+ ### Managed Operations Contract
147
265
 
148
266
  ```ts
149
267
  import type { JSONObject } from '@ai-sdk/provider';
@@ -184,7 +302,7 @@ These types are exported as `Experimental_ManagedSubscription` and
184
302
  a positive `ttlMs`, an ISO deadline in `expiresAt`, or `expiresAt: null` to
185
303
  monitor until stopped. The backend validates these values and enforces its limits.
186
304
 
187
- `Experimental_MCPEventsAdapter` has four operations:
305
+ `Experimental_MCPEventOperations` has four operations:
188
306
 
189
307
  | Operation | Input | Result |
190
308
  | ------------------- | ------------------------------------------------- | ---------------------------------- |
@@ -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.134",
3
+ "version": "7.0.136",
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.108",
46
- "@ai-sdk/provider": "4.0.25",
47
- "@ai-sdk/provider-utils": "5.0.57"
45
+ "@ai-sdk/gateway": "4.0.110",
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.112",
51
- "@ai-sdk/deepseek": "3.0.62",
52
- "@ai-sdk/google": "4.0.92",
53
- "@ai-sdk/groq": "4.0.58",
54
- "@ai-sdk/huggingface": "2.0.66",
55
- "@ai-sdk/moonshotai": "3.0.66",
56
- "@ai-sdk/openai": "4.0.90",
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.19",
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 ?? isStepCount(20),
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
  /**