withruntime 0.8.4 → 0.10.0
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/BLAXEL.md +4 -4
- package/COMPATIBILITY.md +145 -0
- package/DAYTONA.md +13 -7
- package/E2B.md +34 -20
- package/README.md +22 -11
- package/README.src.md +22 -11
- package/VERCEL.md +13 -3
- package/compatibility-lock.json +357 -0
- package/dist/ai-harness/index.js +2 -2
- package/dist/api-defaults.d.ts +17 -0
- package/dist/api-defaults.js +22 -0
- package/dist/blaxel/errors.js +1 -1
- package/dist/blaxel/lookup.d.ts +3 -2
- package/dist/blaxel/lookup.js +3 -2
- package/dist/blaxel/pagination.d.ts +2 -2
- package/dist/blaxel/pagination.js +45 -17
- package/dist/blaxel/preview.d.ts +3 -2
- package/dist/blaxel/preview.js +16 -14
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +66 -11
- package/dist/blaxel/sandbox.d.ts +2 -0
- package/dist/blaxel/sandbox.js +2 -0
- package/dist/blaxel/snapshot.js +3 -5
- package/dist/cli-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +351 -85
- package/dist/client.d.ts +33 -4
- package/dist/client.js +47 -12
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +303 -0
- package/dist/cloudflare/index.js +592 -0
- package/dist/cloudflare/worker.d.ts +419 -0
- package/dist/cloudflare/worker.js +447 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +490 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1347 -0
- package/dist/compat/core.d.ts +40 -0
- package/dist/compat/core.js +238 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +133 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/context.js +2 -1
- package/dist/daytona/daytona.js +11 -4
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +121 -38
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- package/dist/daytona/sandbox.d.ts +1 -1
- package/dist/daytona/sandbox.js +16 -7
- package/dist/e2b/client.js +1 -1
- package/dist/e2b/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +35 -12
- package/dist/e2b/commands.js +169 -73
- package/dist/e2b/errors.js +1 -1
- package/dist/e2b/filesystem.d.ts +8 -6
- package/dist/e2b/filesystem.js +127 -28
- package/dist/e2b/index.d.ts +1 -0
- package/dist/e2b/index.js +1 -0
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +80 -0
- package/dist/e2b/sandbox.d.ts +6 -4
- package/dist/e2b/sandbox.js +59 -25
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +309 -0
- package/dist/freestyle/index.js +343 -0
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +486 -0
- package/dist/network-cli.js +112 -44
- package/dist/openai-agents/index.js +18 -71
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/interpreter.d.ts +11 -4
- package/dist/products/interpreter.js +33 -9
- package/dist/products/jobs.js +3 -2
- package/dist/products/limits.d.ts +10 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +18 -4
- package/dist/products/previews.js +14 -5
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +132 -18
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +326 -0
- package/dist/runloop/index.js +535 -0
- package/dist/runloop/pagination.d.ts +46 -0
- package/dist/runloop/pagination.js +82 -0
- package/dist/runloop/snapshots.d.ts +52 -0
- package/dist/runloop/snapshots.js +244 -0
- package/dist/sandbox.d.ts +76 -16
- package/dist/sandbox.js +696 -135
- package/dist/snapshots.d.ts +20 -5
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +588 -0
- package/dist/tar.d.ts +10 -1
- package/dist/tar.js +311 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +23 -8
- package/dist/transport.js +247 -127
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +15 -3
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +19 -2
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.d.ts +8 -8
- package/dist/vercel/sandbox.js +159 -64
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +46 -3
package/dist/vercel/sandbox.js
CHANGED
|
@@ -36,6 +36,10 @@ function leaseSeconds(timeoutMs) {
|
|
|
36
36
|
return Math.max(seconds, MIN_LEASE_SECONDS);
|
|
37
37
|
}
|
|
38
38
|
function refuseCreate(params) {
|
|
39
|
+
if (params.keepLastSnapshots !== undefined)
|
|
40
|
+
throw new NotSupportedError("Snapshot count and eviction policies (keepLastSnapshots)", "Omit it; manage saved snapshots explicitly with Snapshot.list and snapshot.delete.");
|
|
41
|
+
if (params.ports !== undefined)
|
|
42
|
+
validatePorts(params.ports);
|
|
39
43
|
if (params.mounts && Object.keys(params.mounts).length)
|
|
40
44
|
throw new NotSupportedError("Vercel Drives (mounts)", "Use a Runtime volume: withruntime: { create: { volumes: [{ volumeId, path }] } } (runtime.volumes creates one).");
|
|
41
45
|
if (params.networkId !== undefined)
|
|
@@ -45,6 +49,11 @@ function refuseCreate(params) {
|
|
|
45
49
|
if (params.failoverRegions?.length)
|
|
46
50
|
throw new NotSupportedError("Failover regions (failoverRegions)", "Remove it: Runtime runs in one US region.");
|
|
47
51
|
}
|
|
52
|
+
function validatePorts(ports) {
|
|
53
|
+
if (!Array.isArray(ports) ||
|
|
54
|
+
ports.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65_535))
|
|
55
|
+
throw new RangeError("ports must contain only integers from 1 to 65535.");
|
|
56
|
+
}
|
|
48
57
|
/** Vercel's network policy as Runtime's rules. Header injection and
|
|
49
58
|
* forwarding rules have no Runtime counterpart and are refused. */
|
|
50
59
|
export function networkRules(policy) {
|
|
@@ -111,15 +120,15 @@ function statusOf(state) {
|
|
|
111
120
|
/** Runtime's `/workspace` stands for Vercel's `/vercel/sandbox`: relative
|
|
112
121
|
* paths resolve against it, and paths under /vercel/sandbox are rewritten. */
|
|
113
122
|
export function toRuntimePath(path, cwd) {
|
|
114
|
-
const base = cwd === undefined ? HOME : toVercelPath(cwd);
|
|
115
|
-
const absolute = path.startsWith("/") ? path : `${base}/${path.replace(
|
|
123
|
+
const base = cwd === undefined ? HOME : toVercelPath(cwd).replace(/\/+$/, "");
|
|
124
|
+
const absolute = path.startsWith("/") ? path : `${base}/${path.replace(/^(?:\.(?:\/+|$))+/, "")}`;
|
|
116
125
|
const normal = absolute.replace(/\/+$/, "") || "/";
|
|
117
126
|
if (normal === HOME || normal.startsWith(`${HOME}/`))
|
|
118
127
|
return RUNTIME_HOME + normal.slice(HOME.length);
|
|
119
128
|
return normal;
|
|
120
129
|
}
|
|
121
130
|
function toVercelPath(path) {
|
|
122
|
-
return path.startsWith("/") ? path : `${HOME}/${path.replace(
|
|
131
|
+
return path.startsWith("/") ? path : `${HOME}/${path.replace(/^(?:\.(?:\/+|$))+/, "")}`;
|
|
123
132
|
}
|
|
124
133
|
/** A sandbox with Vercel's methods. `sandbox.withruntime` is the Runtime
|
|
125
134
|
* sandbox underneath, for anything Vercel has no name for. */
|
|
@@ -143,9 +152,9 @@ export class Sandbox {
|
|
|
143
152
|
this.fs = new FileSystem({
|
|
144
153
|
name: this.name,
|
|
145
154
|
files: async () => (await this.#live()).files,
|
|
146
|
-
run: async (argv) => {
|
|
155
|
+
run: async (argv, options) => {
|
|
147
156
|
const live = await this.#live();
|
|
148
|
-
return live.exec(argv);
|
|
157
|
+
return live.exec(argv, options);
|
|
149
158
|
},
|
|
150
159
|
resolve: (path, cwd) => toRuntimePath(path, cwd),
|
|
151
160
|
});
|
|
@@ -227,6 +236,8 @@ export class Sandbox {
|
|
|
227
236
|
refuseCreate(params);
|
|
228
237
|
const client = clientFor(params);
|
|
229
238
|
const persistent = params.persistent ?? true;
|
|
239
|
+
if (params.snapshotExpiration !== undefined && persistent)
|
|
240
|
+
retentionDays(params.snapshotExpiration);
|
|
230
241
|
const vcpus = params.resources?.vcpus ?? DEFAULT_VCPUS;
|
|
231
242
|
const source = await guard(() => resolveImage(client, params));
|
|
232
243
|
const input = {
|
|
@@ -300,15 +311,16 @@ export class Sandbox {
|
|
|
300
311
|
/** A sandbox by name (or Runtime id), woken if it is paused unless
|
|
301
312
|
* `resume: false`. */
|
|
302
313
|
static async get(params) {
|
|
314
|
+
params.signal?.throwIfAborted();
|
|
303
315
|
const client = clientFor(params);
|
|
304
|
-
const runtime = await guard(() => find(client, params.name), params.name);
|
|
316
|
+
const runtime = await guard(() => find(client, params.name, params), params.name);
|
|
305
317
|
const sandbox = new Sandbox({
|
|
306
318
|
runtime,
|
|
307
319
|
client,
|
|
308
320
|
params: params.onResume ? { onResume: params.onResume } : {},
|
|
309
321
|
});
|
|
310
322
|
if (params.resume !== false)
|
|
311
|
-
await sandbox.#live();
|
|
323
|
+
await sandbox.#live(params);
|
|
312
324
|
return sandbox;
|
|
313
325
|
}
|
|
314
326
|
/** The named sandbox, or a new one with that name when there is none. */
|
|
@@ -329,9 +341,22 @@ export class Sandbox {
|
|
|
329
341
|
/** A new sandbox copied from a running one, memory and all (a Runtime
|
|
330
342
|
* fork). `sourceSandbox` is its name. */
|
|
331
343
|
static async fork(params) {
|
|
332
|
-
const unsupported = [
|
|
344
|
+
const unsupported = [
|
|
345
|
+
"resources",
|
|
346
|
+
"ports",
|
|
347
|
+
"timeout",
|
|
348
|
+
"networkPolicy",
|
|
349
|
+
"image",
|
|
350
|
+
"tags",
|
|
351
|
+
"env",
|
|
352
|
+
"persistent",
|
|
353
|
+
"snapshotExpiration",
|
|
354
|
+
"keepLastSnapshots",
|
|
355
|
+
"onResume",
|
|
356
|
+
"runtime",
|
|
357
|
+
].find((field) => params[field] !== undefined);
|
|
333
358
|
if (unsupported)
|
|
334
|
-
throw new NotSupportedError(`Overriding ${unsupported} on a fork`, "
|
|
359
|
+
throw new NotSupportedError(`Overriding ${unsupported} on a fork`, "Omit the override to use Runtime's existing fork behavior.");
|
|
335
360
|
refuseCreate(params);
|
|
336
361
|
const source = await Sandbox.get({ ...params, name: params.sourceSandbox });
|
|
337
362
|
const copy = await guard(() => source.#rt.fork({ ...(params.name ? { name: params.name } : {}), ...signalOf(params) }), params.sourceSandbox);
|
|
@@ -344,6 +369,7 @@ export class Sandbox {
|
|
|
344
369
|
/** Running and stopped (paused) sandboxes, oldest first, with Vercel's
|
|
345
370
|
* paginator: `for await (const s of await Sandbox.list())`. */
|
|
346
371
|
static async list(params = {}) {
|
|
372
|
+
params.signal?.throwIfAborted();
|
|
347
373
|
for (const field of ["since", "until", "cursor", "sortBy", "sortOrder", "namePrefix"])
|
|
348
374
|
if (params[field] !== undefined)
|
|
349
375
|
throw new NotSupportedError(`Listing sandboxes by ${field}`, "List them all (oldest first) and filter the result yourself.");
|
|
@@ -351,13 +377,14 @@ export class Sandbox {
|
|
|
351
377
|
const page = await guard(() => client.sandboxes.list({
|
|
352
378
|
...(params.tags ? { labels: params.tags } : {}),
|
|
353
379
|
...(params.limit ? { limit: Math.min(params.limit, 100) } : {}),
|
|
354
|
-
}));
|
|
355
|
-
return paginate(page);
|
|
380
|
+
}, signalOf(params)));
|
|
381
|
+
return paginate(page, params.signal);
|
|
356
382
|
}
|
|
357
383
|
// ---- commands ---------------------------------------------------------
|
|
358
384
|
/** Wakes the sandbox if it was paused (a stopped persistent sandbox, in
|
|
359
385
|
* Vercel's words), as Vercel resumes one on the next call. */
|
|
360
|
-
async #live() {
|
|
386
|
+
async #live(opts = {}) {
|
|
387
|
+
opts.signal?.throwIfAborted();
|
|
361
388
|
if (this.#deleted)
|
|
362
389
|
throw new APIError(new Response(null, { status: 410 }), {
|
|
363
390
|
message: `Sandbox ${this.name} was deleted.`,
|
|
@@ -365,35 +392,43 @@ export class Sandbox {
|
|
|
365
392
|
});
|
|
366
393
|
const state = this.#rt.state;
|
|
367
394
|
if (state === "paused" || state === "pausing")
|
|
368
|
-
await this.#wake();
|
|
395
|
+
await this.#wake(opts);
|
|
369
396
|
return this.#rt;
|
|
370
397
|
}
|
|
371
|
-
async #wake() {
|
|
372
|
-
await guard(() => this.#rt.wake(
|
|
398
|
+
async #wake(opts = {}) {
|
|
399
|
+
await guard(() => this.#rt.wake(signalOf(opts)), this.name);
|
|
373
400
|
await this.#onResume?.(this);
|
|
374
401
|
}
|
|
375
402
|
/** Runs `work` on the live sandbox; if the lease paused it meanwhile, wakes
|
|
376
403
|
* it and runs `work` once more. */
|
|
377
|
-
async #withResume(work) {
|
|
378
|
-
|
|
404
|
+
async #withResume(work, opts = {}) {
|
|
405
|
+
opts.signal?.throwIfAborted();
|
|
406
|
+
const runtime = await this.#live(opts);
|
|
379
407
|
try {
|
|
380
408
|
return await work(runtime);
|
|
381
409
|
}
|
|
382
410
|
catch (error) {
|
|
383
411
|
if (!(error instanceof RuntimeError && error.code === "sandbox_paused"))
|
|
384
412
|
throw translate(error, this.name);
|
|
385
|
-
await guard(() => runtime.refresh(), this.name);
|
|
386
|
-
await this.#wake();
|
|
413
|
+
await guard(() => runtime.refresh(signalOf(opts)), this.name);
|
|
414
|
+
await this.#wake(opts);
|
|
387
415
|
return guard(() => work(this.#rt), this.name);
|
|
388
416
|
}
|
|
389
417
|
}
|
|
390
|
-
async #linkHome(text) {
|
|
418
|
+
async #linkHome(text, opts = {}) {
|
|
419
|
+
opts.signal?.throwIfAborted();
|
|
391
420
|
if (this.#linked || !text.includes(HOME))
|
|
392
421
|
return this.#linked;
|
|
393
422
|
this.#linked = (async () => {
|
|
394
|
-
await this.#rt.exec(HOME_LINK).catch(() =>
|
|
423
|
+
await this.#rt.exec(HOME_LINK, signalOf(opts)).catch(() => opts.signal?.throwIfAborted());
|
|
395
424
|
})();
|
|
396
|
-
|
|
425
|
+
try {
|
|
426
|
+
await this.#linked;
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
this.#linked = undefined;
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
397
432
|
}
|
|
398
433
|
async runCommand(commandOrParams, args, opts = {}) {
|
|
399
434
|
const params = typeof commandOrParams === "string"
|
|
@@ -406,18 +441,19 @@ export class Sandbox {
|
|
|
406
441
|
];
|
|
407
442
|
const cwd = params.cwd === undefined ? RUNTIME_HOME : toRuntimePath(params.cwd);
|
|
408
443
|
const env = { ...this.#env, ...params.env };
|
|
409
|
-
await this.#live();
|
|
410
|
-
await this.#linkHome(`${argv.join(" ")}\n${params.cwd ?? ""}\n${Object.values(env).join("\n")}
|
|
444
|
+
await this.#live(params);
|
|
445
|
+
await this.#linkHome(`${argv.join(" ")}\n${params.cwd ?? ""}\n${Object.values(env).join("\n")}`, params);
|
|
411
446
|
const timeoutMs = params.timeoutMs ?? LONGEST_MS;
|
|
412
447
|
const common = {
|
|
413
448
|
cwd,
|
|
414
449
|
...(Object.keys(env).length ? { env } : {}),
|
|
415
450
|
timeoutMs,
|
|
451
|
+
...signalOf(params),
|
|
416
452
|
};
|
|
417
453
|
const startedAt = Date.now();
|
|
418
454
|
const vercelCwd = params.cwd === undefined ? HOME : toVercelPath(params.cwd);
|
|
419
455
|
if (params.detached) {
|
|
420
|
-
const process = await this.#withResume((runtime) => runtime.spawn(argv, common));
|
|
456
|
+
const process = await this.#withResume((runtime) => runtime.spawn(argv, common), params);
|
|
421
457
|
const command = new Command({
|
|
422
458
|
id: process.id,
|
|
423
459
|
cwd: vercelCwd,
|
|
@@ -438,8 +474,7 @@ export class Sandbox {
|
|
|
438
474
|
...common,
|
|
439
475
|
onStdout: record("stdout", params.stdout),
|
|
440
476
|
onStderr: record("stderr", params.stderr),
|
|
441
|
-
|
|
442
|
-
}));
|
|
477
|
+
}), params);
|
|
443
478
|
return new CommandFinished({
|
|
444
479
|
id: result.processId ?? `cmd_${startedAt}`,
|
|
445
480
|
cwd: vercelCwd,
|
|
@@ -449,8 +484,8 @@ export class Sandbox {
|
|
|
449
484
|
}, exitCodeOf(result.exitCode, result.timedOut), result.durationMs ?? Date.now() - startedAt);
|
|
450
485
|
}
|
|
451
486
|
/** A command started earlier, by its cmdId. */
|
|
452
|
-
async getCommand(cmdId,
|
|
453
|
-
const process = await this.#withResume((runtime) => runtime.processes.get(cmdId));
|
|
487
|
+
async getCommand(cmdId, opts = {}) {
|
|
488
|
+
const process = await this.#withResume((runtime) => runtime.processes.get(cmdId, signalOf(opts)), opts);
|
|
454
489
|
return new Command({
|
|
455
490
|
id: process.id,
|
|
456
491
|
cwd: process.info.cwd,
|
|
@@ -460,9 +495,9 @@ export class Sandbox {
|
|
|
460
495
|
});
|
|
461
496
|
}
|
|
462
497
|
// ---- files ------------------------------------------------------------
|
|
463
|
-
async mkDir(path,
|
|
498
|
+
async mkDir(path, opts = {}) {
|
|
464
499
|
const target = toRuntimePath(path);
|
|
465
|
-
await this.#withResume((runtime) => runtime.files.mkdir(target, { parents: true }));
|
|
500
|
+
await this.#withResume((runtime) => runtime.files.mkdir(target, { parents: true, ...signalOf(opts) }), opts);
|
|
466
501
|
}
|
|
467
502
|
/** The file's bytes as a Node stream, or null when there is no file. */
|
|
468
503
|
async readFile(file, opts = {}) {
|
|
@@ -476,7 +511,7 @@ export class Sandbox {
|
|
|
476
511
|
async readFileToBuffer(file, opts = {}) {
|
|
477
512
|
const target = toRuntimePath(file.path, file.cwd);
|
|
478
513
|
try {
|
|
479
|
-
return Buffer.from(await this.#withResume((runtime) => runtime.files.read(target, signalOf(opts))));
|
|
514
|
+
return Buffer.from(await this.#withResume((runtime) => runtime.files.read(target, signalOf(opts)), opts));
|
|
480
515
|
}
|
|
481
516
|
catch (error) {
|
|
482
517
|
if (error instanceof APIError && error.response.status === 404)
|
|
@@ -489,39 +524,42 @@ export class Sandbox {
|
|
|
489
524
|
const buffer = await this.readFileToBuffer(src, opts);
|
|
490
525
|
if (buffer === null)
|
|
491
526
|
return null;
|
|
527
|
+
opts.signal?.throwIfAborted();
|
|
492
528
|
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
493
529
|
const paths = await import("node:path");
|
|
494
530
|
const target = paths.resolve(dst.cwd ?? process.cwd(), dst.path);
|
|
531
|
+
opts.signal?.throwIfAborted();
|
|
495
532
|
if (opts.mkdirRecursive)
|
|
496
533
|
await mkdir(paths.dirname(target), { recursive: true });
|
|
497
|
-
await writeFile(target, buffer);
|
|
534
|
+
await writeFile(target, buffer, signalOf(opts));
|
|
498
535
|
return target;
|
|
499
536
|
}
|
|
500
537
|
/** Writes files, making their directories. Relative paths land in the
|
|
501
538
|
* working directory; `mode` sets permissions. */
|
|
502
|
-
async writeFiles(files,
|
|
539
|
+
async writeFiles(files, opts = {}) {
|
|
503
540
|
const modes = [];
|
|
504
541
|
await this.#withResume(async (runtime) => {
|
|
505
542
|
for (const file of files) {
|
|
506
543
|
const target = toRuntimePath(file.path);
|
|
507
|
-
await runtime.files.write(target, file.content);
|
|
544
|
+
await runtime.files.write(target, file.content, signalOf(opts));
|
|
508
545
|
if (file.mode !== undefined)
|
|
509
546
|
modes.push(file.mode.toString(8), target);
|
|
510
547
|
}
|
|
511
548
|
if (!modes.length)
|
|
512
549
|
return;
|
|
513
550
|
const script = 'while [ "$#" -gt 0 ]; do chmod "$1" "$2" || exit 1; shift 2; done';
|
|
514
|
-
const result = await runtime.exec(["sh", "-c", script, "sh", ...modes]);
|
|
551
|
+
const result = await runtime.exec(["sh", "-c", script, "sh", ...modes], signalOf(opts));
|
|
515
552
|
if (result.exitCode !== 0)
|
|
516
553
|
throw new APIError(new Response(null, { status: 400 }), {
|
|
517
554
|
message: `Setting file modes failed: ${result.stderr.trim()}`,
|
|
518
555
|
sandboxName: this.name,
|
|
519
556
|
});
|
|
520
|
-
});
|
|
557
|
+
}, opts);
|
|
521
558
|
}
|
|
522
559
|
// ---- ports ------------------------------------------------------------
|
|
523
|
-
async #share(port) {
|
|
524
|
-
|
|
560
|
+
async #share(port, opts = {}) {
|
|
561
|
+
opts.signal?.throwIfAborted();
|
|
562
|
+
const preview = await guard(() => this.#rt.previews.create(port, { visibility: "public" }, signalOf(opts)), this.name);
|
|
525
563
|
this.#routes.set(port, preview.url.replace(/\/$/, ""));
|
|
526
564
|
}
|
|
527
565
|
/** The public address of a port listed in `ports`, such as
|
|
@@ -566,53 +604,99 @@ export class Sandbox {
|
|
|
566
604
|
async extendTimeout(duration, opts = {}) {
|
|
567
605
|
if (!Number.isFinite(duration) || duration <= 0)
|
|
568
606
|
throw new RangeError(`duration must be a positive number of milliseconds, not ${duration}.`);
|
|
569
|
-
await this.#withResume((runtime) => runtime.extend(Math.ceil(duration / 1000), signalOf(opts)));
|
|
607
|
+
await this.#withResume((runtime) => runtime.extend(Math.ceil(duration / 1000), signalOf(opts)), opts);
|
|
570
608
|
}
|
|
571
609
|
/** Replaces the network rules; returns the policy given. */
|
|
572
|
-
async updateNetworkPolicy(networkPolicy,
|
|
610
|
+
async updateNetworkPolicy(networkPolicy, opts = {}) {
|
|
611
|
+
opts.signal?.throwIfAborted();
|
|
573
612
|
const rules = networkRules(networkPolicy);
|
|
574
|
-
await this.#withResume((runtime) => runtime.network.set(rules));
|
|
613
|
+
await this.#withResume((runtime) => runtime.network.set(rules, signalOf(opts)), opts);
|
|
575
614
|
return networkPolicy;
|
|
576
615
|
}
|
|
577
616
|
/** Changes what Runtime can change on a sandbox: `timeout`, `ports`,
|
|
578
617
|
* `networkPolicy` and `snapshotExpiration`. Anything else is refused before
|
|
579
618
|
* anything changes. */
|
|
580
|
-
async update(params,
|
|
619
|
+
async update(params, opts = {}) {
|
|
620
|
+
opts.signal?.throwIfAborted();
|
|
581
621
|
const allowed = new Set(["timeout", "ports", "networkPolicy", "snapshotExpiration"]);
|
|
582
622
|
const other = Object.keys(params).find((key) => params[key] !== undefined && !allowed.has(key));
|
|
583
623
|
if (other)
|
|
584
624
|
throw new NotSupportedError(`Changing ${other} on an existing sandbox`, "Create a new sandbox with it (Runtime cannot change a sandbox's machine, persistence, tags or region after creation).");
|
|
625
|
+
const rules = params.networkPolicy === undefined ? undefined : networkRules(params.networkPolicy);
|
|
626
|
+
const retention = params.snapshotExpiration === undefined
|
|
627
|
+
? undefined
|
|
628
|
+
: retentionDays(params.snapshotExpiration);
|
|
629
|
+
if (params.ports !== undefined)
|
|
630
|
+
validatePorts(params.ports);
|
|
631
|
+
const wanted = params.ports === undefined ? undefined : new Set(params.ports);
|
|
585
632
|
if (params.timeout !== undefined) {
|
|
586
633
|
const current = this.timeout;
|
|
587
634
|
if (params.timeout < current)
|
|
588
635
|
throw new NotSupportedError("Shortening a sandbox's timeout", "Runtime leases only move later. Call stop() when the work is done.");
|
|
589
636
|
if (params.timeout > current)
|
|
590
|
-
await this.extendTimeout(params.timeout - current);
|
|
637
|
+
await this.extendTimeout(params.timeout - current, opts);
|
|
591
638
|
}
|
|
592
|
-
if (
|
|
593
|
-
await this.
|
|
594
|
-
if (
|
|
595
|
-
await guard(() => this.#rt.setRetention(
|
|
596
|
-
if (
|
|
597
|
-
const wanted = new Set(params.ports);
|
|
639
|
+
if (rules !== undefined)
|
|
640
|
+
await this.#withResume((runtime) => runtime.network.set(rules, signalOf(opts)), opts);
|
|
641
|
+
if (retention !== undefined)
|
|
642
|
+
await guard(() => this.#rt.setRetention(retention, signalOf(opts)), this.name);
|
|
643
|
+
if (wanted !== undefined) {
|
|
598
644
|
for (const port of [...this.#routes.keys()])
|
|
599
645
|
if (!wanted.has(port)) {
|
|
600
|
-
await guard(() => this.#rt.previews.delete(port), this.name);
|
|
646
|
+
await guard(() => this.#rt.previews.delete(port, signalOf(opts)), this.name);
|
|
601
647
|
this.#routes.delete(port);
|
|
602
648
|
}
|
|
603
649
|
for (const port of wanted)
|
|
604
650
|
if (!this.#routes.has(port))
|
|
605
|
-
await this.#share(port);
|
|
651
|
+
await this.#share(port, opts);
|
|
606
652
|
}
|
|
607
653
|
}
|
|
608
|
-
/** Keeps the sandbox's
|
|
609
|
-
*
|
|
654
|
+
/** Keeps the sandbox's filesystem as a disk-only snapshot, then stops
|
|
655
|
+
* the source. Restoring it starts fresh processes, as Vercel does. Start
|
|
610
656
|
* from it with `Sandbox.create({ source: { type: "snapshot", snapshotId } })`. */
|
|
611
657
|
async snapshot(opts = {}) {
|
|
658
|
+
opts.signal?.throwIfAborted();
|
|
659
|
+
const retention = opts.expiration === undefined ? undefined : retentionDays(opts.expiration);
|
|
612
660
|
const taken = await this.#withResume((runtime) => runtime.snapshot({
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
661
|
+
mode: "disk",
|
|
662
|
+
...(retention !== undefined ? { retentionDays: retention } : {}),
|
|
663
|
+
...signalOf(opts),
|
|
664
|
+
}), opts);
|
|
665
|
+
try {
|
|
666
|
+
await guard(() => this.#rt.stop(signalOf(opts)), this.name);
|
|
667
|
+
if (this.#rt.state !== "stopped")
|
|
668
|
+
await guard(() => this.#rt.waitFor("stopped", signalOf(opts)), this.name);
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
const recovery = { snapshotId: taken.id, sourceSandboxId: this.#rt.id };
|
|
672
|
+
if (error instanceof APIError) {
|
|
673
|
+
const json = error.json;
|
|
674
|
+
error.json = {
|
|
675
|
+
...json,
|
|
676
|
+
error: {
|
|
677
|
+
...json?.error,
|
|
678
|
+
details: { ...(json?.error?.details ?? {}), ...recovery },
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
else if (error instanceof Error && Object.isExtensible(error)) {
|
|
683
|
+
Object.assign(error, recovery);
|
|
684
|
+
}
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
if (this.#rt.state !== "stopped") {
|
|
688
|
+
const code = "snapshot_source_stop_timeout";
|
|
689
|
+
const message = `Snapshot ${taken.id} was saved, but sandbox ${this.name} did not finish stopping.`;
|
|
690
|
+
const error = new APIError(new Response(null, { status: 409 }), {
|
|
691
|
+
message,
|
|
692
|
+
sandboxName: this.name,
|
|
693
|
+
json: {
|
|
694
|
+
error: { code, message, details: { snapshotId: taken.id, sourceSandboxId: this.#rt.id } },
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
error.code = code;
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
616
700
|
return new Snapshot(taken, this.#client);
|
|
617
701
|
}
|
|
618
702
|
// ---- what Runtime does differently -----------------------------------
|
|
@@ -656,6 +740,8 @@ function usersRefused() {
|
|
|
656
740
|
/** Milliseconds as whole days for Runtime's retention: 1 to 365, and 0 (no
|
|
657
741
|
* expiration) as 365. */
|
|
658
742
|
export function retentionDays(ms) {
|
|
743
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
744
|
+
throw new RangeError(`Snapshot expiration must be a nonnegative finite number of milliseconds, not ${ms}.`);
|
|
659
745
|
if (ms === 0)
|
|
660
746
|
return 365;
|
|
661
747
|
return Math.min(365, Math.max(1, Math.ceil(ms / 86_400_000)));
|
|
@@ -671,11 +757,13 @@ async function pipe(command, params) {
|
|
|
671
757
|
}
|
|
672
758
|
/** The newest live sandbox with this name, or the sandbox with this Runtime
|
|
673
759
|
* id. A 404 when there is none. */
|
|
674
|
-
async function find(client, name) {
|
|
760
|
+
async function find(client, name, opts = {}) {
|
|
761
|
+
opts.signal?.throwIfAborted();
|
|
675
762
|
if (UUID.test(name))
|
|
676
|
-
return client.sandboxes.get(name);
|
|
677
|
-
const page = await client.sandboxes.list({ name });
|
|
763
|
+
return client.sandboxes.get(name, signalOf(opts));
|
|
764
|
+
const page = await client.sandboxes.list({ name }, signalOf(opts));
|
|
678
765
|
const all = await page.toArray(1000);
|
|
766
|
+
opts.signal?.throwIfAborted();
|
|
679
767
|
const found = all.filter((one) => one.state !== "stopped").at(-1);
|
|
680
768
|
if (!found)
|
|
681
769
|
throw new RuntimeError({
|
|
@@ -705,22 +793,29 @@ function itemOf(runtime) {
|
|
|
705
793
|
}
|
|
706
794
|
/** Vercel's paginator over a Runtime page: the first page's fields, async
|
|
707
795
|
* iteration over every item, `pages()` and `toArray()`. */
|
|
708
|
-
function paginate(first) {
|
|
796
|
+
function paginate(first, signal) {
|
|
709
797
|
const toPage = (page) => ({
|
|
710
798
|
sandboxes: page.data.map(itemOf),
|
|
711
799
|
pagination: { count: page.data.length, next: page.nextCursor },
|
|
712
800
|
});
|
|
713
801
|
return Object.assign(toPage(first), {
|
|
714
802
|
async *pages() {
|
|
715
|
-
for await (const page of first.pages())
|
|
803
|
+
for await (const page of first.pages()) {
|
|
804
|
+
signal?.throwIfAborted();
|
|
716
805
|
yield toPage(page);
|
|
806
|
+
}
|
|
717
807
|
},
|
|
718
808
|
async *[Symbol.asyncIterator]() {
|
|
719
|
-
for await (const item of first)
|
|
809
|
+
for await (const item of first) {
|
|
810
|
+
signal?.throwIfAborted();
|
|
720
811
|
yield itemOf(item);
|
|
812
|
+
}
|
|
721
813
|
},
|
|
722
814
|
async toArray() {
|
|
723
|
-
|
|
815
|
+
signal?.throwIfAborted();
|
|
816
|
+
const items = await first.toArray();
|
|
817
|
+
signal?.throwIfAborted();
|
|
818
|
+
return items.map(itemOf);
|
|
724
819
|
},
|
|
725
820
|
});
|
|
726
821
|
}
|
|
@@ -2,8 +2,8 @@ import type { Runtime } from "../client.js";
|
|
|
2
2
|
import type { Snapshot as RuntimeSnapshot } from "../snapshots.js";
|
|
3
3
|
import { type Credentials, type WithRuntime } from "./client.js";
|
|
4
4
|
type Status = "created" | "failed" | "deleted";
|
|
5
|
-
/** A saved
|
|
6
|
-
*
|
|
5
|
+
/** A saved filesystem, as Vercel's Snapshot, over a Runtime disk snapshot.
|
|
6
|
+
* Restoring it starts fresh processes. `sourceSessionId` is the Runtime id of the
|
|
7
7
|
* sandbox it came from. */
|
|
8
8
|
export declare class Snapshot {
|
|
9
9
|
#private;
|
|
@@ -26,6 +26,7 @@ export declare class Snapshot {
|
|
|
26
26
|
static list(params?: {
|
|
27
27
|
name?: string;
|
|
28
28
|
limit?: number;
|
|
29
|
+
signal?: AbortSignal;
|
|
29
30
|
} & Credentials & {
|
|
30
31
|
withruntime?: WithRuntime;
|
|
31
32
|
} & Record<string, unknown>): Promise<{
|
package/dist/vercel/snapshot.js
CHANGED
|
@@ -20,8 +20,8 @@ function metadataOf(snapshot) {
|
|
|
20
20
|
expiresAt: Date.parse(snapshot.expiresAt),
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
/** A saved
|
|
24
|
-
*
|
|
23
|
+
/** A saved filesystem, as Vercel's Snapshot, over a Runtime disk snapshot.
|
|
24
|
+
* Restoring it starts fresh processes. `sourceSessionId` is the Runtime id of the
|
|
25
25
|
* sandbox it came from. */
|
|
26
26
|
export class Snapshot {
|
|
27
27
|
#snapshot;
|
|
@@ -60,13 +60,14 @@ export class Snapshot {
|
|
|
60
60
|
}
|
|
61
61
|
/** Snapshots, oldest first; `name` narrows to one sandbox's. */
|
|
62
62
|
static async list(params = {}) {
|
|
63
|
+
params.signal?.throwIfAborted();
|
|
63
64
|
for (const field of ["since", "until", "cursor", "sortOrder"])
|
|
64
65
|
if (params[field] !== undefined)
|
|
65
66
|
throw new NotSupportedError(`Listing snapshots by ${field}`, "List them all and filter the result yourself.");
|
|
66
67
|
const client = clientFor(params);
|
|
67
68
|
let sandboxId;
|
|
68
69
|
if (params.name !== undefined) {
|
|
69
|
-
const found = (await guard(() => client.sandboxes.list({ name: params.name, includeStopped: true }))).data[0];
|
|
70
|
+
const found = (await guard(() => client.sandboxes.list({ name: params.name, includeStopped: true }, signalOf(params)))).data[0];
|
|
70
71
|
if (!found)
|
|
71
72
|
return paginate([], null);
|
|
72
73
|
sandboxId = found.id;
|
|
@@ -74,7 +75,7 @@ export class Snapshot {
|
|
|
74
75
|
const page = await guard(() => client.snapshots.list({
|
|
75
76
|
...(sandboxId ? { sandboxId } : {}),
|
|
76
77
|
...(params.limit ? { limit: params.limit } : {}),
|
|
77
|
-
}));
|
|
78
|
+
}, signalOf(params)));
|
|
78
79
|
return paginate(page.data, page.nextCursor, page);
|
|
79
80
|
}
|
|
80
81
|
static tree() {
|
|
@@ -83,10 +84,13 @@ export class Snapshot {
|
|
|
83
84
|
async delete(opts = {}) {
|
|
84
85
|
try {
|
|
85
86
|
await guard(() => this.#client.snapshots.delete(this.#snapshot.id, signalOf(opts)));
|
|
87
|
+
Object.assign(this.#snapshot, await guard(() => this.#client.snapshots.get(this.#snapshot.id, signalOf(opts))));
|
|
86
88
|
}
|
|
87
89
|
catch (error) {
|
|
88
|
-
if (error instanceof APIError && error.response.status === 404)
|
|
90
|
+
if (error instanceof APIError && error.response.status === 404) {
|
|
91
|
+
this.#snapshot.state = "deleted";
|
|
89
92
|
return;
|
|
93
|
+
}
|
|
90
94
|
throw error;
|
|
91
95
|
}
|
|
92
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "withruntime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"mcpName": "com.withruntime/runtime",
|
|
6
6
|
"type": "module",
|
|
@@ -59,6 +59,41 @@
|
|
|
59
59
|
"types": "./dist/ai-harness/index.d.ts",
|
|
60
60
|
"import": "./dist/ai-harness/index.js",
|
|
61
61
|
"default": "./dist/ai-harness/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./runloop": {
|
|
64
|
+
"types": "./dist/runloop/index.d.ts",
|
|
65
|
+
"import": "./dist/runloop/index.js",
|
|
66
|
+
"default": "./dist/runloop/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./codesandbox": {
|
|
69
|
+
"types": "./dist/codesandbox/index.d.ts",
|
|
70
|
+
"import": "./dist/codesandbox/index.js",
|
|
71
|
+
"default": "./dist/codesandbox/index.js"
|
|
72
|
+
},
|
|
73
|
+
"./sprites": {
|
|
74
|
+
"types": "./dist/sprites/index.d.ts",
|
|
75
|
+
"import": "./dist/sprites/index.js",
|
|
76
|
+
"default": "./dist/sprites/index.js"
|
|
77
|
+
},
|
|
78
|
+
"./freestyle": {
|
|
79
|
+
"types": "./dist/freestyle/index.d.ts",
|
|
80
|
+
"import": "./dist/freestyle/index.js",
|
|
81
|
+
"default": "./dist/freestyle/index.js"
|
|
82
|
+
},
|
|
83
|
+
"./modal": {
|
|
84
|
+
"types": "./dist/modal/index.d.ts",
|
|
85
|
+
"import": "./dist/modal/index.js",
|
|
86
|
+
"default": "./dist/modal/index.js"
|
|
87
|
+
},
|
|
88
|
+
"./cloudflare": {
|
|
89
|
+
"workerd": {
|
|
90
|
+
"types": "./dist/cloudflare/worker.d.ts",
|
|
91
|
+
"import": "./dist/cloudflare/worker.js",
|
|
92
|
+
"default": "./dist/cloudflare/worker.js"
|
|
93
|
+
},
|
|
94
|
+
"types": "./dist/cloudflare/index.d.ts",
|
|
95
|
+
"import": "./dist/cloudflare/index.js",
|
|
96
|
+
"default": "./dist/cloudflare/index.js"
|
|
62
97
|
}
|
|
63
98
|
},
|
|
64
99
|
"bin": {
|
|
@@ -72,7 +107,9 @@
|
|
|
72
107
|
"DAYTONA.md",
|
|
73
108
|
"VERCEL.md",
|
|
74
109
|
"BLAXEL.md",
|
|
75
|
-
"LICENSE"
|
|
110
|
+
"LICENSE",
|
|
111
|
+
"COMPATIBILITY.md",
|
|
112
|
+
"compatibility-lock.json"
|
|
76
113
|
],
|
|
77
114
|
"engines": {
|
|
78
115
|
"node": ">=22.12"
|
|
@@ -86,12 +123,17 @@
|
|
|
86
123
|
"test:java": "bun ../../sdks/java/scripts/check.ts",
|
|
87
124
|
"test:ruby": "bun ../../sdks/ruby/scripts/check.ts",
|
|
88
125
|
"test:python": "bun scripts/test-python.ts",
|
|
89
|
-
"test:go": "bun scripts/test-go.ts"
|
|
126
|
+
"test:go": "bun scripts/test-go.ts",
|
|
127
|
+
"compat:upstream": "bun scripts/check-upstream.ts",
|
|
128
|
+
"test:compat": "bun test tests/e2b tests/daytona tests/vercel tests/blaxel tests/compat tests/drop-in-isolation.test.ts tests/drop-in-paths.test.ts tests/process-input.test.ts tests/watch-reentrant.test.ts tests/upstream-check.test.ts tests/process-output.test.ts tests/prepare-compatibility.test.ts",
|
|
129
|
+
"compat:prepare": "bun scripts/prepare-compatibility.ts",
|
|
130
|
+
"compat:oracle": "bun oracle/check.mjs"
|
|
90
131
|
},
|
|
91
132
|
"devDependencies": {
|
|
92
133
|
"@ai-sdk/harness": "1.0.124",
|
|
93
134
|
"@anthropic-ai/claude-agent-sdk": "0.3.277",
|
|
94
135
|
"@anthropic-ai/sdk": "0.128.0",
|
|
136
|
+
"@cloudflare/workers-types": "5.20260929.1",
|
|
95
137
|
"@google/genai": "2.24.0",
|
|
96
138
|
"@mastra/core": "1.71.0",
|
|
97
139
|
"@mistralai/mistralai": "2.7.0",
|
|
@@ -127,6 +169,7 @@
|
|
|
127
169
|
"bugs": "https://github.com/withruntime/runtime/issues",
|
|
128
170
|
"sideEffects": false,
|
|
129
171
|
"browser": {
|
|
172
|
+
"./dist/file-source.js": "./dist/browser-stub.js",
|
|
130
173
|
"./dist/tar.js": "./dist/browser-stub.js",
|
|
131
174
|
"./dist/image-context.js": "./dist/browser-stub.js",
|
|
132
175
|
"./dist/credentials.js": "./dist/browser-stub.js",
|