ai 7.0.70 → 7.0.73

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.
@@ -92,7 +92,7 @@ import {
92
92
  } from "@ai-sdk/provider-utils";
93
93
 
94
94
  // src/version.ts
95
- var VERSION = true ? "7.0.70" : "0.0.0-test";
95
+ var VERSION = true ? "7.0.73" : "0.0.0-test";
96
96
 
97
97
  // src/util/download/download.ts
98
98
  var download = async ({
@@ -207,7 +207,6 @@ export default function Chat() {
207
207
  new WorkflowChatTransport({
208
208
  api: '/api/chat',
209
209
  maxConsecutiveErrors: 5,
210
- initialStartIndex: -50, // On page refresh, fetch last 50 chunks
211
210
  }),
212
211
  [],
213
212
  );
@@ -241,6 +240,7 @@ export async function POST(request: Request) {
241
240
 
242
241
  ```ts filename="app/api/chat/[runId]/stream/route.ts"
243
242
  import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
243
+ import { createUIMessageStreamResponse } from 'ai';
244
244
  import type { NextRequest } from 'next/server';
245
245
  import { getRun } from 'workflow/api';
246
246
 
@@ -252,23 +252,36 @@ export async function GET(
252
252
  const startIndex = Number(
253
253
  new URL(request.url).searchParams.get('startIndex') ?? '0',
254
254
  );
255
+ if (!Number.isSafeInteger(startIndex) || startIndex < 0) {
256
+ return Response.json(
257
+ { error: 'startIndex must be a non-negative safe integer' },
258
+ { status: 400 },
259
+ );
260
+ }
255
261
 
256
262
  const run = await getRun(runId);
257
263
  const readable = run
258
- .getReadable({ startIndex })
259
- .pipeThrough(createModelCallToUIChunkTransform());
264
+ .getReadable({ startIndex: 0 })
265
+ .pipeThrough(
266
+ createModelCallToUIChunkTransform({ uiStartIndex: startIndex }),
267
+ );
260
268
 
261
- return new Response(readable, {
269
+ return createUIMessageStreamResponse({
270
+ stream: readable,
262
271
  headers: {
263
- 'Content-Type': 'text/event-stream',
264
- 'Cache-Control': 'no-cache',
265
- Connection: 'keep-alive',
266
272
  'x-workflow-run-id': runId,
267
273
  },
268
274
  });
269
275
  }
270
276
  ```
271
277
 
278
+ `WorkflowChatTransport` counts `UIMessageChunk` objects, while the durable
279
+ `WorkflowAgent` stream stores raw `ModelCallStreamPart` objects. Replay the raw
280
+ stream from index `0` and apply the non-negative UI cursor in
281
+ `createModelCallToUIChunkTransform()` as shown above. Negative start indexes
282
+ require a durable stream that already stores `UIMessageChunk` objects and
283
+ cannot be used with this raw-to-UI conversion.
284
+
272
285
  For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport).
273
286
 
274
287
  ## Tools as Workflow Steps
@@ -309,13 +309,12 @@ for (const file of result.files) {
309
309
  | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `stabilityai/sdxl-turbo` | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21 |
310
310
  | [Replicate](/providers/ai-sdk-providers/replicate) | `black-forest-labs/flux-schnell` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 |
311
311
  | [Replicate](/providers/ai-sdk-providers/replicate) | `recraft-ai/recraft-v3` | 1024x1024, 1365x1024, 1024x1365, 1536x1024, 1024x1536, 1820x1024, 1024x1820, 1024x2048, 2048x1024, 1434x1024, 1024x1434, 1024x1280, 1280x1024, 1024x1707, 1707x1024 |
312
- | [Google](/providers/ai-sdk-providers/google#image-models) | `imagen-4.0-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
313
- | [Google](/providers/ai-sdk-providers/google#image-models) | `imagen-4.0-fast-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
314
- | [Google](/providers/ai-sdk-providers/google#image-models) | `imagen-4.0-ultra-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
315
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `imagen-4.0-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
316
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `imagen-4.0-fast-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
317
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `imagen-4.0-ultra-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
318
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `imagen-3.0-fast-generate-001` | 1:1, 3:4, 4:3, 9:16, 16:9 |
312
+ | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-2.5-flash-image` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
313
+ | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-3-pro-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
314
+ | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-3.1-flash-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
315
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-2.5-flash-image` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
316
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-3-pro-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
317
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-3.1-flash-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
319
318
  | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/flux-1-dev-fp8` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 |
320
319
  | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/flux-1-schnell-fp8` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 |
321
320
  | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 |
@@ -484,6 +484,64 @@ try {
484
484
  }
485
485
  ```
486
486
 
487
+ ### Basic Sandbox Sessions Without Network Control
488
+
489
+ The following example demonstrates how the basic-session API works. If your
490
+ project can expose a full network sandbox session, passing that session is
491
+ strongly recommended. Pass a restricted basic session only when your project
492
+ cannot expose the full network session.
493
+
494
+ A basic sandbox session only exposes filesystem and process APIs. The agent
495
+ still leaves the sandbox lifecycle to the caller. For a bridge-backed harness,
496
+ configure the bridge port and its externally reachable endpoint on the adapter
497
+ because the basic session cannot resolve them.
498
+
499
+ ```ts
500
+ import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent';
501
+ import { createClaudeCode } from '@ai-sdk/harness-claude-code';
502
+ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
503
+ import { Sandbox } from '@vercel/sandbox';
504
+
505
+ const sandbox = await Sandbox.create({
506
+ runtime: 'node24',
507
+ ports: [4000],
508
+ });
509
+ const sandboxProvider = createVercelSandbox({ sandbox });
510
+ const sandboxSession = await sandboxProvider.createSession();
511
+ const portEndpoint = await sandboxSession.getPortEndpoint({
512
+ port: 4000,
513
+ protocol: 'ws',
514
+ });
515
+ const restrictedSandboxSession = sandboxSession.restricted();
516
+ const claudeCode = createClaudeCode({ port: 4000, portEndpoint });
517
+
518
+ await prepareSandboxForHarness({
519
+ session: restrictedSandboxSession,
520
+ harnesses: [claudeCode],
521
+ });
522
+
523
+ const agent = new HarnessAgent({ harness: claudeCode });
524
+ const session = await agent.createSession({
525
+ sandboxSession: restrictedSandboxSession,
526
+ });
527
+
528
+ try {
529
+ const result = await agent.stream({
530
+ session,
531
+ prompt: 'Create a short TODO.md for this repository.',
532
+ });
533
+
534
+ for await (const part of result.stream) {
535
+ if (part.type === 'text-delta') {
536
+ process.stdout.write(part.text);
537
+ }
538
+ }
539
+ } finally {
540
+ await session.destroy();
541
+ await sandbox.stop();
542
+ }
543
+ ```
544
+
487
545
  ## Next Steps
488
546
 
489
547
  - [Tools](/docs/ai-sdk-harnesses/tools) for built-in and host-executed tools.
@@ -171,7 +171,6 @@ export default function Chat() {
171
171
  new WorkflowChatTransport({
172
172
  api: '/api/chat',
173
173
  maxConsecutiveErrors: 5,
174
- initialStartIndex: -50, // On page refresh, fetch last 50 chunks
175
174
  onChatEnd: ({ chatId, chunkIndex }) => {
176
175
  console.log(`Chat complete: ${chunkIndex} chunks`);
177
176
  },
@@ -188,10 +187,15 @@ export default function Chat() {
188
187
  Key features:
189
188
 
190
189
  - **Automatic reconnection**: Detects interrupted streams (no `finish` event) and reconnects via GET to `{api}/{runId}/stream`
191
- - **Page refresh recovery**: `initialStartIndex` with negative values (e.g., `-50`) fetches only the tail of the stream instead of replaying everything
190
+ - **Page refresh recovery**: `initialStartIndex` controls where the initial reconnection begins
192
191
  - **Configurable retries**: `maxConsecutiveErrors` controls how many consecutive reconnection failures to tolerate
193
192
  - **Lifecycle callbacks**: `onChatSendMessage` and `onChatEnd` for tracking chat state
194
193
 
194
+ Negative `initialStartIndex` values can fetch only the tail when the durable
195
+ server stream already stores `UIMessageChunk` objects. For raw `WorkflowAgent`
196
+ streams, use a non-negative cursor and follow the server-side conversion in the
197
+ WorkflowAgent guide.
198
+
195
199
  For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). For server-side endpoint setup, see the [WorkflowAgent guide](/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport).
196
200
 
197
201
  ## Building Custom Transports
@@ -696,7 +696,7 @@ Returns a `Promise<WorkflowAgentStreamResult>` with the following properties:
696
696
 
697
697
  ## Utilities
698
698
 
699
- ### `createModelCallToUIChunkTransform()`
699
+ ### `createModelCallToUIChunkTransform(options?)`
700
700
 
701
701
  Creates a `TransformStream` that converts raw `ModelCallStreamPart` chunks (written by the agent to the `writable` stream) into `UIMessageChunk` objects suitable for client consumption.
702
702
 
@@ -708,6 +708,20 @@ return createUIMessageStreamResponse({
708
708
  });
709
709
  ```
710
710
 
711
+ When resuming with a `WorkflowChatTransport` cursor, replay the raw workflow
712
+ stream from index `0` and pass the non-negative UI chunk index to the transform:
713
+
714
+ ```ts
715
+ const readable = run
716
+ .getReadable({ startIndex: 0 })
717
+ .pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex: startIndex }));
718
+ ```
719
+
720
+ `uiStartIndex` must be a non-negative safe integer. Raw model stream parts and
721
+ UI message chunks are not one-to-one, so do not pass a UI chunk index to
722
+ `getReadable`. Negative tail indexes require a durable stream that already
723
+ stores `UIMessageChunk` objects.
724
+
711
725
  ### `toUIMessageChunk()`
712
726
 
713
727
  Converts a single `ModelCallStreamPart` to a `UIMessageChunk`. Returns `undefined` for parts that don't map to UI chunks.
@@ -20,7 +20,6 @@ export default function Chat() {
20
20
  transport: new WorkflowChatTransport({
21
21
  api: '/api/chat',
22
22
  maxConsecutiveErrors: 5,
23
- initialStartIndex: -50,
24
23
  }),
25
24
  });
26
25
 
@@ -67,7 +66,7 @@ export default function Chat() {
67
66
  type: 'number',
68
67
  isOptional: true,
69
68
  description:
70
- 'Default chunk index to start from when reconnecting. Negative values read from the end of the stream (e.g., -50 fetches the last 50 chunks), useful for resuming after a page refresh without replaying the full conversation. Can be overridden per-call via reconnectToStream options. Default: 0.',
69
+ 'Default chunk index to start from when reconnecting. Negative values read from the end of a durable UIMessageChunk stream (e.g., -50 fetches the last 50 chunks), useful for resuming after a page refresh without replaying the full conversation. Raw ModelCallStreamPart streams do not support negative UI chunk indexes. Can be overridden per-call via reconnectToStream options. Default: 0.',
71
70
  },
72
71
  {
73
72
  name: 'onChatSendMessage',
@@ -183,7 +182,7 @@ const stream = await transport.reconnectToStream({
183
182
  type: 'number',
184
183
  isOptional: true,
185
184
  description:
186
- "Override the start index for this reconnection. Negative values read from the end of the stream. When omitted, falls back to the constructor's initialStartIndex.",
185
+ "Override the start index for this reconnection. Negative values read from the end when the server's durable stream and tail-index header use the same UIMessageChunk index space. When omitted, falls back to the constructor's initialStartIndex.",
187
186
  },
188
187
  ]}
189
188
  />
@@ -209,6 +208,10 @@ When `initialStartIndex` is negative (e.g., `-50`), the transport sends it as-is
209
208
 
210
209
  If the header is missing or invalid, the transport falls back to replaying from the beginning (`startIndex=0`).
211
210
 
211
+ Negative indexes require a durable server stream whose stored objects are
212
+ already `UIMessageChunk` objects. The raw `WorkflowAgent` conversion shown
213
+ below supports non-negative indexes only.
214
+
212
215
  ## Server Requirements
213
216
 
214
217
  For `WorkflowChatTransport` to work, your server must provide two endpoints:
@@ -262,7 +265,7 @@ export default function Chat() {
262
265
  }
263
266
  ```
264
267
 
265
- ### With Callbacks and Page Refresh Recovery
268
+ ### With Callbacks
266
269
 
267
270
  ```tsx
268
271
  'use client';
@@ -277,7 +280,6 @@ export default function Chat() {
277
280
  new WorkflowChatTransport({
278
281
  api: '/api/chat',
279
282
  maxConsecutiveErrors: 5,
280
- initialStartIndex: -50, // Resume from last 50 chunks on page refresh
281
283
  onChatSendMessage: response => {
282
284
  const runId = response.headers.get('x-workflow-run-id');
283
285
  console.log('Workflow run started:', runId);
@@ -318,6 +320,7 @@ export async function POST(request: Request) {
318
320
 
319
321
  ```ts filename="app/api/chat/[runId]/stream/route.ts"
320
322
  import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
323
+ import { createUIMessageStreamResponse } from 'ai';
321
324
  import type { NextRequest } from 'next/server';
322
325
  import { getRun } from 'workflow/api';
323
326
 
@@ -329,19 +332,30 @@ export async function GET(
329
332
  const startIndex = Number(
330
333
  new URL(request.url).searchParams.get('startIndex') ?? '0',
331
334
  );
335
+ if (!Number.isSafeInteger(startIndex) || startIndex < 0) {
336
+ return Response.json(
337
+ { error: 'startIndex must be a non-negative safe integer' },
338
+ { status: 400 },
339
+ );
340
+ }
332
341
 
333
342
  const run = await getRun(runId);
334
343
  const readable = run
335
- .getReadable({ startIndex })
336
- .pipeThrough(createModelCallToUIChunkTransform());
344
+ .getReadable({ startIndex: 0 })
345
+ .pipeThrough(
346
+ createModelCallToUIChunkTransform({ uiStartIndex: startIndex }),
347
+ );
337
348
 
338
- return new Response(readable, {
349
+ return createUIMessageStreamResponse({
350
+ stream: readable,
339
351
  headers: {
340
- 'Content-Type': 'text/event-stream',
341
- 'Cache-Control': 'no-cache',
342
- Connection: 'keep-alive',
343
352
  'x-workflow-run-id': runId,
344
353
  },
345
354
  });
346
355
  }
347
356
  ```
357
+
358
+ This `WorkflowAgent` endpoint replays raw `ModelCallStreamPart` objects from
359
+ index `0`, then applies the transport's non-negative cursor after converting
360
+ them to `UIMessageChunk` objects. Negative start indexes require a durable
361
+ stream whose stored objects are already `UIMessageChunk` objects.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.70",
3
+ "version": "7.0.73",
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.56",
45
+ "@ai-sdk/gateway": "4.0.59",
46
46
  "@ai-sdk/provider": "4.0.7",
47
47
  "@ai-sdk/provider-utils": "5.0.28"
48
48
  },
@@ -546,7 +546,10 @@ class DefaultStreamObjectResult<
546
546
  controller.enqueue(chunk);
547
547
 
548
548
  if (chunk.type === 'error') {
549
- onError({ error: wrapGatewayError(chunk.error) });
549
+ void notify({
550
+ event: { error: wrapGatewayError(chunk.error) },
551
+ callbacks: onError,
552
+ });
550
553
  }
551
554
  },
552
555
  });
@@ -1193,7 +1193,10 @@ class DefaultStreamTextResult<
1193
1193
 
1194
1194
  const { part } = chunk;
1195
1195
 
1196
- await onChunk?.({ chunk: part });
1196
+ await notify({
1197
+ event: { chunk: part },
1198
+ callbacks: onChunk,
1199
+ });
1197
1200
 
1198
1201
  if (part.type === 'error') {
1199
1202
  const error = wrapGatewayError(part.error);
@@ -1202,7 +1205,10 @@ class DefaultStreamTextResult<
1202
1205
  recordedNoOutputError = error;
1203
1206
  }
1204
1207
 
1205
- await onError({ error });
1208
+ await notify({
1209
+ event: { error },
1210
+ callbacks: onError,
1211
+ });
1206
1212
  }
1207
1213
 
1208
1214
  if (