ai 7.0.114 → 7.0.117

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 (34) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.js +951 -1250
  3. package/dist/index.js.map +1 -1
  4. package/dist/internal/index.js +159 -212
  5. package/dist/internal/index.js.map +1 -1
  6. package/docs/03-agents/02-building-agents.mdx +2 -2
  7. package/docs/03-agents/08-terminal-ui.mdx +12 -8
  8. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +6 -1
  9. package/docs/03-ai-sdk-core/17-mcp-apps.mdx +1 -1
  10. package/docs/03-ai-sdk-core/26-reasoning.mdx +2 -2
  11. package/docs/03-ai-sdk-core/32-evaluation.mdx +7 -4
  12. package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +7 -4
  13. package/docs/03-ai-sdk-harnesses/01-overview.mdx +3 -2
  14. package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +97 -107
  15. package/docs/03-ai-sdk-harnesses/03-tools.mdx +1 -22
  16. package/docs/03-ai-sdk-harnesses/04-skills.mdx +0 -4
  17. package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +3 -3
  18. package/docs/03-ai-sdk-harnesses/06-workflow-utilities.mdx +43 -9
  19. package/docs/03-ai-sdk-harnesses/07-ui.mdx +33 -30
  20. package/docs/03-ai-sdk-harnesses/08-terminal-ui.mdx +8 -6
  21. package/docs/04-ai-sdk-ui/01-overview.mdx +1 -1
  22. package/docs/04-ai-sdk-ui/02-chatbot.mdx +9 -7
  23. package/docs/04-ai-sdk-ui/08-object-generation.mdx +4 -1
  24. package/docs/06-advanced/04-caching.mdx +2 -1
  25. package/docs/06-advanced/11-secure-url-fetching.mdx +39 -0
  26. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +1 -1
  27. package/docs/07-reference/02-ai-sdk-ui/03-use-object.mdx +4 -1
  28. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -0
  29. package/docs/07-reference/03-ai-sdk-rsc/02-create-ai.mdx +1 -1
  30. package/docs/07-reference/03-ai-sdk-rsc/03-create-streamable-ui.mdx +1 -1
  31. package/docs/07-reference/06-ai-sdk-tui/01-run-agent-tui.mdx +12 -8
  32. package/package.json +12 -12
  33. package/src/prompt/file-part-data.ts +9 -3
  34. package/src/util/download/download.ts +2 -2
@@ -140,8 +140,8 @@ Context](/docs/ai-sdk-core/runtime-and-tool-context).
140
140
  ### Tools That Use Experimental Sandboxes
141
141
 
142
142
  Pass `experimental_sandbox` when an agent tool needs a command or code execution
143
- environment. The experimental sandbox is a per-call value, so provide it to `generate()`,
144
- `stream()`, or the agent UI stream helper that invokes the agent.
143
+ environment. The experimental sandbox is a per-call value, so provide it to
144
+ `generate()`, `stream()`, or the agent UI stream helper that invokes the agent.
145
145
 
146
146
  ```ts highlight="13,19-23,31"
147
147
  const agent = new ToolLoopAgent({
@@ -82,17 +82,21 @@ Pass a sandbox session with the `sandbox` option when your agent tools need an
82
82
  execution environment:
83
83
 
84
84
  ```ts
85
- import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash';
85
+ import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash';
86
86
 
87
- const sandboxSession = await createJustBashSandbox({
87
+ const sandboxSession = await createJustBashNetworkSandboxSession({
88
88
  cwd: '/home/user',
89
- }).createSession();
90
-
91
- await runAgentTUI({
92
- title: 'Sandbox Agent',
93
- agent,
94
- sandbox: sandboxSession.restricted(),
95
89
  });
90
+
91
+ try {
92
+ await runAgentTUI({
93
+ title: 'Sandbox Agent',
94
+ agent,
95
+ sandbox: sandboxSession.restricted(),
96
+ });
97
+ } finally {
98
+ await sandboxSession.destroy();
99
+ }
96
100
  ```
97
101
 
98
102
  The terminal UI forwards the sandbox to every agent call as
@@ -1386,12 +1386,17 @@ orders the remaining tools.
1386
1386
  and Google (Gemini 3 models).
1387
1387
  </Note>
1388
1388
 
1389
- For Google, use base64 inline-data file parts
1389
+ For Google Generative AI, use base64 inline-data file parts
1390
1390
  (`{ type: 'file', mediaType, data: { type: 'data', data } }`) or base64
1391
1391
  `data:` URLs in URL-style file parts
1392
1392
  (`{ type: 'file', mediaType, data: { type: 'url', url: new URL('data:...') } }`).
1393
1393
  Remote HTTP(S) URLs in tool-result URL parts are not supported.
1394
1394
 
1395
+ The [Google Vertex provider](/providers/ai-sdk-providers/google-vertex#tool-result-files)
1396
+ also supports direct `gs://` tool-result file references for Gemini 3 and later
1397
+ models with supported image and document MIME types. It downloads HTTP(S)
1398
+ tool-result URLs and sends their contents as inline data.
1399
+
1395
1400
  In order to send multi-modal tool results, e.g. screenshots, back to the model,
1396
1401
  they need to be converted into a specific format.
1397
1402
 
@@ -242,7 +242,7 @@ export default function Chat() {
242
242
  }
243
243
  ```
244
244
 
245
- `experimental_MCPAppRenderer` renders nothing for ordinary tools. For app-backed tools, it loads the resource, creates the sandbox bridge, sends tool input and result notifications to the iframe, and forwards supported app requests through your handlers.
245
+ For ordinary tools, `experimental_MCPAppRenderer` renders the provided `fallback` (or nothing when no fallback is provided). For app-backed tools, it loads the resource, creates the sandbox bridge, sends tool input and result notifications to the iframe, and forwards supported app requests through your handlers.
246
246
 
247
247
  ## Best Practices
248
248
 
@@ -77,9 +77,9 @@ This design lets you use the portable `reasoning` parameter by default and fall
77
77
 
78
78
  ## Provider Support
79
79
 
80
- The `reasoning` parameter is supported by the following providers: OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, Fireworks, and Amazon Bedrock. Each provider translates the value to its native reasoning API. Some providers support all six levels natively, while others coerce to fewer levels (a warning is emitted when coercion occurs). Some providers use a numeric token budget instead of an enum for reasoning control; in those cases the top-level `reasoning` value is mapped to a budget calculated as a percentage of the model's maximum output tokens.
80
+ The `reasoning` parameter is supported by the following providers: OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, Fireworks, Amazon Bedrock, and Perplexity. Each provider translates the value to its native reasoning API. Some providers support all six levels natively, while others coerce to fewer levels (a warning is emitted when coercion occurs). Some providers use a numeric token budget instead of an enum for reasoning control; in those cases the top-level `reasoning` value is mapped to a budget calculated as a percentage of the model's maximum output tokens.
81
81
 
82
- Providers that do not support reasoning (e.g. Mistral, Perplexity, Cohere) emit an `unsupported` warning and ignore the parameter.
82
+ Providers that do not support reasoning (e.g. Mistral and Cohere) emit an `unsupported` warning and ignore the parameter.
83
83
 
84
84
  ## Migrating from `providerOptions`
85
85
 
@@ -132,7 +132,7 @@ with `AI_GATEWAY_API_KEY` or Vercel OIDC, then pass a Gateway model ID:
132
132
  import { experimental_evaluate } from 'ai';
133
133
 
134
134
  const result = await experimental_evaluate({
135
- model: 'typesafe-ai/jev-latest',
135
+ model: 'typesafe-ai/jev',
136
136
  state: 'I was charged twice. Please refund the extra charge.',
137
137
  questions: {
138
138
  refund: {
@@ -246,8 +246,8 @@ if (result.answers.requestsRefund.probability >= 0.8) {
246
246
  The model's `supportedQuestionTypes` are checked before calling the provider.
247
247
  Any unsupported question fails the entire call with
248
248
  `Experimental_EvaluationUnsupportedQuestionTypeError`. Successful calls return
249
- an answer for every question; there is no partial success or automatic model
250
- substitution.
249
+ an answer for every question; there is no partial success, and Core does not
250
+ substitute models automatically.
251
251
 
252
252
  Invalid inputs throw `InvalidArgumentError`. Missing answers, mismatched answer
253
253
  types, invalid options, scores, or probabilities throw `InvalidResponseDataError`.
@@ -266,7 +266,10 @@ For tests, use `Experimental_EvaluationMockModelV4` from `ai/test`.
266
266
  Evaluation currently returns one complete result for one shared state. It does
267
267
  not stream answers, perform multilabel classification, or batch unrelated
268
268
  states. Run separate calls for separate states. Provider support and judgment
269
- quality depend on the chosen model; the SDK does not choose a model automatically.
269
+ quality depend on the chosen model. Core does not choose another model, but a
270
+ provider can implement routing through its provider options. For example, AI
271
+ Gateway supports conditional evaluation fallbacks under
272
+ `providerOptions.gateway.models`.
270
273
 
271
274
  Runnable examples are in
272
275
  [`examples/ai-functions/src/evaluate`](https://github.com/vercel/ai/tree/main/examples/ai-functions/src/evaluate),
@@ -301,8 +301,10 @@ This distinction matters when the step includes local tool execution: the step d
301
301
 
302
302
  ## Runtime and Tool Context
303
303
 
304
- Lifecycle callbacks receive the full `runtimeContext` and `toolsContext` values that flow through the call.
305
- This makes callbacks useful for attaching application context without changing prompts or tool inputs.
304
+ Generation and step lifecycle callbacks receive the full `runtimeContext` and
305
+ `toolsContext` values that flow through the call. Tool-execution callbacks receive
306
+ the scoped `toolContext` for the tool being executed. This makes callbacks useful
307
+ for attaching application context without changing prompts or tool inputs.
306
308
 
307
309
  ```tsx highlight="8-24,26-39"
308
310
  import { generateText, tool } from 'ai';
@@ -349,8 +351,9 @@ const result = await generateText({
349
351
 
350
352
  <Note>
351
353
  Telemetry integrations can filter `runtimeContext` and `toolsContext` before
352
- exporting them. Lifecycle callbacks receive the full context objects. Be
353
- careful not to log secrets or sensitive user data from callbacks.
354
+ exporting them. Generation and step callbacks receive the full context
355
+ objects; tool-execution callbacks receive scoped tool context. Be careful not
356
+ to log secrets or sensitive user data from callbacks.
354
357
  </Note>
355
358
 
356
359
  ## Available Callbacks
@@ -9,8 +9,9 @@ The AI SDK harness abstraction lets you run established agent harnesses through
9
9
  single AI SDK surface. A harness is a complete agent runtime, such as Claude
10
10
  Code, Codex, or Pi. It owns capabilities that are larger than a model call:
11
11
  workspace access, built-in coding tools, native session state, compaction,
12
- permission flows, and runtime-specific configuration. Additionally, all AI SDK
13
- agent harnesses operate in a sandbox, keeping the host environment safe.
12
+ permission flows, and runtime-specific configuration. Harness adapters can run
13
+ the runtime in a sandbox, but their execution environments differ by adapter;
14
+ review the adapter's runtime location and security guidance before use.
14
15
 
15
16
  The AI SDK harness abstraction is separate from the provider/model abstraction.
16
17
  Providers expose models to AI SDK Core functions such as `generateText` and
@@ -11,7 +11,7 @@ results while a preconfigured harness powers these results.
11
11
 
12
12
  ## Installation
13
13
 
14
- Install the core harness package, a harness adapter, and a sandbox provider:
14
+ Install the core harness package, a harness adapter, and a sandbox adapter:
15
15
 
16
16
  <InstallPackages packages="@ai-sdk/harness @ai-sdk/harness-claude-code @ai-sdk/sandbox-vercel" />
17
17
 
@@ -24,15 +24,10 @@ like `@ai-sdk/sandbox-vercel`. Host-runtime harnesses such as Pi can also run wi
24
24
  ```ts
25
25
  import { HarnessAgent } from '@ai-sdk/harness/agent';
26
26
  import { claudeCode } from '@ai-sdk/harness-claude-code';
27
- import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
28
27
 
29
28
  export const agent = new HarnessAgent({
30
29
  harness: claudeCode,
31
30
  model: 'claude-sonnet-4-6',
32
- sandbox: createVercelSandbox({
33
- runtime: 'node24',
34
- ports: [4000],
35
- }),
36
31
  instructions:
37
32
  'You are a careful coding assistant. Prefer small changes and explain tradeoffs.',
38
33
  });
@@ -40,6 +35,17 @@ export const agent = new HarnessAgent({
40
35
 
41
36
  Construct the agent at module scope. It holds configuration, not a live session.
42
37
  Live state belongs to `HarnessAgentSession`.
38
+ Create a sandbox session separately and pass it to `agent.createSession()`:
39
+
40
+ ```ts
41
+ import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';
42
+
43
+ const sandboxSession = await createVercelNetworkSandboxSession({
44
+ runtime: 'node24',
45
+ ports: [4000],
46
+ template: await agent.getSandboxTemplate(),
47
+ });
48
+ ```
43
49
 
44
50
  Set `model` to select the model that the harness runtime uses. Model identifiers
45
51
  are specific to each harness. When omitted, the harness uses its default model.
@@ -51,7 +57,7 @@ are set.
51
57
  ## Run a Turn
52
58
 
53
59
  ```ts
54
- const session = await agent.createSession();
60
+ const session = await agent.createSession({ sandboxSession });
55
61
 
56
62
  let exitCode = 0;
57
63
  try {
@@ -66,6 +72,7 @@ try {
66
72
  console.error(err);
67
73
  } finally {
68
74
  await session.destroy();
75
+ await sandboxSession.destroy();
69
76
  process.exit(exitCode);
70
77
  }
71
78
  ```
@@ -75,7 +82,7 @@ try {
75
82
  Use `stream()` for incremental output:
76
83
 
77
84
  ```ts
78
- const session = await agent.createSession();
85
+ const session = await agent.createSession({ sandboxSession });
79
86
 
80
87
  let exitCode = 0;
81
88
  try {
@@ -94,6 +101,7 @@ try {
94
101
  console.error(err);
95
102
  } finally {
96
103
  await session.destroy();
104
+ await sandboxSession.destroy();
97
105
  process.exit(exitCode);
98
106
  }
99
107
  ```
@@ -106,7 +114,6 @@ steps, and tool executions:
106
114
  ```ts
107
115
  const agent = new HarnessAgent({
108
116
  harness: claudeCode,
109
- sandbox,
110
117
  tools: { weather },
111
118
  onStart: event => console.log('call started', event.callId),
112
119
  onStepStart: event => console.log('step started', event.stepNumber),
@@ -145,7 +152,6 @@ import { z } from 'zod';
145
152
 
146
153
  const agent = new HarnessAgent({
147
154
  harness: claudeCode,
148
- sandbox,
149
155
  output: Output.object({
150
156
  schema: z.object({
151
157
  recipe: z.object({
@@ -163,7 +169,7 @@ const agent = new HarnessAgent({
163
169
  }),
164
170
  });
165
171
 
166
- const session = await agent.createSession();
172
+ const session = await agent.createSession({ sandboxSession });
167
173
  try {
168
174
  const result = await agent.generate({
169
175
  session,
@@ -205,10 +211,10 @@ of relying on message replay.
205
211
  End every session explicitly:
206
212
 
207
213
  - `session.destroy()` stops the runtime and discards resumability.
208
- - `session.detach()` parks the runtime and sandbox, returns resume state, and
209
- keeps the sandbox warm for a later attach. If the turn is unfinished, the
214
+ - `session.detach()` parks the runtime, returns resume state, and leaves a
215
+ supplied sandbox running for a later attach. If the turn is unfinished, the
210
216
  resume state includes the continuation state.
211
- - `session.stop()` saves resume state, then stops the runtime and sandbox. If
217
+ - `session.stop()` saves resume state, then stops the runtime. If
212
218
  the turn is unfinished, the resume state includes the continuation state.
213
219
  - `session.suspendTurn()` is for advanced active-turn continuation across a
214
220
  process boundary.
@@ -237,7 +243,6 @@ const getPolicy = tool({
237
243
 
238
244
  const agent = new HarnessAgent({
239
245
  harness: claudeCode,
240
- sandbox,
241
246
  tools: { getPolicy },
242
247
  callOptionsSchema: z.object({
243
248
  area: z.enum(['frontend', 'backend']),
@@ -253,7 +258,7 @@ const agent = new HarnessAgent({
253
258
  }),
254
259
  });
255
260
 
256
- const session = await agent.createSession();
261
+ const session = await agent.createSession({ sandboxSession });
257
262
  try {
258
263
  await agent.generate({
259
264
  session,
@@ -305,7 +310,10 @@ the harness runtime but do not stop or destroy the supplied sandbox session.
305
310
  In this case, the agent does not need a `sandbox` provider in its constructor.
306
311
 
307
312
  ```ts
308
- const session = await agent.createSession({ sessionId: chatId });
313
+ const session = await agent.createSession({
314
+ sessionId: chatId,
315
+ sandboxSession,
316
+ });
309
317
 
310
318
  try {
311
319
  const result = await agent.stream({ session, messages });
@@ -326,22 +334,39 @@ try {
326
334
 
327
335
  ## Resuming
328
336
 
329
- Persist the opaque resume state and pass it back with the original `sessionId`:
337
+ Persist the opaque resume state. In this example, the harness `sessionId` is
338
+ `chatId`, and the sandbox ID is derived from the same known ID. If your
339
+ application cannot derive the sandbox ID, persist `sandboxSession.id` alongside
340
+ the harness `sessionId` and resume state instead. Reattach the sandbox before
341
+ asking the agent to resume. When you choose `sandboxId` on creation, it names a **new**
342
+ live sandbox and never looks one up. Reusing that ID with the creator fails
343
+ if the named sandbox exists; only the resume function performs a lookup.
330
344
 
331
345
  ```ts
332
346
  const resumeState = await loadResumeState({ chatId });
347
+ const sandboxId = `chat-${chatId}`;
348
+
349
+ const sandboxSession = resumeState
350
+ ? await resumeVercelNetworkSandboxSession({ sandboxId })
351
+ : await createVercelNetworkSandboxSession({
352
+ sandboxId,
353
+ ports: [4000],
354
+ template: await agent.getSandboxTemplate(),
355
+ });
333
356
 
334
357
  const session = await agent.createSession(
335
358
  resumeState
336
- ? { sessionId: chatId, resumeFrom: resumeState }
337
- : { sessionId: chatId },
359
+ ? { sessionId: chatId, resumeFrom: resumeState, sandboxSession }
360
+ : { sessionId: chatId, sandboxSession },
338
361
  );
339
362
  ```
340
363
 
341
364
  `HarnessAgent` validates that the resume state was produced by the same harness
342
365
  adapter before handing it to the runtime. If the resume state includes an
343
366
  unfinished turn, call `continueStream()` or `continueGenerate()` before sending a
344
- new prompt.
367
+ new prompt. The Vercel resume function reattaches whether the sandbox is still
368
+ running after `detach()` or stopped with a restorable snapshot. After
369
+ `sandboxSession.destroy()` deletes it, that ID cannot be resumed.
345
370
 
346
371
  ## Continue a Suspended Turn
347
372
 
@@ -362,6 +387,7 @@ When you only have raw continuation state from `suspendTurn()`, resume with
362
387
  const session = await agent.createSession({
363
388
  sessionId: chatId,
364
389
  continueFrom: continuationState,
390
+ sandboxSession,
365
391
  // Rebind this when the suspended turn used host-only tool context:
366
392
  toolsContext,
367
393
  });
@@ -391,14 +417,10 @@ import { isStepCount } from 'ai';
391
417
 
392
418
  const steppedAgent = new HarnessAgent({
393
419
  harness: claudeCode,
394
- sandbox: createVercelSandbox({
395
- runtime: 'node24',
396
- ports: [4000],
397
- }),
398
420
  stopWhen: isStepCount(1),
399
421
  });
400
422
 
401
- const session = await steppedAgent.createSession();
423
+ const session = await steppedAgent.createSession({ sandboxSession });
402
424
  const result = await steppedAgent.generate({
403
425
  session,
404
426
  prompt: 'Create a short TODO.md for this repository.',
@@ -434,10 +456,6 @@ files or lightweight configuration.
434
456
  ```ts
435
457
  const agent = new HarnessAgent({
436
458
  harness: claudeCode,
437
- sandbox: createVercelSandbox({
438
- runtime: 'node24',
439
- ports: [4000],
440
- }),
441
459
  sandboxConfig: {
442
460
  workDir: 'repo',
443
461
  bootstrapHash: 'ripgrep-v1',
@@ -469,76 +487,55 @@ while `onBootstrap` receives the sandbox's default working directory.
469
487
 
470
488
  ## Prepare Reusable Sandboxes
471
489
 
472
- Use `prepareHarnessSandboxTemplate()` when you want the sandbox provider to
473
- create or refresh its reusable template for one harness ahead of time:
490
+ Use `agent.getSandboxTemplate()` to resolve one harness's bootstrap recipe and
491
+ `sandboxConfig.onBootstrap` before creating a sandbox. The template's identity
492
+ is known before sandbox creation. Vercel persists the prepared snapshot and
493
+ creates a live sandbox from it; identical calls reuse that snapshot:
474
494
 
475
495
  ```ts
476
- import { prepareHarnessSandboxTemplate } from '@ai-sdk/harness/agent';
477
-
478
- await prepareHarnessSandboxTemplate({
479
- harness: claudeCode,
480
- sandboxProvider: createVercelSandbox({
481
- runtime: 'node24',
482
- ports: [4000],
483
- }),
484
- sandboxConfig: {
485
- bootstrapHash: 'ripgrep-v1',
486
- onBootstrap: async ({ session, abortSignal }) => {
487
- await session.run({
488
- command:
489
- 'command -v rg >/dev/null || (apt-get update && apt-get install -y ripgrep)',
490
- abortSignal,
491
- });
492
- },
493
- },
496
+ const sandboxSession = await createVercelNetworkSandboxSession({
497
+ runtime: 'node24',
498
+ ports: [4000],
499
+ template: await agent.getSandboxTemplate(),
494
500
  });
501
+
502
+ const session = await agent.createSession({ sandboxSession });
503
+ try {
504
+ await agent.generate({ session, prompt: 'Inspect this repository.' });
505
+ } finally {
506
+ await session.destroy();
507
+ await sandboxSession.destroy();
508
+ }
495
509
  ```
496
510
 
497
- Use `prepareSandboxForHarness()` when you own the native sandbox lifecycle and
498
- want to snapshot the prepared sandbox yourself. It applies the selected harness
499
- bootstrap recipes and `sandboxConfig.onBootstrap`, then returns preparation
500
- metadata. It does not stop or snapshot the sandbox.
511
+ For multiple harnesses sharing a caller bootstrap hook, build one template from
512
+ all adapters. The last adapter wins for duplicate `harnessId` values, and the
513
+ recipe identities are sorted before hashing. `onSession` is not part of the
514
+ template and runs separately on every acquired session.
501
515
 
502
516
  ```ts
503
- import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent';
504
- import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
505
- import { Sandbox } from '@vercel/sandbox';
517
+ import { createHarnessSandboxTemplate } from '@ai-sdk/harness/agent';
506
518
 
507
- const nativeSandbox = await Sandbox.create({
508
- runtime: 'node24',
509
- ports: [4000],
510
- });
511
- const sandboxProvider = createVercelSandbox({ sandbox: nativeSandbox });
512
- const session = await sandboxProvider.createSession();
513
-
514
- const preparation = await prepareSandboxForHarness({
515
- session: session.restricted(),
519
+ const template = await createHarnessSandboxTemplate({
516
520
  harnesses: [claudeCode, codex],
517
521
  sandboxConfig,
518
522
  });
523
+ console.log(template?.identity);
519
524
 
520
- const { snapshot } = await nativeSandbox.stop();
521
- if (snapshot == null) {
522
- throw new Error('Prepared sandbox did not create a snapshot.');
523
- }
524
-
525
- const sandboxFromSnapshot = await Sandbox.create({
526
- source: {
527
- type: 'snapshot',
528
- snapshotId: snapshot.id,
529
- },
525
+ const sandboxSession = await createVercelNetworkSandboxSession({
526
+ source: { type: 'snapshot', snapshotId: baseSnapshotId },
530
527
  ports: [4000],
528
+ template,
531
529
  });
532
-
533
- const agent = new HarnessAgent({
534
- harness: claudeCode,
535
- sandbox: createVercelSandbox({ sandbox: sandboxFromSnapshot }),
536
- sandboxConfig,
537
- });
538
-
539
- console.log(preparation.identity);
540
530
  ```
541
531
 
532
+ The derived template includes the source snapshot ID in its cache key, so
533
+ repeated calls with the same source and template reuse the prepared snapshot.
534
+ Pin mutable Git revisions, image tags, and source URLs when relying on cached
535
+ templates. `sandboxId` names the live sandbox, not its reusable template.
536
+ The existing native `name` option still names the live sandbox; if both are
537
+ supplied, their values must match.
538
+
542
539
  ## Settings
543
540
 
544
541
  `HarnessAgent` accepts these main settings:
@@ -546,7 +543,6 @@ console.log(preparation.identity);
546
543
  - `harness`: the adapter instance.
547
544
  - `model`: optional harness-specific model identifier. When omitted, the
548
545
  harness uses its default model.
549
- - `sandbox`: a `HarnessV1SandboxProvider`.
550
546
  - `id`: optional stable agent identifier.
551
547
  - `instructions`: instructions appended to the runtime's system or developer
552
548
  prompt when supported, or prepended to the user prompt otherwise.
@@ -580,26 +576,21 @@ Adapter-specific settings belong on the adapter factory, for example
580
576
 
581
577
  When your application creates and manages sandboxes itself, prepare the network
582
578
  sandbox session first and then pass that same session to `agent.createSession()`.
583
- The agent does not need a sandbox provider and does not stop or destroy the
579
+ The agent does not create the sandbox and does not stop or destroy the
584
580
  caller-owned sandbox.
585
581
 
586
582
  ```ts
587
- import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent';
583
+ import { HarnessAgent } from '@ai-sdk/harness/agent';
588
584
  import { claudeCode } from '@ai-sdk/harness-claude-code';
589
- import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
585
+ import { createVercelNetworkSandboxSessionFromNativeSandbox } from '@ai-sdk/sandbox-vercel';
590
586
  import { Sandbox } from '@vercel/sandbox';
591
587
 
592
588
  const sandbox = await Sandbox.create({
593
589
  runtime: 'node24',
594
590
  ports: [4000],
595
591
  });
596
- const sandboxProvider = createVercelSandbox({ sandbox });
597
- const sandboxSession = await sandboxProvider.createSession();
598
-
599
- await prepareSandboxForHarness({
600
- session: sandboxSession.restricted(),
601
- harnesses: [claudeCode],
602
- });
592
+ const sandboxSession =
593
+ createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
603
594
 
604
595
  const agent = new HarnessAgent({ harness: claudeCode });
605
596
  const session = await agent.createSession({ sandboxSession });
@@ -617,7 +608,7 @@ try {
617
608
  }
618
609
  } finally {
619
610
  await session.destroy();
620
- await sandbox.stop();
611
+ await sandboxSession.destroy();
621
612
  }
622
613
  ```
623
614
 
@@ -634,29 +625,28 @@ configure the bridge port and its externally reachable endpoint on the adapter
634
625
  because the basic session cannot resolve them.
635
626
 
636
627
  ```ts
637
- import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent';
628
+ import { HarnessAgent } from '@ai-sdk/harness/agent';
638
629
  import { createClaudeCode } from '@ai-sdk/harness-claude-code';
639
- import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
630
+ import {
631
+ createVercelNetworkSandboxSessionFromNativeSandbox,
632
+ createVercelSandboxSessionFromNativeSandbox,
633
+ } from '@ai-sdk/sandbox-vercel';
640
634
  import { Sandbox } from '@vercel/sandbox';
641
635
 
642
636
  const sandbox = await Sandbox.create({
643
637
  runtime: 'node24',
644
638
  ports: [4000],
645
639
  });
646
- const sandboxProvider = createVercelSandbox({ sandbox });
647
- const sandboxSession = await sandboxProvider.createSession();
640
+ const sandboxSession =
641
+ createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
648
642
  const portEndpoint = await sandboxSession.getPortEndpoint({
649
643
  port: 4000,
650
644
  protocol: 'ws',
651
645
  });
652
- const restrictedSandboxSession = sandboxSession.restricted();
646
+ const restrictedSandboxSession =
647
+ createVercelSandboxSessionFromNativeSandbox(sandbox);
653
648
  const claudeCode = createClaudeCode({ port: 4000, portEndpoint });
654
649
 
655
- await prepareSandboxForHarness({
656
- session: restrictedSandboxSession,
657
- harnesses: [claudeCode],
658
- });
659
-
660
650
  const agent = new HarnessAgent({ harness: claudeCode });
661
651
  const session = await agent.createSession({
662
652
  sandboxSession: restrictedSandboxSession,
@@ -675,7 +665,7 @@ try {
675
665
  }
676
666
  } finally {
677
667
  await session.destroy();
678
- await sandbox.stop();
668
+ await sandboxSession.destroy();
679
669
  }
680
670
  ```
681
671
 
@@ -25,10 +25,6 @@ the combined tool set through `agent.tools`.
25
25
  ```ts
26
26
  const agent = new HarnessAgent({
27
27
  harness: claudeCode,
28
- sandbox: createVercelSandbox({
29
- runtime: 'node24',
30
- ports: [4000],
31
- }),
32
28
  });
33
29
 
34
30
  agent.tools.bash;
@@ -60,7 +56,6 @@ Pass AI SDK tools to `HarnessAgent` the same way you do for a `ToolLoopAgent`:
60
56
  ```ts
61
57
  import { HarnessAgent } from '@ai-sdk/harness/agent';
62
58
  import { claudeCode } from '@ai-sdk/harness-claude-code';
63
- import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
64
59
  import { tool } from 'ai';
65
60
  import { z } from 'zod';
66
61
 
@@ -82,10 +77,6 @@ const weather = tool({
82
77
 
83
78
  const agent = new HarnessAgent({
84
79
  harness: claudeCode,
85
- sandbox: createVercelSandbox({
86
- runtime: 'node24',
87
- ports: [4000],
88
- }),
89
80
  tools: { weather },
90
81
  });
91
82
  ```
@@ -129,6 +120,7 @@ When recreating a session for an unfinished turn, rebind it explicitly:
129
120
  const session = await agent.createSession({
130
121
  sessionId,
131
122
  continueFrom,
123
+ sandboxSession,
132
124
  toolsContext: {
133
125
  lookupAccount: { userId: 'user-123' },
134
126
  },
@@ -191,10 +183,6 @@ with `tools`.
191
183
  ```ts
192
184
  const agent = new HarnessAgent({
193
185
  harness: claudeCode,
194
- sandbox: createVercelSandbox({
195
- runtime: 'node24',
196
- ports: [4000],
197
- }),
198
186
  tools: { weather },
199
187
  activeTools: ['weather'],
200
188
  });
@@ -205,10 +193,6 @@ const agent = new HarnessAgent({
205
193
  ```ts
206
194
  const agent = new HarnessAgent({
207
195
  harness: claudeCode,
208
- sandbox: createVercelSandbox({
209
- runtime: 'node24',
210
- ports: [4000],
211
- }),
212
196
  tools: { weather },
213
197
  inactiveTools: ['bash', 'write'],
214
198
  });
@@ -260,7 +244,6 @@ Use `permissionMode` for adapter-native built-ins:
260
244
  ```ts
261
245
  const agent = new HarnessAgent({
262
246
  harness: pi,
263
- sandbox: createVercelSandbox({ runtime: 'node24' }),
264
247
  permissionMode: 'allow-edits',
265
248
  });
266
249
  ```
@@ -279,10 +262,6 @@ Use `toolApproval` for host-executed tools:
279
262
  ```ts
280
263
  const agent = new HarnessAgent({
281
264
  harness: claudeCode,
282
- sandbox: createVercelSandbox({
283
- runtime: 'node24',
284
- ports: [4000],
285
- }),
286
265
  tools: { weather },
287
266
  toolApproval: {
288
267
  weather: 'user-approval',
@@ -18,10 +18,6 @@ Pass skills to `HarnessAgent` with the `skills` setting:
18
18
  ```ts
19
19
  const agent = new HarnessAgent({
20
20
  harness: claudeCode,
21
- sandbox: createVercelSandbox({
22
- runtime: 'node24',
23
- ports: [4000],
24
- }),
25
21
  skills: [
26
22
  {
27
23
  name: 'careful-refactors',