withruntime 0.11.1 → 0.12.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 +3 -3
- package/COMPATIBILITY.md +74 -9
- package/DAYTONA.md +33 -5
- package/E2B.md +137 -48
- package/README.md +11 -9
- package/README.src.md +11 -9
- package/VERCEL.md +43 -5
- package/compatibility-lock.json +58 -49
- package/dist/billing-cli.d.ts +1 -1
- package/dist/billing-cli.js +77 -1
- package/dist/blaxel/errors.d.ts +14 -0
- package/dist/blaxel/errors.js +33 -4
- package/dist/blaxel/index.d.ts +2 -2
- package/dist/blaxel/index.js +4 -4
- package/dist/blaxel/interpreter.d.ts +2 -5
- package/dist/blaxel/sandbox.d.ts +15 -7
- package/dist/blaxel/sandbox.js +64 -9
- package/dist/blaxel/snapshot.d.ts +4 -0
- package/dist/blaxel/snapshot.js +4 -0
- package/dist/cli/logs.d.ts +21 -0
- package/dist/cli/logs.js +188 -0
- package/dist/cli/logs.test.d.ts +1 -0
- package/dist/cli/logs.test.js +272 -0
- package/dist/cli-extras.d.ts +3 -3
- package/dist/cli-extras.js +17 -12
- package/dist/cli-words.d.ts +10 -1
- package/dist/cli-words.js +18 -0
- package/dist/cli.d.ts +6 -2
- package/dist/cli.js +468 -219
- package/dist/client.d.ts +13 -63
- package/dist/client.js +35 -124
- package/dist/cloudflare/errors.d.ts +143 -0
- package/dist/cloudflare/errors.js +167 -0
- package/dist/cloudflare/index.d.ts +175 -6
- package/dist/cloudflare/index.js +416 -26
- package/dist/cloudflare/interpreter.d.ts +119 -0
- package/dist/cloudflare/interpreter.js +223 -0
- package/dist/cloudflare/streams.d.ts +38 -0
- package/dist/cloudflare/streams.js +145 -0
- package/dist/cloudflare/watch.d.ts +9 -0
- package/dist/cloudflare/watch.js +24 -0
- package/dist/codesandbox/index.d.ts +216 -28
- package/dist/codesandbox/index.js +478 -78
- package/dist/compat/core.d.ts +18 -0
- package/dist/compat/core.js +29 -0
- package/dist/compat/files.d.ts +1 -0
- package/dist/compat/files.js +25 -5
- package/dist/daytona/computer-use.d.ts +13 -4
- package/dist/daytona/computer-use.js +66 -8
- package/dist/daytona/daytona.d.ts +7 -2
- package/dist/daytona/daytona.js +31 -5
- package/dist/daytona/errors.d.ts +11 -0
- package/dist/daytona/errors.js +18 -0
- package/dist/daytona/filesystem.js +2 -1
- package/dist/daytona/image.d.ts +13 -9
- package/dist/daytona/image.js +18 -12
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/sandbox.d.ts +9 -3
- package/dist/daytona/sandbox.js +17 -3
- package/dist/domains/index.d.ts +9 -0
- package/dist/domains/index.js +10 -0
- package/dist/e2b/client.d.ts +15 -2
- package/dist/e2b/client.js +61 -7
- package/dist/e2b/commands.d.ts +4 -1
- package/dist/e2b/commands.js +17 -7
- package/dist/e2b/errors.d.ts +43 -2
- package/dist/e2b/errors.js +78 -6
- package/dist/e2b/filesystem.d.ts +1 -1
- package/dist/e2b/filesystem.js +82 -16
- package/dist/e2b/index.d.ts +3 -3
- package/dist/e2b/index.js +2 -2
- package/dist/e2b/sandbox.d.ts +50 -35
- package/dist/e2b/sandbox.js +228 -185
- package/dist/e2b/unsupported.d.ts +19 -0
- package/dist/e2b/unsupported.js +22 -1
- package/dist/e2b/users.d.ts +9 -0
- package/dist/e2b/users.js +22 -0
- package/dist/errors.d.ts +14 -5
- package/dist/errors.js +28 -14
- package/dist/freestyle/index.d.ts +285 -37
- package/dist/freestyle/index.js +656 -144
- package/dist/identity.d.ts +3 -0
- package/dist/identity.js +27 -0
- package/dist/images/index.d.ts +9 -0
- package/dist/images/index.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/ips/index.d.ts +9 -0
- package/dist/ips/index.js +10 -0
- package/dist/jobs/index.d.ts +9 -0
- package/dist/jobs/index.js +10 -0
- package/dist/keys.js +2 -0
- package/dist/login.js +2 -0
- package/dist/machine.d.ts +189 -0
- package/dist/machine.js +521 -0
- package/dist/mcp.js +1 -1
- package/dist/modal/index.d.ts +339 -61
- package/dist/modal/index.js +781 -121
- package/dist/network-cli.d.ts +3 -1
- package/dist/network-cli.js +78 -36
- package/dist/networks/index.d.ts +11 -0
- package/dist/networks/index.js +10 -0
- package/dist/openai-agents/index.d.ts +1 -1
- package/dist/openai-agents/index.js +1 -1
- package/dist/ports/index.d.ts +9 -0
- package/dist/ports/index.js +10 -0
- package/dist/product-names.d.ts +52 -0
- package/dist/product-names.js +164 -0
- package/dist/product-renames.d.ts +103 -0
- package/dist/product-renames.js +189 -0
- package/dist/products/apps.d.ts +176 -0
- package/dist/products/apps.js +165 -0
- package/dist/products/billing.d.ts +41 -0
- package/dist/products/billing.js +13 -0
- package/dist/products/browser.d.ts +10 -0
- package/dist/products/browsers.d.ts +71 -0
- package/dist/products/browsers.js +102 -0
- package/dist/products/builds.d.ts +126 -0
- package/dist/products/builds.js +221 -0
- package/dist/products/index.d.ts +3 -16
- package/dist/products/index.js +3 -14
- package/dist/products/jobs.d.ts +19 -3
- package/dist/products/jobs.js +28 -12
- package/dist/products/limits.d.ts +4 -10
- package/dist/products/logs.d.ts +245 -0
- package/dist/products/logs.js +274 -0
- package/dist/products/logs.test.d.ts +1 -0
- package/dist/products/logs.test.js +343 -0
- package/dist/products/network-products.d.ts +38 -7
- package/dist/products/network-products.js +31 -18
- package/dist/products/network.d.ts +1 -1
- package/dist/products/observability.d.ts +3 -2
- package/dist/products/private-network.js +1 -1
- package/dist/products/queues.d.ts +149 -0
- package/dist/products/queues.js +213 -0
- package/dist/products/referrals.d.ts +21 -7
- package/dist/products/referrals.js +3 -3
- package/dist/products/secrets.d.ts +8 -4
- package/dist/products/secrets.js +39 -18
- package/dist/products/signed-requests.d.ts +21 -0
- package/dist/products/signed-requests.js +11 -0
- package/dist/products/streams.d.ts +73 -0
- package/dist/products/streams.js +17 -0
- package/dist/products/switching.d.ts +37 -10
- package/dist/products/switching.js +4 -3
- package/dist/products/volumes.d.ts +3 -0
- package/dist/record-cli.d.ts +23 -0
- package/dist/record-cli.js +308 -0
- package/dist/renamed-products.d.ts +38 -0
- package/dist/renamed-products.js +34 -0
- package/dist/runloop/index.d.ts +9 -2
- package/dist/runloop/index.js +29 -8
- package/dist/runloop/sdk.d.ts +131 -0
- package/dist/runloop/sdk.js +167 -0
- package/dist/sandbox.d.ts +10 -94
- package/dist/sandbox.js +22 -371
- package/dist/sandboxes/index.d.ts +11 -0
- package/dist/sandboxes/index.js +12 -0
- package/dist/snapshots/index.d.ts +9 -0
- package/dist/snapshots/index.js +11 -0
- package/dist/snapshots.d.ts +7 -4
- package/dist/sprites/index.d.ts +343 -31
- package/dist/sprites/index.js +866 -84
- package/dist/ssh.d.ts +1 -1
- package/dist/ssh.js +11 -11
- package/dist/terminal.d.ts +24 -0
- package/dist/terminal.js +32 -0
- package/dist/transport.d.ts +18 -2
- package/dist/transport.js +61 -6
- package/dist/tunnel.js +1 -1
- package/dist/types.d.ts +116 -8
- package/dist/vercel/sandbox.d.ts +1 -2
- package/dist/vercel/sandbox.js +6 -5
- package/dist/vercel/snapshot.js +10 -2
- package/dist/volumes/index.d.ts +9 -0
- package/dist/volumes/index.js +10 -0
- package/package.json +48 -2
package/dist/sandbox.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STREAMED_EXEC_TIMEOUT_MS } from "./api-defaults.js";
|
|
2
2
|
import { CommandError, ConnectionError, DELIBERATE, errorFor, NotFoundError, RateLimitError, RuntimeError, ServiceUnavailableError, } from "./errors.js";
|
|
3
|
-
import { Page } from "./page.js";
|
|
4
3
|
import { sandboxWatches, watchDirectory, } from "./products/watch.js";
|
|
5
|
-
import { Transport } from "./transport.js";
|
|
4
|
+
import { Transport, timeoutSignal } from "./transport.js";
|
|
6
5
|
import { sandboxFactories } from "./products/index.js";
|
|
7
6
|
import { Tunnel } from "./tunnel.js";
|
|
8
7
|
import { pause } from "./wait.js";
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { Machine, machinePage, requestOnly } from "./machine.js";
|
|
9
|
+
/** Sandboxes, as a product of the machine engine (machine.ts). */
|
|
10
|
+
export const SANDBOXES = { plural: "sandboxes", noun: "Sandbox" };
|
|
11
11
|
const CHUNK = 1_048_576;
|
|
12
12
|
/** Chunks of a large write in flight at once. Each chunk's reply waits on the
|
|
13
13
|
* API and the guest, and the link idles while every chunk in flight waits:
|
|
@@ -20,7 +20,7 @@ const enc = (id) => encodeURIComponent(id);
|
|
|
20
20
|
function requestScope(options) {
|
|
21
21
|
const deadline = options.timeoutMs === undefined || options.timeoutMs === 0
|
|
22
22
|
? undefined
|
|
23
|
-
:
|
|
23
|
+
: timeoutSignal(options.timeoutMs);
|
|
24
24
|
const signal = deadline && options.signal
|
|
25
25
|
? AbortSignal.any([deadline, options.signal])
|
|
26
26
|
: (deadline ?? options.signal);
|
|
@@ -59,15 +59,6 @@ function requestScope(options) {
|
|
|
59
59
|
};
|
|
60
60
|
return { request, check, wait };
|
|
61
61
|
}
|
|
62
|
-
/** DELETE /v1/sandboxes/{id}, for `sandbox.delete()` and
|
|
63
|
-
* `runtime.sandboxes.delete(id)`. */
|
|
64
|
-
export function deleteSandbox(t, id, options = {}) {
|
|
65
|
-
return t.json({
|
|
66
|
-
method: "DELETE",
|
|
67
|
-
path: `/v1/sandboxes/${enc(id)}`,
|
|
68
|
-
...options,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
62
|
/* Web APIs only, so the sandbox's commands, processes and files work in a
|
|
72
63
|
browser with a session token (Sandbox.fromSession): no Buffer, no
|
|
73
64
|
node:crypto. */
|
|
@@ -97,26 +88,21 @@ function commandBody(command, options) {
|
|
|
97
88
|
};
|
|
98
89
|
}
|
|
99
90
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
100
|
-
export class Sandbox {
|
|
101
|
-
#info;
|
|
102
|
-
#keepAlive;
|
|
91
|
+
export class Sandbox extends Machine {
|
|
103
92
|
#t;
|
|
104
93
|
files;
|
|
105
94
|
processes;
|
|
106
95
|
/** Short-lived tokens that let a browser reach this sandbox directly. */
|
|
107
96
|
sessions;
|
|
108
97
|
constructor(transport, info) {
|
|
98
|
+
super(SANDBOXES, transport, info);
|
|
109
99
|
this.#t = transport;
|
|
110
|
-
this.#info = info;
|
|
111
100
|
this.files = new Files(transport, info.id);
|
|
112
101
|
this.processes = new Processes(transport, info.id);
|
|
113
102
|
this.sessions = new SandboxSessions(transport, info.id);
|
|
114
103
|
for (const [name, make] of sandboxFactories())
|
|
115
104
|
Object.defineProperty(this, name, { value: make(transport, this), enumerable: false });
|
|
116
105
|
}
|
|
117
|
-
get id() {
|
|
118
|
-
return this.#info.id;
|
|
119
|
-
}
|
|
120
106
|
/** A sandbox reached with a session token instead of an API key, for code in
|
|
121
107
|
* a browser: commands and their streams, processes, files and previews of
|
|
122
108
|
* this one sandbox. Makes no call; `info` holds only the id until
|
|
@@ -141,41 +127,6 @@ export class Sandbox {
|
|
|
141
127
|
});
|
|
142
128
|
return new Sandbox(transport, { id: session.sandboxId });
|
|
143
129
|
}
|
|
144
|
-
/** The API's latest answer. `start`, the report of an image's start
|
|
145
|
-
* command, is the create's alone: the API keeps no record of it, so a
|
|
146
|
-
* later read of this sandbox keeps it here. */
|
|
147
|
-
#keep(info) {
|
|
148
|
-
const start = this.#info?.start;
|
|
149
|
-
this.#info = info.start === undefined && start !== undefined ? { ...info, start } : info;
|
|
150
|
-
}
|
|
151
|
-
/** What the API last said about this sandbox. `refresh()` asks again. */
|
|
152
|
-
get info() {
|
|
153
|
-
return this.#info;
|
|
154
|
-
}
|
|
155
|
-
get state() {
|
|
156
|
-
return this.#info.state;
|
|
157
|
-
}
|
|
158
|
-
async refresh(options = {}) {
|
|
159
|
-
this.#keep(await this.#t.json({
|
|
160
|
-
method: "GET",
|
|
161
|
-
path: `/v1/sandboxes/${enc(this.id)}`,
|
|
162
|
-
...options,
|
|
163
|
-
}));
|
|
164
|
-
return this;
|
|
165
|
-
}
|
|
166
|
-
/** Waits (server-side, no polling) until the sandbox reaches `state`. */
|
|
167
|
-
async waitFor(state, options = {}) {
|
|
168
|
-
this.#keep(await this.#t.json({
|
|
169
|
-
method: "GET",
|
|
170
|
-
path: `/v1/sandboxes/${enc(this.id)}`,
|
|
171
|
-
query: {
|
|
172
|
-
waitFor: state,
|
|
173
|
-
timeoutSeconds: options.timeoutSeconds ?? WAIT_FOR_TIMEOUT_SECONDS,
|
|
174
|
-
},
|
|
175
|
-
...options,
|
|
176
|
-
}));
|
|
177
|
-
return this;
|
|
178
|
-
}
|
|
179
130
|
/** Runs a command and returns its exit code and output. A string runs under
|
|
180
131
|
* `bash -c`; an array runs without a shell. With onStdout/onStderr the output
|
|
181
132
|
* streams as it happens. A timeout is a result (timedOut: true, the output so
|
|
@@ -220,6 +171,7 @@ export class Sandbox {
|
|
|
220
171
|
...truncation(dropped),
|
|
221
172
|
...(exit?.durationMs === undefined ? {} : { durationMs: exit.durationMs }),
|
|
222
173
|
...(processId ? { processId } : {}),
|
|
174
|
+
...(exit?.outOfMemory ? { outOfMemory: exit.outOfMemory } : {}),
|
|
223
175
|
};
|
|
224
176
|
}
|
|
225
177
|
if (options.check && (result.exitCode !== 0 || result.timedOut))
|
|
@@ -245,8 +197,8 @@ export class Sandbox {
|
|
|
245
197
|
code: "cancelled",
|
|
246
198
|
status: 0,
|
|
247
199
|
hint: stopped
|
|
248
|
-
? `See what it printed: runtime
|
|
249
|
-
: `Stop it: runtime
|
|
200
|
+
? `See what it printed: runtime sandboxes logs ${id}`
|
|
201
|
+
: `Stop it: runtime sandboxes kill ${id}, or sandbox.processes.get("${processId}") and kill().`,
|
|
250
202
|
details: { sandboxId: this.id, processId, stopped },
|
|
251
203
|
cause: error,
|
|
252
204
|
});
|
|
@@ -257,7 +209,7 @@ export class Sandbox {
|
|
|
257
209
|
message: `Lost the output of process ${processId}; the command may still be running.`,
|
|
258
210
|
code: "connection_error",
|
|
259
211
|
status: 0,
|
|
260
|
-
hint: `Follow it: runtime
|
|
212
|
+
hint: `Follow it: runtime sandboxes logs ${id} -f (sandbox.processes.get("${processId}")), or stop it: runtime sandboxes kill ${id}.`,
|
|
261
213
|
details: { sandboxId: this.id, processId },
|
|
262
214
|
cause: error,
|
|
263
215
|
});
|
|
@@ -368,275 +320,6 @@ export class Sandbox {
|
|
|
368
320
|
throw error;
|
|
369
321
|
}
|
|
370
322
|
}
|
|
371
|
-
async stop(options = {}) {
|
|
372
|
-
this.#keepAlive?.();
|
|
373
|
-
return this.#lifecycle("stop", options);
|
|
374
|
-
}
|
|
375
|
-
async pause(options = {}) {
|
|
376
|
-
return this.#lifecycle("pause", options);
|
|
377
|
-
}
|
|
378
|
-
/** Deletes it for good: stops it if it runs or is paused, deletes its disk
|
|
379
|
-
* and paused memory, revokes its previews and ports, and removes it from
|
|
380
|
-
* lists. Its snapshots, usage and audit entries stay. Deleting it again
|
|
381
|
-
* answers the same. */
|
|
382
|
-
async delete(options = {}) {
|
|
383
|
-
this.#keepAlive?.();
|
|
384
|
-
return deleteSandbox(this.#t, this.id, options);
|
|
385
|
-
}
|
|
386
|
-
/** Carries on a paused sandbox; for one with a time limit,
|
|
387
|
-
* `timeoutSeconds` is its new one. */
|
|
388
|
-
async wake(options = {}) {
|
|
389
|
-
const { timeoutSeconds, ...rest } = options;
|
|
390
|
-
try {
|
|
391
|
-
return await this.#lifecycle("wake", rest, timeoutSeconds === undefined ? {} : { timeoutSeconds });
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
// Waking one that is already awake is done, not a mistake. With a new
|
|
395
|
-
// lease asked for, the refusal stands: it was not given.
|
|
396
|
-
if (!(error instanceof RuntimeError) || error.code !== "not_paused" || timeoutSeconds)
|
|
397
|
-
throw error;
|
|
398
|
-
await this.refresh(requestOnly(rest));
|
|
399
|
-
if (this.state !== "running" && this.state !== "starting")
|
|
400
|
-
throw error;
|
|
401
|
-
return this;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
/** More time before its time limit ends (at most an hour ahead of now). A
|
|
405
|
-
* sandbox with no time limit needs none: the call answers at once and
|
|
406
|
-
* changes nothing. */
|
|
407
|
-
async extend(seconds, options = {}) {
|
|
408
|
-
return this.#lifecycle("extend", { ...options, wait: false }, { seconds });
|
|
409
|
-
}
|
|
410
|
-
/** Changes its name, labels, automatic wake, idle pause or persistence;
|
|
411
|
-
* fields left out stay as they are. `persistent: true` keeps it running
|
|
412
|
-
* while credit lasts (paid only). */
|
|
413
|
-
async update(settings, options = {}) {
|
|
414
|
-
return this.#lifecycle("update", { ...options, wait: false }, { ...settings });
|
|
415
|
-
}
|
|
416
|
-
/** Moves it to another image (id, name, name:tag or name@version), keeping
|
|
417
|
-
* its id, /workspace (its home: dotfiles, pip --user and npm -g installs),
|
|
418
|
-
* volumes, environment, name and previews. Its processes restart, and
|
|
419
|
-
* everything else on its old disk (sudo installs, apt packages, /etc) is
|
|
420
|
-
* lost; snapshot it first to keep everything. A running sandbox is paused
|
|
421
|
-
* first. A switch that fails is undone (`switch_undone`), the sandbox on
|
|
422
|
-
* its old image with nothing lost. Charged as a wake. `keep` is optional:
|
|
423
|
-
* "workspace" is the only value, and it is sent either way, so an API from
|
|
424
|
-
* before it was optional accepts the call too. */
|
|
425
|
-
async switchImage(image, options = {}) {
|
|
426
|
-
const { keep = "workspace", ...rest } = options;
|
|
427
|
-
this.#keep(await this.#t.json({
|
|
428
|
-
method: "POST",
|
|
429
|
-
path: `/v1/sandboxes/${enc(this.id)}:switch-image`,
|
|
430
|
-
body: { image, keep },
|
|
431
|
-
// A pause, two boots and the copy between them.
|
|
432
|
-
wait: 120,
|
|
433
|
-
...requestOnly(rest),
|
|
434
|
-
}));
|
|
435
|
-
return this;
|
|
436
|
-
}
|
|
437
|
-
/** Gives it more or fewer vCPUs or more or less memory by a restart: its id,
|
|
438
|
-
* whole disk, volumes, environment, name and previews stay, and its
|
|
439
|
-
* programs stop (snapshot it first to keep its memory too). A running sandbox is paused, or stopped if it is
|
|
440
|
-
* persistent, and comes back running at the new size on the same server; a
|
|
441
|
-
* paused or stopped one is started. Memory bills on the new size from then.
|
|
442
|
-
* Refused, with nothing changed, when the server has no room
|
|
443
|
-
* (`no_capacity`), above your quota or the trial's 2 vCPU and 4 GiB, or
|
|
444
|
-
* while a snapshot or fork of it is being taken. `restart` is optional:
|
|
445
|
-
* true is the only value, and it is sent either way, so an API from before
|
|
446
|
-
* it was optional accepts the call too. */
|
|
447
|
-
async resize(size, options = {}) {
|
|
448
|
-
const { restart = true, ...rest } = options;
|
|
449
|
-
this.#keep(await this.#t.json({
|
|
450
|
-
method: "POST",
|
|
451
|
-
path: `/v1/sandboxes/${enc(this.id)}:resize`,
|
|
452
|
-
body: { ...size, restart },
|
|
453
|
-
// A pause or stop and a cold boot.
|
|
454
|
-
wait: 120,
|
|
455
|
-
...requestOnly(rest),
|
|
456
|
-
}));
|
|
457
|
-
return this;
|
|
458
|
-
}
|
|
459
|
-
/** Keeps a sandbox with a time limit running past it, in the background,
|
|
460
|
-
* until stop() or the returned function ends it: every `everySeconds` (60)
|
|
461
|
-
* it extends the limit so that `marginSeconds` (600) remain, never more
|
|
462
|
-
* than the hour ahead the API allows. A sandbox with no time limit
|
|
463
|
-
* (`endsAt` null) needs none, and is only watched. Running time is billed
|
|
464
|
-
* as it is used. A paused sandbox is left paused (a request wakes it,
|
|
465
|
-
* unless autoWake is off); a stopped one ends the loop. It does not keep a
|
|
466
|
-
* Node or Bun process alive by itself. */
|
|
467
|
-
keepAlive(options = {}) {
|
|
468
|
-
this.#keepAlive?.();
|
|
469
|
-
const every = Math.max(10, options.everySeconds ?? 60) * 1000;
|
|
470
|
-
const margin = Math.min(3600, Math.max(60, options.marginSeconds ?? KEEP_ALIVE_MARGIN_SECONDS));
|
|
471
|
-
let ended = false;
|
|
472
|
-
let timer;
|
|
473
|
-
const end = () => {
|
|
474
|
-
ended = true;
|
|
475
|
-
if (timer)
|
|
476
|
-
clearTimeout(timer);
|
|
477
|
-
if (this.#keepAlive === end)
|
|
478
|
-
this.#keepAlive = undefined;
|
|
479
|
-
};
|
|
480
|
-
const tick = async () => {
|
|
481
|
-
if (ended)
|
|
482
|
-
return;
|
|
483
|
-
try {
|
|
484
|
-
await this.refresh();
|
|
485
|
-
if (this.state === "stopped" || this.state === "stopping")
|
|
486
|
-
return end();
|
|
487
|
-
// No time limit: nothing to extend (an older server sends no endsAt).
|
|
488
|
-
if (this.state === "running" && this.info.endsAt !== null) {
|
|
489
|
-
const left = (Date.parse(this.info.endsAt ?? this.info.expiresAt) - Date.now()) / 1000;
|
|
490
|
-
const need = Math.ceil(margin - left);
|
|
491
|
-
if (need >= 1)
|
|
492
|
-
await this.extend(Math.min(3600, need));
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
options.onError?.(error);
|
|
497
|
-
}
|
|
498
|
-
if (ended)
|
|
499
|
-
return;
|
|
500
|
-
timer = setTimeout(() => void tick(), every);
|
|
501
|
-
timer.unref?.();
|
|
502
|
-
};
|
|
503
|
-
this.#keepAlive = end;
|
|
504
|
-
void tick();
|
|
505
|
-
return end;
|
|
506
|
-
}
|
|
507
|
-
/** Days a paused sandbox is kept before deletion (1 to 365). */
|
|
508
|
-
async setRetention(days, options = {}) {
|
|
509
|
-
return this.#lifecycle("retention", { ...options, wait: false }, { days });
|
|
510
|
-
}
|
|
511
|
-
/** Keeps this sandbox's whole machine (files, memory, running processes) as
|
|
512
|
-
* a snapshot to start new sandboxes from. A running sandbox is paused for
|
|
513
|
-
* the moment it takes, then woken; a paused one stays paused. Past
|
|
514
|
-
* `timeoutMs` (ten minutes unless given) mid-capture, it stays paused and
|
|
515
|
-
* the error names the snapshot. */
|
|
516
|
-
async snapshot(options = {}) {
|
|
517
|
-
const { idempotencyKey, signal, timeoutMs, ...body } = options;
|
|
518
|
-
if (body.mode !== undefined && body.mode !== "memory" && body.mode !== "disk")
|
|
519
|
-
throw new TypeError("Snapshot mode must be memory or disk.");
|
|
520
|
-
signal?.throwIfAborted();
|
|
521
|
-
// One minute, whatever `timeoutMs` said, was too short: pausing a busy
|
|
522
|
-
// 16 vCPU, 32 GiB sandbox writes its memory out first (4 October 2026).
|
|
523
|
-
const until = performance.now() + (timeoutMs && timeoutMs > 0 ? timeoutMs : SNAPSHOT_DEADLINE_MS);
|
|
524
|
-
let running = false;
|
|
525
|
-
/** The capture in progress, once the server has one. */
|
|
526
|
-
let capturing;
|
|
527
|
-
let late = false;
|
|
528
|
-
const request = () => {
|
|
529
|
-
signal?.throwIfAborted();
|
|
530
|
-
const left = Math.ceil(until - performance.now());
|
|
531
|
-
if (left <= 0 && capturing) {
|
|
532
|
-
late = true;
|
|
533
|
-
throw new RuntimeError({
|
|
534
|
-
code: "snapshot_timeout",
|
|
535
|
-
status: 0,
|
|
536
|
-
message: `Snapshot ${capturing} was still capturing at the deadline.${running ? " The sandbox stays paused until it ends: waking it now would fail the capture." : ""}`,
|
|
537
|
-
hint: `Wait until runtime.snapshots.get("${capturing}") is ready${running ? ", then wake the sandbox" : ""}, or pass a longer timeoutMs.`,
|
|
538
|
-
details: { snapshotId: capturing, sourceSandboxId: this.id },
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
if (left <= 0)
|
|
542
|
-
throw new RuntimeError({
|
|
543
|
-
code: "snapshot_timeout",
|
|
544
|
-
status: 0,
|
|
545
|
-
message: "Snapshot capture ran past its deadline.",
|
|
546
|
-
});
|
|
547
|
-
return { ...(signal ? { signal } : {}), timeoutMs: left };
|
|
548
|
-
};
|
|
549
|
-
await this.refresh(request());
|
|
550
|
-
// Straight after a fork or a wake the sandbox is still `resuming`, and
|
|
551
|
-
// after a pause still `pausing`: wait for where it is going, or a
|
|
552
|
-
// snapshot of it is refused as not paused (user lane, 23 September 2026).
|
|
553
|
-
if (this.state === "resuming" || this.state === "starting")
|
|
554
|
-
await this.waitFor("running", request());
|
|
555
|
-
else if (this.state === "pausing")
|
|
556
|
-
await this.waitFor("paused", request());
|
|
557
|
-
running = this.state === "running";
|
|
558
|
-
// Check before entering cleanup: an abort during refresh must not pause or wake.
|
|
559
|
-
const pauseOptions = running ? request() : undefined;
|
|
560
|
-
let captured;
|
|
561
|
-
let primary;
|
|
562
|
-
let failed = false;
|
|
563
|
-
try {
|
|
564
|
-
if (running)
|
|
565
|
-
await this.pause(pauseOptions);
|
|
566
|
-
let snapshot = await this.#t.json({
|
|
567
|
-
method: "POST",
|
|
568
|
-
path: `/v1/sandboxes/${enc(this.id)}:snapshot`,
|
|
569
|
-
body,
|
|
570
|
-
wait: 10,
|
|
571
|
-
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
572
|
-
...request(),
|
|
573
|
-
});
|
|
574
|
-
captured = snapshot;
|
|
575
|
-
// Prefer: wait is bounded on the server. A capture still in progress
|
|
576
|
-
// must keep its source paused, or the worker refuses it after we wake.
|
|
577
|
-
while (snapshot.state === "capturing") {
|
|
578
|
-
capturing = snapshot.id;
|
|
579
|
-
request();
|
|
580
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
581
|
-
snapshot = await this.#t.json({
|
|
582
|
-
method: "GET",
|
|
583
|
-
path: `/v1/snapshots/${enc(snapshot.id)}`,
|
|
584
|
-
...request(),
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
if (snapshot.state !== "ready")
|
|
588
|
-
throw new RuntimeError({
|
|
589
|
-
code: "snapshot_failed",
|
|
590
|
-
status: 409,
|
|
591
|
-
message: snapshot.error ?? `Snapshot capture ended in state ${snapshot.state}.`,
|
|
592
|
-
details: { snapshotId: snapshot.id },
|
|
593
|
-
});
|
|
594
|
-
if (body.mode === "disk" && snapshot.mode !== "disk")
|
|
595
|
-
throw new RuntimeError({
|
|
596
|
-
code: "snapshot_mode_mismatch",
|
|
597
|
-
status: 409,
|
|
598
|
-
message: "The server did not confirm a disk-only snapshot.",
|
|
599
|
-
details: { snapshotId: snapshot.id },
|
|
600
|
-
});
|
|
601
|
-
captured = snapshot;
|
|
602
|
-
}
|
|
603
|
-
catch (error) {
|
|
604
|
-
failed = true;
|
|
605
|
-
primary = error;
|
|
606
|
-
// A request cut off by the deadline mid-capture is late all the same.
|
|
607
|
-
if (capturing && performance.now() >= until)
|
|
608
|
-
late = true;
|
|
609
|
-
}
|
|
610
|
-
// A capture still running needs its source paused: the worker fails one
|
|
611
|
-
// whose source woke. The timeout says so and leaves the wake to the caller.
|
|
612
|
-
if (running && !late) {
|
|
613
|
-
try {
|
|
614
|
-
await this.wake();
|
|
615
|
-
}
|
|
616
|
-
catch (wakeError) {
|
|
617
|
-
const recovery = {
|
|
618
|
-
...(captured ? { snapshotId: captured.id } : {}),
|
|
619
|
-
sourceSandboxId: this.id,
|
|
620
|
-
sourceWakeError: {
|
|
621
|
-
...(wakeError instanceof RuntimeError ? { code: wakeError.code } : {}),
|
|
622
|
-
message: wakeError instanceof Error ? wakeError.message : String(wakeError),
|
|
623
|
-
},
|
|
624
|
-
};
|
|
625
|
-
const reported = failed ? primary : wakeError;
|
|
626
|
-
if (reported instanceof Error && Object.isExtensible(reported)) {
|
|
627
|
-
if (reported instanceof RuntimeError)
|
|
628
|
-
Object.assign(reported, { details: { ...reported.details, ...recovery } });
|
|
629
|
-
else
|
|
630
|
-
Object.assign(reported, recovery);
|
|
631
|
-
}
|
|
632
|
-
if (!failed)
|
|
633
|
-
throw wakeError;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
if (failed)
|
|
637
|
-
throw primary;
|
|
638
|
-
return captured;
|
|
639
|
-
}
|
|
640
323
|
async fork(options = {}) {
|
|
641
324
|
const { idempotencyKey, signal, timeoutMs, ...body } = options;
|
|
642
325
|
const reply = await this.#t.json({
|
|
@@ -649,29 +332,6 @@ export class Sandbox {
|
|
|
649
332
|
const sandboxes = reply.sandboxes.map((info) => new Sandbox(this.#t, info));
|
|
650
333
|
return options.count === undefined ? sandboxes[0] : sandboxes;
|
|
651
334
|
}
|
|
652
|
-
/** Starts a stopped persistent sandbox again from its disk (memory is not kept). */
|
|
653
|
-
async restart(options = {}) {
|
|
654
|
-
return this.#lifecycle("restart", options);
|
|
655
|
-
}
|
|
656
|
-
async #lifecycle(verb, options, body = {}) {
|
|
657
|
-
this.#keep(await this.#t.json({
|
|
658
|
-
method: "POST",
|
|
659
|
-
path: `/v1/sandboxes/${enc(this.id)}:${verb}`,
|
|
660
|
-
body,
|
|
661
|
-
wait: options.wait === false ? 0 : 60,
|
|
662
|
-
...requestOnly(options),
|
|
663
|
-
}));
|
|
664
|
-
return this;
|
|
665
|
-
}
|
|
666
|
-
async [Symbol.asyncDispose]() {
|
|
667
|
-
this.#keepAlive?.();
|
|
668
|
-
if (this.#info.state === "stopped")
|
|
669
|
-
return;
|
|
670
|
-
await this.stop({ wait: false }).catch(() => undefined);
|
|
671
|
-
}
|
|
672
|
-
toJSON() {
|
|
673
|
-
return this.#info;
|
|
674
|
-
}
|
|
675
335
|
}
|
|
676
336
|
/** Where a reader of a process's output is, in bytes of stdout and stderr
|
|
677
337
|
* together. Output it did not receive, named by the server or not, becomes a
|
|
@@ -747,15 +407,6 @@ function pick(options) {
|
|
|
747
407
|
...(options.timeoutMs ? { timeoutMs: options.timeoutMs + 60_000 } : {}),
|
|
748
408
|
};
|
|
749
409
|
}
|
|
750
|
-
/** Any other call's request options, as the caller gave them: `timeoutMs` is
|
|
751
|
-
* the call's whole deadline, and 0 turns it off. */
|
|
752
|
-
function requestOnly(options) {
|
|
753
|
-
return {
|
|
754
|
-
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
755
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
756
|
-
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
410
|
/** Sessions: a token your backend makes and hands to your own frontend, which
|
|
760
411
|
* then runs commands, reads and writes files and reaches previews in this one
|
|
761
412
|
* sandbox directly, without proxying through your servers. A session cannot
|
|
@@ -894,7 +545,7 @@ export class Processes {
|
|
|
894
545
|
message: `Lost the output stream of process ${processId} for ${FOLLOW_OUTAGE_MS / 60_000} minutes; the command may still be running.`,
|
|
895
546
|
code: "connection_error",
|
|
896
547
|
status: 0,
|
|
897
|
-
hint: `Follow it again: runtime
|
|
548
|
+
hint: `Follow it again: runtime sandboxes logs ${this.sandboxId} ${processId} -f`,
|
|
898
549
|
details: { sandboxId: this.sandboxId, processId },
|
|
899
550
|
cause: failure,
|
|
900
551
|
});
|
|
@@ -905,7 +556,7 @@ export class Processes {
|
|
|
905
556
|
message: `The output stream of process ${processId} keeps closing before it ends.`,
|
|
906
557
|
code: "connection_error",
|
|
907
558
|
status: 0,
|
|
908
|
-
hint: `Read what it printed so far: runtime
|
|
559
|
+
hint: `Read what it printed so far: runtime sandboxes logs ${this.sandboxId} ${processId}`,
|
|
909
560
|
details: { sandboxId: this.sandboxId, processId },
|
|
910
561
|
});
|
|
911
562
|
}
|
|
@@ -997,6 +648,7 @@ export class Process {
|
|
|
997
648
|
...this.info,
|
|
998
649
|
state: exit.state,
|
|
999
650
|
exitCode: exit.exitCode,
|
|
651
|
+
...(exit.outOfMemory ? { outOfMemory: exit.outOfMemory } : {}),
|
|
1000
652
|
};
|
|
1001
653
|
return {
|
|
1002
654
|
exitCode: exit?.exitCode ?? null,
|
|
@@ -1004,6 +656,7 @@ export class Process {
|
|
|
1004
656
|
timedOut: exit?.timedOut ?? false,
|
|
1005
657
|
...truncation(dropped),
|
|
1006
658
|
processId: this.id,
|
|
659
|
+
...(exit?.outOfMemory ? { outOfMemory: exit.outOfMemory } : {}),
|
|
1007
660
|
};
|
|
1008
661
|
}
|
|
1009
662
|
/** Sends input. Offsets protect transport retries; signal and timeoutMs cover
|
|
@@ -1618,9 +1271,11 @@ export class Files {
|
|
|
1618
1271
|
const data = await readFile(localPath);
|
|
1619
1272
|
const mode = info.mode & 0o777;
|
|
1620
1273
|
await this.write(remotePath, data, { mode }).catch(async (error) => {
|
|
1621
|
-
//
|
|
1274
|
+
// Make a missing parent, but keep errors about the sandbox or upload.
|
|
1622
1275
|
const parent = remotePath.slice(0, remotePath.lastIndexOf("/"));
|
|
1623
|
-
if (!(error instanceof NotFoundError) ||
|
|
1276
|
+
if (!(error instanceof NotFoundError) ||
|
|
1277
|
+
(error.code !== "file_not_found" && error.code !== "path_not_found") ||
|
|
1278
|
+
!parent)
|
|
1624
1279
|
throw error;
|
|
1625
1280
|
await this.mkdir(parent, { parents: true });
|
|
1626
1281
|
await this.write(remotePath, data, { mode });
|
|
@@ -1752,11 +1407,7 @@ export class Files {
|
|
|
1752
1407
|
}
|
|
1753
1408
|
}
|
|
1754
1409
|
}
|
|
1410
|
+
/** One page of `runtime.sandboxes.list()`, and the pages after it. */
|
|
1755
1411
|
export function sandboxPage(t, body, query, options = {}) {
|
|
1756
|
-
return
|
|
1757
|
-
method: "GET",
|
|
1758
|
-
path: "/v1/sandboxes",
|
|
1759
|
-
query: Object.assign({}, query, { cursor }),
|
|
1760
|
-
...options,
|
|
1761
|
-
}), query, options));
|
|
1412
|
+
return machinePage(SANDBOXES, t, (transport, info) => new Sandbox(transport, info), body, query, options);
|
|
1762
1413
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `withruntime/sandboxes`: Sandboxes alone. Linux microVMs: create, find and list sandboxes.
|
|
2
|
+
*
|
|
3
|
+
* import { sandboxes } from "withruntime/sandboxes";
|
|
4
|
+
* const client = sandboxes(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime, type RuntimeOptions } from "../client.js";
|
|
7
|
+
export { Sandbox } from "../index.js";
|
|
8
|
+
export { Sandboxes } from "../client.js";
|
|
9
|
+
export type * from "../types.js";
|
|
10
|
+
/** `runtime.sandboxes` of a client made with `options`. */
|
|
11
|
+
export declare function sandboxes(options?: RuntimeOptions): Runtime["sandboxes"];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** `withruntime/sandboxes`: Sandboxes alone. Linux microVMs: create, find and list sandboxes.
|
|
2
|
+
*
|
|
3
|
+
* import { sandboxes } from "withruntime/sandboxes";
|
|
4
|
+
* const client = sandboxes(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime } from "../client.js";
|
|
7
|
+
export { Sandbox } from "../index.js";
|
|
8
|
+
export { Sandboxes } from "../client.js";
|
|
9
|
+
/** `runtime.sandboxes` of a client made with `options`. */
|
|
10
|
+
export function sandboxes(options) {
|
|
11
|
+
return new Runtime(options).sandboxes;
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** `withruntime/snapshots`: Snapshots alone. Saved sandboxes, disk and memory, to start or fork from.
|
|
2
|
+
*
|
|
3
|
+
* import { snapshots } from "withruntime/snapshots";
|
|
4
|
+
* const client = snapshots(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime, type RuntimeOptions } from "../client.js";
|
|
7
|
+
export { Snapshots, type Snapshot, type SnapshotOptions } from "../snapshots.js";
|
|
8
|
+
/** `runtime.snapshots` of a client made with `options`. */
|
|
9
|
+
export declare function snapshots(options?: RuntimeOptions): Runtime["snapshots"];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `withruntime/snapshots`: Snapshots alone. Saved sandboxes, disk and memory, to start or fork from.
|
|
2
|
+
*
|
|
3
|
+
* import { snapshots } from "withruntime/snapshots";
|
|
4
|
+
* const client = snapshots(); // RUNTIME_API_KEY, or this machine's saved connection
|
|
5
|
+
*/
|
|
6
|
+
import { Runtime } from "../client.js";
|
|
7
|
+
export { Snapshots } from "../snapshots.js";
|
|
8
|
+
/** `runtime.snapshots` of a client made with `options`. */
|
|
9
|
+
export function snapshots(options) {
|
|
10
|
+
return new Runtime(options).snapshots;
|
|
11
|
+
}
|
package/dist/snapshots.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export type Snapshot = {
|
|
|
17
17
|
diskMiB: number;
|
|
18
18
|
memoryGuarantee: string;
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
/** Null: kept while your account has credit, up to a year from when it was taken. */
|
|
21
|
+
retentionDays: number | null;
|
|
21
22
|
/** True while raw fork memory awaits verified compression. The snapshot
|
|
22
23
|
* remains ready for starts on its current host, with no storage charge or
|
|
23
24
|
* off-host copy until final publication. Absent on older servers. */
|
|
@@ -39,7 +40,8 @@ export type Snapshot = {
|
|
|
39
40
|
error: string | null;
|
|
40
41
|
createdAt: string;
|
|
41
42
|
readyAt: string | null;
|
|
42
|
-
|
|
43
|
+
/** Null while it is kept as long as your account has credit. */
|
|
44
|
+
expiresAt: string | null;
|
|
43
45
|
[key: string]: unknown;
|
|
44
46
|
};
|
|
45
47
|
export type SnapshotOptions = {
|
|
@@ -47,7 +49,8 @@ export type SnapshotOptions = {
|
|
|
47
49
|
mode?: "memory" | "disk";
|
|
48
50
|
name?: string;
|
|
49
51
|
labels?: Record<string, string>;
|
|
50
|
-
|
|
52
|
+
/** Days, or null to keep it while your account has credit. */
|
|
53
|
+
retentionDays?: number | null;
|
|
51
54
|
};
|
|
52
55
|
/** `runtime.snapshots`. Take one with `sbx.snapshot()`, start from one with
|
|
53
56
|
* `runtime.sandboxes.create({ snapshot: id })`, or do both with `sbx.fork()`. */
|
|
@@ -71,5 +74,5 @@ export declare class Snapshots {
|
|
|
71
74
|
ifLabels?: Record<string, string>;
|
|
72
75
|
}, options?: RequestOptions): Promise<Snapshot>;
|
|
73
76
|
delete(id: string, options?: RequestOptions): Promise<void>;
|
|
74
|
-
extend(id: string, retentionDays: number, options?: RequestOptions): Promise<Snapshot>;
|
|
77
|
+
extend(id: string, retentionDays: number | null, options?: RequestOptions): Promise<Snapshot>;
|
|
75
78
|
}
|