@ai-sdk/sandbox-vercel 0.0.0 → 1.0.0-canary.2

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 ADDED
@@ -0,0 +1,20 @@
1
+ # @ai-sdk/sandbox-vercel
2
+
3
+ ## 1.0.0-canary.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c7a3e5: Start the `1.0.0` canary release line for the experimental harness and sandbox packages. They were unintentionally published as `0.0.0-canary.*` because they were scaffolded with a `0.0.0-canary.0` premajor version, which semver could not advance past on a major bump.
8
+ - Updated dependencies [6c7a3e5]
9
+ - @ai-sdk/harness@1.0.0-canary.2
10
+
11
+ ## 0.0.0-canary.1
12
+
13
+ ### Major Changes
14
+
15
+ - 9d6dbe0: feat(harness): add sandbox specific expansion for harness abstraction, add `sandbox-just-bash` and `sandbox-vercel`
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [9d6dbe0]
20
+ - @ai-sdk/harness@0.0.0-canary.1
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # AI SDK - Vercel Sandbox
2
+
3
+ _This package is **experimental**._
4
+
5
+ `HarnessV1SandboxProvider` implementation for [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox).
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ npm i @ai-sdk/sandbox-vercel @vercel/sandbox
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ The factory is synchronous. The returned provider is stable; the actual `@vercel/sandbox` `Sandbox` is created on demand inside `provider.createSession()`.
16
+
17
+ ```ts
18
+ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
19
+
20
+ const vercelSandbox = createVercelSandbox({
21
+ runtime: 'node24',
22
+ ports: [3000],
23
+ });
24
+
25
+ const networkSandboxSession = await vercelSandbox.createSession();
26
+ const sandboxSession = networkSandboxSession.restricted();
27
+
28
+ await sandboxSession.writeTextFile({ path: 'hello.txt', content: 'hi' });
29
+
30
+ const { stdout } = await sandboxSession.run({
31
+ command: 'cat hello.txt',
32
+ });
33
+ console.log(stdout); // "hi"
34
+ await networkSandboxSession.stop();
35
+ ```
36
+
37
+ `networkSandboxSession.restricted()` is typed as `Experimental_SandboxSession`, so it's safe to pass to AI SDK tools that accept `experimental_sandbox`. The network sandbox session itself carries the infra surface (`ports`, `getPortUrl`, `setNetworkPolicy`, `stop`) that only the harness should reach for.
38
+
39
+ The flat-field settings are aliased directly from `@vercel/sandbox`'s `Sandbox.create` parameters, so every option Vercel supports — including its native `NetworkPolicy` — is available without re-declaration:
40
+
41
+ ```ts
42
+ const sandbox = createVercelSandbox({
43
+ runtime: 'node24',
44
+ ports: [3000],
45
+ timeout: 10 * 60 * 1000,
46
+ networkPolicy: {
47
+ allow: ['api.example.com'],
48
+ subnets: { deny: ['169.254.169.254/32'] },
49
+ },
50
+ });
51
+ ```
52
+
53
+ To wrap an already-created `@vercel/sandbox` `Sandbox` instead — e.g. when you need credentials or options outside the factory's settings, or you want to share one sandbox across multiple harness sessions — pass it via `sandbox`. The network sandbox session's `stop()` is a no-op in this case; the caller owns the lifecycle.
54
+
55
+ ```ts
56
+ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
57
+ import { Sandbox } from '@vercel/sandbox';
58
+
59
+ const sandbox = createVercelSandbox({
60
+ sandbox: await Sandbox.create({ runtime: 'node24', ports: [3000] }),
61
+ });
62
+ ```
63
+
64
+ ### Mid-session network policy
65
+
66
+ Once the network sandbox session is alive, the host can update outbound network policy on the running sandbox:
67
+
68
+ ```ts
69
+ await networkSandboxSession.setNetworkPolicy?.({
70
+ mode: 'custom',
71
+ allowedHosts: ['api.example.com'],
72
+ deniedCIDRs: ['169.254.169.254/32'],
73
+ });
74
+ ```
75
+
76
+ `HarnessV1NetworkPolicy` is the harness-level abstraction used here. The provider translates it to `@vercel/sandbox`'s native `NetworkPolicy` for enforcement.
@@ -0,0 +1,77 @@
1
+ import { HarnessV1SandboxProvider, HarnessV1NetworkSandboxSession } from '@ai-sdk/harness';
2
+ import { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
3
+ import { Sandbox } from '@vercel/sandbox';
4
+
5
+ /**
6
+ * Flattens an intersection of object types into a single object type so the
7
+ * resolved shape displays as its named properties rather than a chain of
8
+ * `A & B & C`.
9
+ */
10
+ type Prettify<T> = {
11
+ [K in keyof T]: T[K];
12
+ } & {};
13
+ /**
14
+ * Distributes `Omit` across each member of a union instead of collapsing the
15
+ * union to its common keys. `Sandbox.create`'s parameter is a union (a
16
+ * git/tarball/no-source create variant and a snapshot-source create variant),
17
+ * so a plain `Omit` would discard keys absent from any one member (e.g.
18
+ * `runtime`, which the snapshot variant lacks) and merge the `source` shapes.
19
+ * Applying `Omit` per-member preserves every variant intact; the `Prettify`
20
+ * wrapper collapses each member's intersections into a readable object shape.
21
+ */
22
+ type DistributiveOmit<T, K extends keyof any> = T extends unknown ? Prettify<Omit<T, K>> : never;
23
+ /**
24
+ * Parameters forwarded to `@vercel/sandbox`'s `Sandbox.create` when creating
25
+ * a sandbox from scratch. Aliased directly from the underlying SDK so the
26
+ * full surface — every option Vercel supports, including its native
27
+ * `NetworkPolicy` — is available without us re-declaring it.
28
+ */
29
+ type VercelSandboxCreateParams = DistributiveOmit<NonNullable<Parameters<typeof Sandbox.create>[0]>, 'onResume'>;
30
+ /**
31
+ * Settings for {@link createVercelSandbox}. Two mutually-exclusive shapes:
32
+ *
33
+ * - `{ sandbox }` — wrap an already-created `@vercel/sandbox` `Sandbox`. The
34
+ * caller owns its lifecycle; the provider's `stop()` and `destroy()` are
35
+ * no-ops. Optionally declare `bridgePorts` to give the harness a port pool to
36
+ * lease from for concurrent sessions on the same provided sandbox.
37
+ * - {@link VercelSandboxCreateParams} fields — provider creates the underlying
38
+ * sandbox. When the adapter declares a bootstrap recipe the provider uses
39
+ * `Sandbox.getOrCreate` to maintain a persistent named template snapshot
40
+ * keyed by the recipe identity, and forks an ephemeral sandbox per session
41
+ * from the snapshot. Use `name` to override the auto-derived template name.
42
+ */
43
+ type VercelSandboxSettings = {
44
+ sandbox: Sandbox;
45
+ bridgePorts?: ReadonlyArray<number>;
46
+ } | (VercelSandboxCreateParams & {
47
+ sandbox?: never;
48
+ name?: string;
49
+ });
50
+ declare function createVercelSandbox(settings?: VercelSandboxSettings): HarnessV1SandboxProvider;
51
+ /**
52
+ * `HarnessV1SandboxProvider` implementation backed by `@vercel/sandbox`.
53
+ * Construct one via {@link createVercelSandbox} at module scope and pass it
54
+ * to a `HarnessAgent` (or call `createSession()` directly if you want raw
55
+ * access to a network sandbox session).
56
+ */
57
+ declare class VercelSandboxProvider implements HarnessV1SandboxProvider {
58
+ private readonly settings;
59
+ readonly specificationVersion: "harness-sandbox-v1";
60
+ readonly providerId = "vercel-sandbox";
61
+ readonly bridgePorts?: ReadonlyArray<number>;
62
+ constructor(settings: VercelSandboxSettings);
63
+ createSession: (options?: {
64
+ sessionId?: string;
65
+ abortSignal?: AbortSignal;
66
+ identity?: string;
67
+ onFirstCreate?: (session: Experimental_SandboxSession, opts: {
68
+ abortSignal?: AbortSignal;
69
+ }) => Promise<void>;
70
+ }) => Promise<HarnessV1NetworkSandboxSession>;
71
+ resumeSession: (options: {
72
+ sessionId: string;
73
+ abortSignal?: AbortSignal;
74
+ }) => Promise<HarnessV1NetworkSandboxSession>;
75
+ }
76
+
77
+ export { VercelSandboxProvider, type VercelSandboxSettings, createVercelSandbox };
package/dist/index.js ADDED
@@ -0,0 +1,474 @@
1
+ // src/vercel-sandbox.ts
2
+ import { Sandbox } from "@vercel/sandbox";
3
+
4
+ // src/vercel-network-sandbox-session.ts
5
+ import {
6
+ HarnessCapabilityUnsupportedError
7
+ } from "@ai-sdk/harness";
8
+
9
+ // src/vercel-sandbox-session.ts
10
+ import { posix } from "path";
11
+ import {
12
+ extractLines
13
+ } from "@ai-sdk/provider-utils";
14
+ var VercelSandboxSession = class {
15
+ constructor(sandbox) {
16
+ this.sandbox = sandbox;
17
+ }
18
+ get description() {
19
+ return [
20
+ `Vercel Sandbox (name: ${this.sandbox.name}).`,
21
+ "Filesystem changes persist for the lifetime of the sandbox."
22
+ ].join("\n");
23
+ }
24
+ async run({
25
+ command,
26
+ workingDirectory,
27
+ env,
28
+ abortSignal
29
+ }) {
30
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
31
+ const finished = await this.sandbox.runCommand({
32
+ cmd: "bash",
33
+ args: ["-c", command],
34
+ ...workingDirectory !== void 0 ? { cwd: workingDirectory } : {},
35
+ ...env !== void 0 ? { env } : {},
36
+ ...abortSignal !== void 0 ? { signal: abortSignal } : {}
37
+ });
38
+ const [stdout, stderr] = await Promise.all([
39
+ finished.stdout(),
40
+ finished.stderr()
41
+ ]);
42
+ return {
43
+ exitCode: finished.exitCode,
44
+ stdout,
45
+ stderr
46
+ };
47
+ }
48
+ async spawn({
49
+ command,
50
+ workingDirectory,
51
+ env,
52
+ abortSignal
53
+ }) {
54
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
55
+ const live = await this.sandbox.runCommand({
56
+ cmd: "bash",
57
+ args: ["-c", command],
58
+ detached: true,
59
+ ...workingDirectory !== void 0 ? { cwd: workingDirectory } : {},
60
+ ...env !== void 0 ? { env } : {},
61
+ ...abortSignal !== void 0 ? { signal: abortSignal } : {}
62
+ });
63
+ return createSandboxProcess(live, abortSignal);
64
+ }
65
+ async readFile({
66
+ path,
67
+ abortSignal
68
+ }) {
69
+ const bytes = await this.readBinaryFile({ path, abortSignal });
70
+ if (bytes == null) return null;
71
+ return bytesToStream(bytes);
72
+ }
73
+ async readBinaryFile({
74
+ path,
75
+ abortSignal
76
+ }) {
77
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
78
+ try {
79
+ const buffer = await this.sandbox.readFileToBuffer(
80
+ { path },
81
+ abortSignal ? { signal: abortSignal } : void 0
82
+ );
83
+ if (buffer == null) return null;
84
+ return new Uint8Array(
85
+ buffer.buffer,
86
+ buffer.byteOffset,
87
+ buffer.byteLength
88
+ );
89
+ } catch (error) {
90
+ if (isFileNotFoundError(error)) return null;
91
+ throw error;
92
+ }
93
+ }
94
+ async readTextFile({
95
+ path,
96
+ encoding = "utf-8",
97
+ startLine,
98
+ endLine,
99
+ abortSignal
100
+ }) {
101
+ const bytes = await this.readBinaryFile({ path, abortSignal });
102
+ if (bytes == null) return null;
103
+ const text = Buffer.from(bytes).toString(encoding);
104
+ return extractLines({ text, startLine, endLine });
105
+ }
106
+ async writeFile({
107
+ path,
108
+ content,
109
+ abortSignal
110
+ }) {
111
+ const bytes = await collectStream(content);
112
+ await this.writeBinaryFile({ path, content: bytes, abortSignal });
113
+ }
114
+ async writeBinaryFile({
115
+ path,
116
+ content,
117
+ abortSignal
118
+ }) {
119
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
120
+ const parent = posix.dirname(path);
121
+ if (parent && parent !== "." && parent !== "/") {
122
+ await this.sandbox.runCommand({
123
+ cmd: "mkdir",
124
+ args: ["-p", parent],
125
+ ...abortSignal !== void 0 ? { signal: abortSignal } : {}
126
+ });
127
+ }
128
+ await this.sandbox.writeFiles(
129
+ [{ path, content }],
130
+ abortSignal ? { signal: abortSignal } : void 0
131
+ );
132
+ }
133
+ async writeTextFile({
134
+ path,
135
+ content,
136
+ encoding = "utf-8",
137
+ abortSignal
138
+ }) {
139
+ const buffer = Buffer.from(content, encoding);
140
+ await this.writeBinaryFile({
141
+ path,
142
+ content: new Uint8Array(
143
+ buffer.buffer,
144
+ buffer.byteOffset,
145
+ buffer.byteLength
146
+ ),
147
+ abortSignal
148
+ });
149
+ }
150
+ };
151
+ function createSandboxProcess(command, abortSignal) {
152
+ const encoder = new TextEncoder();
153
+ const controllers = {};
154
+ const stdout = new ReadableStream({
155
+ start(controller) {
156
+ controllers.stdout = controller;
157
+ }
158
+ });
159
+ const stderr = new ReadableStream({
160
+ start(controller) {
161
+ controllers.stderr = controller;
162
+ }
163
+ });
164
+ const drained = (async () => {
165
+ var _a, _b, _c, _d;
166
+ try {
167
+ const iterator = abortSignal ? command.logs({ signal: abortSignal }) : command.logs();
168
+ for await (const message of iterator) {
169
+ const target = message.stream === "stdout" ? controllers.stdout : controllers.stderr;
170
+ target == null ? void 0 : target.enqueue(encoder.encode(message.data));
171
+ }
172
+ (_a = controllers.stdout) == null ? void 0 : _a.close();
173
+ (_b = controllers.stderr) == null ? void 0 : _b.close();
174
+ } catch (error) {
175
+ (_c = controllers.stdout) == null ? void 0 : _c.error(error);
176
+ (_d = controllers.stderr) == null ? void 0 : _d.error(error);
177
+ }
178
+ })();
179
+ return {
180
+ stdout,
181
+ stderr,
182
+ async wait() {
183
+ var _a;
184
+ const finished = await command.wait();
185
+ await drained;
186
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
187
+ throw (_a = abortSignal.reason) != null ? _a : new DOMException("Aborted", "AbortError");
188
+ }
189
+ return { exitCode: finished.exitCode };
190
+ },
191
+ async kill() {
192
+ await command.kill();
193
+ }
194
+ };
195
+ }
196
+ function bytesToStream(bytes) {
197
+ return new ReadableStream({
198
+ start(controller) {
199
+ controller.enqueue(bytes);
200
+ controller.close();
201
+ }
202
+ });
203
+ }
204
+ async function collectStream(stream) {
205
+ const reader = stream.getReader();
206
+ const chunks = [];
207
+ let total = 0;
208
+ while (true) {
209
+ const { value, done } = await reader.read();
210
+ if (done) break;
211
+ if (value) {
212
+ chunks.push(value);
213
+ total += value.byteLength;
214
+ }
215
+ }
216
+ const out = new Uint8Array(total);
217
+ let offset = 0;
218
+ for (const chunk of chunks) {
219
+ out.set(chunk, offset);
220
+ offset += chunk.byteLength;
221
+ }
222
+ return out;
223
+ }
224
+ function isFileNotFoundError(error) {
225
+ if (error == null || typeof error !== "object") return false;
226
+ const code = error.code;
227
+ if (code === "ENOENT") return true;
228
+ const message = error.message;
229
+ return typeof message === "string" && /no such file|not found|does not exist|ENOENT/i.test(message);
230
+ }
231
+
232
+ // src/vercel-network-sandbox-session.ts
233
+ var VERCEL_PROVIDER_ID = "vercel-sandbox";
234
+ var VercelNetworkSandboxSession = class extends VercelSandboxSession {
235
+ constructor(input) {
236
+ super(input.sandbox);
237
+ this.getPortUrl = async (options) => {
238
+ var _a;
239
+ const exposedPorts = this.ports;
240
+ if (!exposedPorts.includes(options.port)) {
241
+ throw new HarnessCapabilityUnsupportedError({
242
+ harnessId: VERCEL_PROVIDER_ID,
243
+ message: `Port ${options.port} is not exposed on this sandbox. Exposed ports: [${exposedPorts.join(", ")}].`
244
+ });
245
+ }
246
+ const protocol = (_a = options.protocol) != null ? _a : "https";
247
+ const url = new URL(this.sandbox.domain(options.port));
248
+ const isSecure = url.protocol === "https:";
249
+ switch (protocol) {
250
+ case "http":
251
+ url.protocol = isSecure ? "https:" : "http:";
252
+ break;
253
+ case "https":
254
+ url.protocol = "https:";
255
+ break;
256
+ case "ws":
257
+ url.protocol = isSecure ? "wss:" : "ws:";
258
+ break;
259
+ }
260
+ return url.toString();
261
+ };
262
+ this.setNetworkPolicy = async (policy) => {
263
+ await this.sandbox.update({ networkPolicy: toVercelPolicy(policy) });
264
+ };
265
+ this.setPorts = async (ports, options) => {
266
+ await this.sandbox.update(
267
+ { ports: [...ports] },
268
+ (options == null ? void 0 : options.abortSignal) ? { signal: options.abortSignal } : void 0
269
+ );
270
+ };
271
+ this.stop = async () => {
272
+ if (!this.ownsLifecycle) return;
273
+ await this.sandbox.stop();
274
+ };
275
+ this.destroy = async () => {
276
+ if (!this.ownsLifecycle) return;
277
+ await this.sandbox.stop().catch(() => {
278
+ });
279
+ await this.sandbox.delete();
280
+ };
281
+ this.ownsLifecycle = input.ownsLifecycle;
282
+ this.id = input.sandbox.name;
283
+ this.defaultWorkingDirectory = input.sandbox.currentSession().cwd;
284
+ }
285
+ get ports() {
286
+ return this.sandbox.routes.map((route) => route.port);
287
+ }
288
+ restricted() {
289
+ return new VercelSandboxSession(this.sandbox);
290
+ }
291
+ };
292
+ function toVercelPolicy(policy) {
293
+ switch (policy.mode) {
294
+ case "allow-all":
295
+ return "allow-all";
296
+ case "deny-all":
297
+ return "deny-all";
298
+ case "custom": {
299
+ const result = {};
300
+ const { allowedHosts, allowedCIDRs, deniedCIDRs } = policy;
301
+ if (allowedHosts != null && allowedHosts.length > 0) {
302
+ result.allow = [...allowedHosts];
303
+ }
304
+ if (allowedCIDRs != null && allowedCIDRs.length > 0 || deniedCIDRs != null && deniedCIDRs.length > 0) {
305
+ result.subnets = {
306
+ ...allowedCIDRs != null && allowedCIDRs.length > 0 ? { allow: [...allowedCIDRs] } : {},
307
+ ...deniedCIDRs != null && deniedCIDRs.length > 0 ? { deny: [...deniedCIDRs] } : {}
308
+ };
309
+ }
310
+ if (result.allow == null && result.subnets == null) {
311
+ throw new HarnessCapabilityUnsupportedError({
312
+ harnessId: VERCEL_PROVIDER_ID,
313
+ message: "Custom network policy requires at least one of allowedHosts or allowedCIDRs to be non-empty."
314
+ });
315
+ }
316
+ return result;
317
+ }
318
+ }
319
+ }
320
+
321
+ // src/vercel-sandbox.ts
322
+ var DEFAULT_SANDBOX_TIMEOUT_MS = 30 * 60 * 1e3;
323
+ var VERCEL_PROVIDER_ID2 = "vercel-sandbox";
324
+ var TEMPLATE_NAME_PREFIX = "ai-sdk-harness";
325
+ var SESSION_NAME_PREFIX = "ai-sdk-harness-session";
326
+ var SNAPSHOT_POLL_INTERVAL_MS = 500;
327
+ var SNAPSHOT_POLL_TIMEOUT_MS = 3e4;
328
+ function sessionSandboxName(sessionId) {
329
+ return `${SESSION_NAME_PREFIX}-${sessionId}`;
330
+ }
331
+ function createVercelSandbox(settings = {}) {
332
+ return new VercelSandboxProvider(settings);
333
+ }
334
+ var VercelSandboxProvider = class {
335
+ constructor(settings) {
336
+ this.settings = settings;
337
+ this.specificationVersion = "harness-sandbox-v1";
338
+ this.providerId = VERCEL_PROVIDER_ID2;
339
+ this.createSession = async (options) => {
340
+ var _a, _b, _c, _d;
341
+ (_a = options == null ? void 0 : options.abortSignal) == null ? void 0 : _a.throwIfAborted();
342
+ if ("sandbox" in this.settings && this.settings.sandbox != null) {
343
+ return new VercelNetworkSandboxSession({
344
+ sandbox: this.settings.sandbox,
345
+ ownsLifecycle: false
346
+ });
347
+ }
348
+ const settings = this.settings;
349
+ const {
350
+ sandbox: _ignoredSandbox,
351
+ name: explicitName,
352
+ ...createParams
353
+ } = settings;
354
+ const baseParams = {
355
+ ...createParams,
356
+ timeout: (_b = createParams.timeout) != null ? _b : DEFAULT_SANDBOX_TIMEOUT_MS
357
+ };
358
+ const identity = options == null ? void 0 : options.identity;
359
+ const onFirstCreate = options == null ? void 0 : options.onFirstCreate;
360
+ const sessionNameOverride = (options == null ? void 0 : options.sessionId) ? { name: sessionSandboxName(options.sessionId) } : {};
361
+ if (identity == null || onFirstCreate == null) {
362
+ const sandbox = await Sandbox.create({
363
+ ...baseParams,
364
+ ...sessionNameOverride,
365
+ ...(options == null ? void 0 : options.abortSignal) ? { signal: options.abortSignal } : {}
366
+ });
367
+ return new VercelNetworkSandboxSession({ sandbox, ownsLifecycle: true });
368
+ }
369
+ const templateName = explicitName != null ? explicitName : `${TEMPLATE_NAME_PREFIX}-${identity}`;
370
+ const cache = getSnapshotCache();
371
+ let snapshotId = cache.get(templateName);
372
+ if (snapshotId == null) {
373
+ const template = await Sandbox.getOrCreate({
374
+ ...baseParams,
375
+ name: templateName,
376
+ persistent: true,
377
+ snapshotExpiration: (_c = baseParams.snapshotExpiration) != null ? _c : 0,
378
+ onCreate: async (sbx) => {
379
+ await onFirstCreate(new VercelSandboxSession(sbx), {
380
+ abortSignal: options == null ? void 0 : options.abortSignal
381
+ });
382
+ },
383
+ ...(options == null ? void 0 : options.abortSignal) ? { signal: options.abortSignal } : {}
384
+ });
385
+ let resolvedId = template.currentSnapshotId;
386
+ if (resolvedId == null) {
387
+ const stopResult = await template.stop(
388
+ (options == null ? void 0 : options.abortSignal) ? { signal: options.abortSignal } : void 0
389
+ );
390
+ resolvedId = (_d = stopResult.snapshot) == null ? void 0 : _d.id;
391
+ if (resolvedId == null) {
392
+ resolvedId = await pollForTemplateSnapshot(
393
+ templateName,
394
+ options == null ? void 0 : options.abortSignal
395
+ );
396
+ }
397
+ }
398
+ cache.set(templateName, resolvedId);
399
+ snapshotId = resolvedId;
400
+ }
401
+ const {
402
+ runtime: _ignoredRuntime,
403
+ source: _ignoredSource,
404
+ persistent: _ignoredPersistent,
405
+ ...forkParams
406
+ } = baseParams;
407
+ const fork = await Sandbox.create({
408
+ ...forkParams,
409
+ source: { type: "snapshot", snapshotId },
410
+ ...sessionNameOverride,
411
+ ...(options == null ? void 0 : options.abortSignal) ? { signal: options.abortSignal } : {}
412
+ });
413
+ return new VercelNetworkSandboxSession({
414
+ sandbox: fork,
415
+ ownsLifecycle: true
416
+ });
417
+ };
418
+ this.resumeSession = async (options) => {
419
+ var _a;
420
+ (_a = options.abortSignal) == null ? void 0 : _a.throwIfAborted();
421
+ if ("sandbox" in this.settings && this.settings.sandbox != null) {
422
+ return new VercelNetworkSandboxSession({
423
+ sandbox: this.settings.sandbox,
424
+ ownsLifecycle: false
425
+ });
426
+ }
427
+ const sandbox = await Sandbox.get({
428
+ name: sessionSandboxName(options.sessionId),
429
+ ...options.abortSignal ? { signal: options.abortSignal } : {}
430
+ });
431
+ return new VercelNetworkSandboxSession({ sandbox, ownsLifecycle: true });
432
+ };
433
+ if ("sandbox" in settings && settings.sandbox != null && settings.bridgePorts != null && settings.bridgePorts.length > 0) {
434
+ this.bridgePorts = [...settings.bridgePorts];
435
+ }
436
+ }
437
+ };
438
+ var SNAPSHOT_CACHE_KEY = /* @__PURE__ */ Symbol.for(
439
+ "ai-sdk.harness.vercel-template-snapshots"
440
+ );
441
+ function getSnapshotCache() {
442
+ const globals = globalThis;
443
+ let cache = globals[SNAPSHOT_CACHE_KEY];
444
+ if (cache == null) {
445
+ cache = /* @__PURE__ */ new Map();
446
+ globals[SNAPSHOT_CACHE_KEY] = cache;
447
+ }
448
+ return cache;
449
+ }
450
+ async function pollForTemplateSnapshot(name, abortSignal) {
451
+ const deadline = Date.now() + SNAPSHOT_POLL_TIMEOUT_MS;
452
+ while (Date.now() < deadline) {
453
+ abortSignal == null ? void 0 : abortSignal.throwIfAborted();
454
+ const refreshed = await Sandbox.get({
455
+ name,
456
+ resume: false,
457
+ ...abortSignal ? { signal: abortSignal } : {}
458
+ });
459
+ if (refreshed.currentSnapshotId) {
460
+ return refreshed.currentSnapshotId;
461
+ }
462
+ await new Promise(
463
+ (resolve) => setTimeout(resolve, SNAPSHOT_POLL_INTERVAL_MS)
464
+ );
465
+ }
466
+ throw new Error(
467
+ `Timed out waiting for snapshot of template "${name}" to publish.`
468
+ );
469
+ }
470
+ export {
471
+ VercelSandboxProvider,
472
+ createVercelSandbox
473
+ };
474
+ //# sourceMappingURL=index.js.map