ai 7.0.160 → 7.0.161

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.
@@ -579,6 +579,61 @@ to `'.'` to use the sandbox's default working directory itself. When omitted,
579
579
  regular sessions use the default `<harnessId>-<sessionId>` directory, while
580
580
  `onBootstrap` receives the sandbox's default working directory.
581
581
 
582
+ When the harness declares a sandbox bootstrap recipe, as Claude Code and Codex
583
+ do, `createSession()` runs the session's sandbox setup before the harness
584
+ starts: it runs `onBootstrap` when its marker is missing, creates the session
585
+ working directory, and calls `onSession`. For any other harness, such as Pi,
586
+ the setup runs on the first file, exec, or spawn operation that the harness or
587
+ a host tool performs on the sandbox. A session whose turns never touch the
588
+ sandbox then makes no setup calls. If the setup fails, the operation that
589
+ triggered it rejects, and the next operation runs the setup again. A sandbox
590
+ session without `defaultWorkingDirectory` still answers one `pwd` in
591
+ `createSession()`, because the session working directory is derived from it.
592
+ When the sandbox comes from the `sandbox` provider setting instead of
593
+ `sandboxSession`, the provider runs `onBootstrap` when it creates a new
594
+ sandbox, which happens in `createSession()`.
595
+
596
+ ## Start the Sandbox on First Use
597
+
598
+ Deferring the setup does not defer the sandbox itself: a session you pass to
599
+ `createSession()` already exists. To defer the sandbox as well, pass
600
+ `defaultWorkingDirectory` and a `createSandboxSession` function instead. With
601
+ a harness that has no sandbox bootstrap recipe, such as Pi, `createSession()`
602
+ returns without calling `createSandboxSession`, and the first file, exec, or
603
+ spawn operation calls it, so a turn that only chats never starts the sandbox.
604
+ For any other harness, `createSession()` calls it right away.
605
+
606
+ ```ts
607
+ import { HarnessAgent } from '@ai-sdk/harness/agent';
608
+ import { pi } from '@ai-sdk/harness-pi';
609
+ import { createVercelNetworkSandboxSessionFromNativeSandbox } from '@ai-sdk/sandbox-vercel';
610
+ import { Sandbox } from '@vercel/sandbox';
611
+
612
+ const agent = new HarnessAgent({ harness: pi });
613
+
614
+ const session = await agent.createSession({
615
+ sessionId: chatId,
616
+ resumeFrom: resumeState,
617
+ sandboxSession: {
618
+ defaultWorkingDirectory: '/vercel/sandbox',
619
+ createSandboxSession: async () =>
620
+ createVercelNetworkSandboxSessionFromNativeSandbox(
621
+ await Sandbox.getOrCreate({
622
+ name: `chat-${chatId}`,
623
+ runtime: 'node24',
624
+ }),
625
+ ),
626
+ },
627
+ });
628
+ ```
629
+
630
+ The harness needs the working directory before the sandbox exists, so
631
+ `defaultWorkingDirectory` must match the one the sandbox reports. Because the
632
+ sandbox is looked up by name, the same call works for the first turn and for
633
+ every later one. A failed `createSandboxSession` or setup is retried on the
634
+ next operation. You own the sandbox lifecycle, as with any session you pass to
635
+ `createSession()`.
636
+
582
637
  ## Prepare Reusable Sandboxes
583
638
 
584
639
  Use `agent.getSandboxTemplate()` to resolve one harness's bootstrap recipe and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.160",
3
+ "version": "7.0.161",
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.133",
45
+ "@ai-sdk/gateway": "4.0.134",
46
46
  "@ai-sdk/provider": "4.0.35",
47
- "@ai-sdk/provider-utils": "5.0.72"
47
+ "@ai-sdk/provider-utils": "5.0.73"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.130",
51
- "@ai-sdk/deepseek": "3.0.77",
52
- "@ai-sdk/google": "4.0.109",
53
- "@ai-sdk/groq": "4.0.74",
54
- "@ai-sdk/huggingface": "2.0.82",
55
- "@ai-sdk/moonshotai": "3.0.82",
56
- "@ai-sdk/openai": "4.0.106",
50
+ "@ai-sdk/amazon-bedrock": "5.0.131",
51
+ "@ai-sdk/deepseek": "3.0.78",
52
+ "@ai-sdk/google": "4.0.110",
53
+ "@ai-sdk/groq": "4.0.75",
54
+ "@ai-sdk/huggingface": "2.0.83",
55
+ "@ai-sdk/moonshotai": "3.0.83",
56
+ "@ai-sdk/openai": "4.0.107",
57
57
  "@ai-sdk/test-server": "2.0.4",
58
- "@ai-sdk/xai": "5.0.36",
58
+ "@ai-sdk/xai": "5.0.37",
59
59
  "@edge-runtime/vm": "^5.0.0",
60
60
  "@smithy/eventstream-codec": "^4.3.3",
61
61
  "@smithy/util-utf8": "^4.3.3",
@@ -1,3 +1,4 @@
1
+ import { convertDataContentToUint8Array } from '../prompt/data-content';
1
2
  import {
2
3
  convertBase64ToUint8Array,
3
4
  convertUint8ArrayToBase64,
@@ -48,13 +49,15 @@ export class DefaultGeneratedFile implements GeneratedFile {
48
49
  mediaType,
49
50
  providerMetadata,
50
51
  }: {
51
- data: string | Uint8Array;
52
+ data: string | Uint8Array | ArrayBuffer;
52
53
  mediaType: string;
53
54
  providerMetadata?: Record<string, JSONObject>;
54
55
  }) {
55
- const isUint8Array = data instanceof Uint8Array;
56
- this.base64Data = isUint8Array ? undefined : data;
57
- this.uint8ArrayData = isUint8Array ? data : undefined;
56
+ this.base64Data = typeof data === 'string' ? data : undefined;
57
+ this.uint8ArrayData =
58
+ typeof data === 'string'
59
+ ? undefined
60
+ : convertDataContentToUint8Array(data);
58
61
  this.mediaType = mediaType;
59
62
  this.providerMetadata = providerMetadata;
60
63
  }
@@ -1,4 +1,8 @@
1
- import type { ModelMessage } from '@ai-sdk/provider-utils';
1
+ import {
2
+ isArrayBuffer,
3
+ isUint8Array,
4
+ type ModelMessage,
5
+ } from '@ai-sdk/provider-utils';
2
6
 
3
7
  /**
4
8
  * Clone model messages while preserving URL instances. Node's structuredClone
@@ -7,35 +11,67 @@ import type { ModelMessage } from '@ai-sdk/provider-utils';
7
11
  export function cloneModelMessages<T extends ModelMessage>(
8
12
  messages: Array<T>,
9
13
  ): Array<T> {
10
- return messages.map(message => cloneValue(message)) as Array<T>;
14
+ return cloneValue(messages, new Map()) as Array<T>;
11
15
  }
12
16
 
13
- function cloneValue<T>(value: T): T {
17
+ function cloneValue(value: unknown, seen: Map<object, unknown>): unknown {
18
+ if (value == null || typeof value !== 'object') {
19
+ return value;
20
+ }
21
+
22
+ if (seen.has(value)) {
23
+ return seen.get(value);
24
+ }
25
+
14
26
  if (value instanceof URL) {
15
- return new URL(value.href) as T;
27
+ const copy = new URL(value.href);
28
+ seen.set(value, copy);
29
+ return copy;
16
30
  }
17
31
 
18
32
  if (Array.isArray(value)) {
19
- return value.map(item => cloneValue(item)) as T;
33
+ const copy: unknown[] = new Array(value.length);
34
+ seen.set(value, copy);
35
+ value.forEach((item, index) => {
36
+ copy[index] = cloneValue(item, seen);
37
+ });
38
+ return copy;
20
39
  }
21
40
 
22
- if (value instanceof Uint8Array) {
23
- return new Uint8Array(value) as T;
41
+ if (isUint8Array(value)) {
42
+ const copy = new Uint8Array(value);
43
+ seen.set(value, copy);
44
+ return copy;
24
45
  }
25
46
 
26
- if (value instanceof ArrayBuffer) {
27
- return value.slice(0) as T;
47
+ if (isArrayBuffer(value)) {
48
+ const copy = new Uint8Array(value).slice().buffer;
49
+ seen.set(value, copy);
50
+ return copy;
28
51
  }
29
52
 
30
- if (value instanceof Date) {
31
- return new Date(value) as T;
53
+ if (
54
+ value instanceof Date ||
55
+ Object.prototype.toString.call(value) === '[object Date]'
56
+ ) {
57
+ try {
58
+ const copy = new Date(Date.prototype.getTime.call(value));
59
+ seen.set(value, copy);
60
+ return copy;
61
+ } catch {
62
+ // A string tag alone does not make a plain object a Date.
63
+ }
32
64
  }
33
65
 
34
- if (value != null && typeof value === 'object') {
35
- return Object.fromEntries(
36
- Object.entries(value).map(([key, value]) => [key, cloneValue(value)]),
37
- ) as T;
66
+ const copy: Record<string, unknown> = {};
67
+ seen.set(value, copy);
68
+ for (const [key, val] of Object.entries(value)) {
69
+ Object.defineProperty(copy, key, {
70
+ value: cloneValue(val, seen),
71
+ writable: true,
72
+ enumerable: true,
73
+ configurable: true,
74
+ });
38
75
  }
39
-
40
- return value;
76
+ return copy;
41
77
  }
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  isBuffer,
3
+ isUint8Array,
4
+ isArrayBuffer,
3
5
  type CustomPart,
4
6
  type DataContent,
5
7
  type FilePart,
@@ -19,8 +21,8 @@ import { z, type ZodType } from '../util/zod';
19
21
 
20
22
  const fileInlineDataSchema: ZodType<DataContent> = z.union([
21
23
  z.string(),
22
- z.instanceof(Uint8Array),
23
- z.instanceof(ArrayBuffer),
24
+ z.custom<Uint8Array>(isUint8Array, { message: 'Must be a Uint8Array' }),
25
+ z.custom<ArrayBuffer>(isArrayBuffer, { message: 'Must be an ArrayBuffer' }),
24
26
  z.custom<Buffer>(isBuffer, { message: 'Must be a Buffer' }),
25
27
  ]);
26
28
 
@@ -1,4 +1,6 @@
1
1
  import {
2
+ isUint8Array,
3
+ isArrayBuffer,
2
4
  convertBase64ToUint8Array,
3
5
  convertUint8ArrayToBase64,
4
6
  type DataContent,
@@ -16,11 +18,7 @@ export function convertDataContentToBase64String(content: DataContent): string {
16
18
  return content;
17
19
  }
18
20
 
19
- if (content instanceof ArrayBuffer) {
20
- return convertUint8ArrayToBase64(new Uint8Array(content));
21
- }
22
-
23
- return convertUint8ArrayToBase64(content);
21
+ return convertUint8ArrayToBase64(convertDataContentToUint8Array(content));
24
22
  }
25
23
 
26
24
  /**
@@ -32,8 +30,12 @@ export function convertDataContentToBase64String(content: DataContent): string {
32
30
  export function convertDataContentToUint8Array(
33
31
  content: DataContent,
34
32
  ): Uint8Array {
35
- if (content instanceof Uint8Array) {
36
- return content;
33
+ if (isUint8Array(content)) {
34
+ if (content instanceof Uint8Array) {
35
+ return content;
36
+ }
37
+ const { buffer, byteOffset, byteLength } = content;
38
+ return new Uint8Array(buffer, byteOffset, byteLength);
37
39
  }
38
40
 
39
41
  if (typeof content === 'string') {
@@ -49,7 +51,7 @@ export function convertDataContentToUint8Array(
49
51
  }
50
52
  }
51
53
 
52
- if (content instanceof ArrayBuffer) {
54
+ if (isArrayBuffer(content)) {
53
55
  return new Uint8Array(content);
54
56
  }
55
57
 
@@ -1,5 +1,8 @@
1
+ import { convertDataContentToUint8Array } from './data-content';
1
2
  import type { LanguageModelV4FilePart } from '@ai-sdk/provider';
2
3
  import {
4
+ isUint8Array,
5
+ isArrayBuffer,
3
6
  isBuffer,
4
7
  isProviderReference,
5
8
  type DataContent,
@@ -58,12 +61,9 @@ function convertUrlStringToFilePartData(content: string): ConvertResult {
58
61
  }
59
62
 
60
63
  function convertInlineDataToFilePartData(content: DataContent): ConvertResult {
61
- if (content instanceof Uint8Array) {
62
- return { data: { type: 'data', data: content }, mediaType: undefined };
63
- }
64
- if (content instanceof ArrayBuffer) {
64
+ if (isUint8Array(content) || isArrayBuffer(content)) {
65
65
  return {
66
- data: { type: 'data', data: new Uint8Array(content) },
66
+ data: { type: 'data', data: convertDataContentToUint8Array(content) },
67
67
  mediaType: undefined,
68
68
  };
69
69
  }
@@ -130,6 +130,10 @@ export function convertToLanguageModelV4FilePart(
130
130
  }
131
131
  }
132
132
 
133
+ if (isUint8Array(content) || isArrayBuffer(content)) {
134
+ return convertInlineDataToFilePartData(content);
135
+ }
136
+
133
137
  if (isProviderReference(content)) {
134
138
  return {
135
139
  data: { type: 'reference', reference: content as ProviderReference },