withruntime 0.11.1 → 0.11.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/BLAXEL.md +1 -1
- package/DAYTONA.md +1 -1
- package/E2B.md +57 -29
- package/README.md +4 -3
- package/README.src.md +4 -3
- package/compatibility-lock.json +14 -14
- package/dist/blaxel/errors.js +4 -4
- package/dist/blaxel/sandbox.d.ts +1 -2
- package/dist/blaxel/sandbox.js +1 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +63 -15
- package/dist/client.d.ts +11 -59
- package/dist/client.js +8 -113
- package/dist/daytona/daytona.d.ts +1 -2
- package/dist/daytona/daytona.js +1 -2
- package/dist/daytona/sandbox.d.ts +1 -1
- package/dist/daytona/sandbox.js +1 -1
- package/dist/e2b/client.d.ts +15 -2
- package/dist/e2b/client.js +61 -7
- package/dist/e2b/commands.d.ts +3 -0
- package/dist/e2b/commands.js +16 -5
- package/dist/e2b/errors.d.ts +26 -2
- package/dist/e2b/errors.js +38 -5
- package/dist/e2b/filesystem.d.ts +1 -1
- package/dist/e2b/filesystem.js +82 -16
- package/dist/e2b/index.d.ts +2 -2
- package/dist/e2b/index.js +2 -2
- package/dist/e2b/sandbox.d.ts +23 -14
- package/dist/e2b/sandbox.js +195 -178
- package/dist/e2b/unsupported.d.ts +19 -0
- package/dist/e2b/unsupported.js +21 -0
- package/dist/e2b/users.d.ts +9 -0
- package/dist/e2b/users.js +22 -0
- package/dist/errors.d.ts +4 -4
- package/dist/errors.js +12 -10
- package/dist/machine.d.ts +180 -0
- package/dist/machine.js +504 -0
- package/dist/openai-agents/index.d.ts +1 -1
- package/dist/openai-agents/index.js +1 -1
- package/dist/products/jobs.d.ts +2 -3
- package/dist/products/jobs.js +1 -2
- package/dist/products/limits.d.ts +4 -10
- package/dist/products/network.d.ts +1 -1
- package/dist/products/secrets.d.ts +1 -1
- package/dist/products/switching.d.ts +2 -3
- package/dist/sandbox.d.ts +7 -92
- package/dist/sandbox.js +8 -362
- package/dist/transport.d.ts +4 -2
- package/dist/transport.js +6 -4
- package/dist/types.d.ts +53 -8
- package/dist/vercel/sandbox.d.ts +1 -2
- package/dist/vercel/sandbox.js +1 -2
- package/package.json +1 -1
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import { Page } from "./page.js";
|
|
1
|
+
import type { Page } from "./page.js";
|
|
2
2
|
import { type FileEvent, type WatchHandle, type WatchOptions } from "./products/watch.js";
|
|
3
3
|
import { Transport, type RequestOptions } from "./transport.js";
|
|
4
|
-
import type { CommandResult, DeletedSandbox, ExecOptions, FileEntry, OutputEvent, BinaryOutputEvent,
|
|
4
|
+
import type { CommandResult, DeletedSandbox, ExecOptions, FileEntry, OutputEvent, BinaryOutputEvent, ProcessInfo, SandboxInfo, SandboxSettings } from "./types.js";
|
|
5
5
|
import { type SandboxExtensions } from "./products/index.js";
|
|
6
|
-
import type { Snapshot, SnapshotOptions } from "./snapshots.js";
|
|
7
6
|
import { Tunnel, type PortForward } from "./tunnel.js";
|
|
8
7
|
import type { StreamUploadSource } from "./file-source.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export declare
|
|
8
|
+
import { Machine, type MachineProduct } from "./machine.js";
|
|
9
|
+
/** Sandboxes, as a product of the machine engine (machine.ts). */
|
|
10
|
+
export declare const SANDBOXES: MachineProduct;
|
|
12
11
|
/** A running (or stopped) sandbox. Every method is one API call or a short
|
|
13
12
|
* sequence of them; `await using sbx = await Sandbox.create()` stops it for
|
|
14
13
|
* you at the end of the block. */
|
|
15
14
|
export interface Sandbox extends SandboxExtensions {
|
|
16
15
|
}
|
|
17
|
-
export declare class Sandbox
|
|
16
|
+
export declare class Sandbox extends Machine<SandboxInfo, SandboxSettings, DeletedSandbox> {
|
|
18
17
|
#private;
|
|
19
18
|
readonly files: Files;
|
|
20
19
|
readonly processes: Processes;
|
|
21
20
|
/** Short-lived tokens that let a browser reach this sandbox directly. */
|
|
22
21
|
readonly sessions: SandboxSessions;
|
|
23
22
|
constructor(transport: Transport, info: SandboxInfo);
|
|
24
|
-
get id(): string;
|
|
25
23
|
/** A sandbox reached with a session token instead of an API key, for code in
|
|
26
24
|
* a browser: commands and their streams, processes, files and previews of
|
|
27
25
|
* this one sandbox. Makes no call; `info` holds only the id until
|
|
@@ -39,14 +37,6 @@ export declare class Sandbox implements AsyncDisposable {
|
|
|
39
37
|
apiUrl?: string;
|
|
40
38
|
fetch?: typeof fetch;
|
|
41
39
|
}): Sandbox;
|
|
42
|
-
/** What the API last said about this sandbox. `refresh()` asks again. */
|
|
43
|
-
get info(): SandboxInfo;
|
|
44
|
-
get state(): SandboxInfo["state"];
|
|
45
|
-
refresh(options?: RequestOptions): Promise<this>;
|
|
46
|
-
/** Waits (server-side, no polling) until the sandbox reaches `state`. */
|
|
47
|
-
waitFor(state: "running" | "paused" | "stopped", options?: {
|
|
48
|
-
timeoutSeconds?: number;
|
|
49
|
-
} & RequestOptions): Promise<this>;
|
|
50
40
|
/** Runs a command and returns its exit code and output. A string runs under
|
|
51
41
|
* `bash -c`; an array runs without a shell. With onStdout/onStderr the output
|
|
52
42
|
* streams as it happens. A timeout is a result (timedOut: true, the output so
|
|
@@ -94,76 +84,6 @@ export declare class Sandbox implements AsyncDisposable {
|
|
|
94
84
|
localPort?: number;
|
|
95
85
|
host?: string;
|
|
96
86
|
}): Promise<PortForward>;
|
|
97
|
-
stop(options?: RequestOptions & {
|
|
98
|
-
wait?: boolean;
|
|
99
|
-
}): Promise<this>;
|
|
100
|
-
pause(options?: RequestOptions & {
|
|
101
|
-
wait?: boolean;
|
|
102
|
-
}): Promise<this>;
|
|
103
|
-
/** Deletes it for good: stops it if it runs or is paused, deletes its disk
|
|
104
|
-
* and paused memory, revokes its previews and ports, and removes it from
|
|
105
|
-
* lists. Its snapshots, usage and audit entries stay. Deleting it again
|
|
106
|
-
* answers the same. */
|
|
107
|
-
delete(options?: RequestOptions): Promise<DeletedSandbox>;
|
|
108
|
-
/** Carries on a paused sandbox; for one with a time limit,
|
|
109
|
-
* `timeoutSeconds` is its new one. */
|
|
110
|
-
wake(options?: RequestOptions & {
|
|
111
|
-
wait?: boolean;
|
|
112
|
-
timeoutSeconds?: number;
|
|
113
|
-
}): Promise<this>;
|
|
114
|
-
/** More time before its time limit ends (at most an hour ahead of now). A
|
|
115
|
-
* sandbox with no time limit needs none: the call answers at once and
|
|
116
|
-
* changes nothing. */
|
|
117
|
-
extend(seconds: number, options?: RequestOptions): Promise<this>;
|
|
118
|
-
/** Changes its name, labels, automatic wake, idle pause or persistence;
|
|
119
|
-
* fields left out stay as they are. `persistent: true` keeps it running
|
|
120
|
-
* while credit lasts (paid only). */
|
|
121
|
-
update(settings: SandboxSettings, options?: RequestOptions): Promise<this>;
|
|
122
|
-
/** Moves it to another image (id, name, name:tag or name@version), keeping
|
|
123
|
-
* its id, /workspace (its home: dotfiles, pip --user and npm -g installs),
|
|
124
|
-
* volumes, environment, name and previews. Its processes restart, and
|
|
125
|
-
* everything else on its old disk (sudo installs, apt packages, /etc) is
|
|
126
|
-
* lost; snapshot it first to keep everything. A running sandbox is paused
|
|
127
|
-
* first. A switch that fails is undone (`switch_undone`), the sandbox on
|
|
128
|
-
* its old image with nothing lost. Charged as a wake. `keep` is optional:
|
|
129
|
-
* "workspace" is the only value, and it is sent either way, so an API from
|
|
130
|
-
* before it was optional accepts the call too. */
|
|
131
|
-
switchImage(image: string, options?: RequestOptions & {
|
|
132
|
-
keep?: "workspace";
|
|
133
|
-
}): Promise<this>;
|
|
134
|
-
/** Gives it more or fewer vCPUs or more or less memory by a restart: its id,
|
|
135
|
-
* whole disk, volumes, environment, name and previews stay, and its
|
|
136
|
-
* programs stop (snapshot it first to keep its memory too). A running sandbox is paused, or stopped if it is
|
|
137
|
-
* persistent, and comes back running at the new size on the same server; a
|
|
138
|
-
* paused or stopped one is started. Memory bills on the new size from then.
|
|
139
|
-
* Refused, with nothing changed, when the server has no room
|
|
140
|
-
* (`no_capacity`), above your quota or the trial's 2 vCPU and 4 GiB, or
|
|
141
|
-
* while a snapshot or fork of it is being taken. `restart` is optional:
|
|
142
|
-
* true is the only value, and it is sent either way, so an API from before
|
|
143
|
-
* it was optional accepts the call too. */
|
|
144
|
-
resize(size: {
|
|
145
|
-
vcpu?: number;
|
|
146
|
-
memoryMiB?: number;
|
|
147
|
-
}, options?: RequestOptions & {
|
|
148
|
-
restart?: true;
|
|
149
|
-
}): Promise<this>;
|
|
150
|
-
/** Keeps a sandbox with a time limit running past it, in the background,
|
|
151
|
-
* until stop() or the returned function ends it: every `everySeconds` (60)
|
|
152
|
-
* it extends the limit so that `marginSeconds` (600) remain, never more
|
|
153
|
-
* than the hour ahead the API allows. A sandbox with no time limit
|
|
154
|
-
* (`endsAt` null) needs none, and is only watched. Running time is billed
|
|
155
|
-
* as it is used. A paused sandbox is left paused (a request wakes it,
|
|
156
|
-
* unless autoWake is off); a stopped one ends the loop. It does not keep a
|
|
157
|
-
* Node or Bun process alive by itself. */
|
|
158
|
-
keepAlive(options?: KeepAliveOptions): () => void;
|
|
159
|
-
/** Days a paused sandbox is kept before deletion (1 to 365). */
|
|
160
|
-
setRetention(days: number, options?: RequestOptions): Promise<this>;
|
|
161
|
-
/** Keeps this sandbox's whole machine (files, memory, running processes) as
|
|
162
|
-
* a snapshot to start new sandboxes from. A running sandbox is paused for
|
|
163
|
-
* the moment it takes, then woken; a paused one stays paused. Past
|
|
164
|
-
* `timeoutMs` (ten minutes unless given) mid-capture, it stays paused and
|
|
165
|
-
* the error names the snapshot. */
|
|
166
|
-
snapshot(options?: SnapshotOptions & RequestOptions): Promise<Snapshot>;
|
|
167
87
|
/** Copies of this sandbox as it is now (files, memory, running processes),
|
|
168
88
|
* each its own sandbox, answered once they run. A running sandbox is paused
|
|
169
89
|
* for the moment its snapshot takes, then woken. One without `count`; an
|
|
@@ -182,12 +102,6 @@ export declare class Sandbox implements AsyncDisposable {
|
|
|
182
102
|
keepSnapshot?: boolean;
|
|
183
103
|
funding?: "trial" | "paid";
|
|
184
104
|
} & RequestOptions): Promise<Sandbox[]>;
|
|
185
|
-
/** Starts a stopped persistent sandbox again from its disk (memory is not kept). */
|
|
186
|
-
restart(options?: RequestOptions & {
|
|
187
|
-
wait?: boolean;
|
|
188
|
-
}): Promise<this>;
|
|
189
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
190
|
-
toJSON(): SandboxInfo;
|
|
191
105
|
}
|
|
192
106
|
/** A sandbox session, as the API answers it. `token` is set only in the
|
|
193
107
|
* answer to `create`. */
|
|
@@ -411,6 +325,7 @@ export declare class Files {
|
|
|
411
325
|
* the API's folder route. */
|
|
412
326
|
download(remotePath: string, localPath: string, options?: RequestOptions): Promise<void>;
|
|
413
327
|
}
|
|
328
|
+
/** One page of `runtime.sandboxes.list()`, and the pages after it. */
|
|
414
329
|
export declare function sandboxPage(t: Transport, body: {
|
|
415
330
|
data: SandboxInfo[];
|
|
416
331
|
nextCursor: string | null;
|
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
4
|
import { Transport } 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:
|
|
@@ -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
|
|
@@ -368,275 +319,6 @@ export class Sandbox {
|
|
|
368
319
|
throw error;
|
|
369
320
|
}
|
|
370
321
|
}
|
|
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
322
|
async fork(options = {}) {
|
|
641
323
|
const { idempotencyKey, signal, timeoutMs, ...body } = options;
|
|
642
324
|
const reply = await this.#t.json({
|
|
@@ -649,29 +331,6 @@ export class Sandbox {
|
|
|
649
331
|
const sandboxes = reply.sandboxes.map((info) => new Sandbox(this.#t, info));
|
|
650
332
|
return options.count === undefined ? sandboxes[0] : sandboxes;
|
|
651
333
|
}
|
|
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
334
|
}
|
|
676
335
|
/** Where a reader of a process's output is, in bytes of stdout and stderr
|
|
677
336
|
* together. Output it did not receive, named by the server or not, becomes a
|
|
@@ -747,15 +406,6 @@ function pick(options) {
|
|
|
747
406
|
...(options.timeoutMs ? { timeoutMs: options.timeoutMs + 60_000 } : {}),
|
|
748
407
|
};
|
|
749
408
|
}
|
|
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
409
|
/** Sessions: a token your backend makes and hands to your own frontend, which
|
|
760
410
|
* then runs commands, reads and writes files and reaches previews in this one
|
|
761
411
|
* sandbox directly, without proxying through your servers. A session cannot
|
|
@@ -1752,11 +1402,7 @@ export class Files {
|
|
|
1752
1402
|
}
|
|
1753
1403
|
}
|
|
1754
1404
|
}
|
|
1405
|
+
/** One page of `runtime.sandboxes.list()`, and the pages after it. */
|
|
1755
1406
|
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));
|
|
1407
|
+
return machinePage(SANDBOXES, t, (transport, info) => new Sandbox(transport, info), body, query, options);
|
|
1762
1408
|
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
|
-
export declare const VERSION = "0.11.
|
|
2
|
+
export declare const VERSION = "0.11.2";
|
|
3
3
|
export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
4
4
|
/** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
|
|
5
5
|
* own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
|
|
@@ -50,7 +50,7 @@ export type Call = RequestOptions & {
|
|
|
50
50
|
retry?: boolean;
|
|
51
51
|
/** How long to keep retrying, with the same key and input, a refusal that
|
|
52
52
|
* clears when a sandbox stops or a host frees room (WAITS_FOR_ROOM): a
|
|
53
|
-
* full
|
|
53
|
+
* full slots without credit, a full quota, a full region. Then the refusal is thrown as
|
|
54
54
|
* it came. Only a create sets it; 0 or absent fails at once. */
|
|
55
55
|
waitForCapacityMs?: number;
|
|
56
56
|
/** Called each time a call waits for room, with the refusal it is waiting
|
|
@@ -103,6 +103,8 @@ export declare class Transport {
|
|
|
103
103
|
/** Connections held at once, answers and streams together; the rest
|
|
104
104
|
* wait their turn. Default 48: one address may hold 64 to the API. */
|
|
105
105
|
maxConnections?: number;
|
|
106
|
+
/** Called each time a call has to wait for one of those connections. */
|
|
107
|
+
onQueued?: (maxConnections: number) => void;
|
|
106
108
|
/** How long a create waits for room. Default 120 000; 0 fails at once. */
|
|
107
109
|
waitForCapacityMs?: number;
|
|
108
110
|
});
|
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectionError, DELIBERATE, errorFor, NOTHING_RAN, RuntimeError, WAITS_FOR_ROOM, } from "./errors.js";
|
|
2
2
|
import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
|
|
3
|
-
export const VERSION = "0.11.
|
|
3
|
+
export const VERSION = "0.11.2";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
5
5
|
/** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
|
|
6
6
|
* own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
|
|
@@ -120,7 +120,7 @@ function abortReason(signal) {
|
|
|
120
120
|
}
|
|
121
121
|
/** At most `maximum` holders at once; the rest wait their turn, first come
|
|
122
122
|
* first served. */
|
|
123
|
-
function slots(maximum) {
|
|
123
|
+
function slots(maximum, onWait) {
|
|
124
124
|
let active = 0;
|
|
125
125
|
const waiting = [];
|
|
126
126
|
return {
|
|
@@ -130,6 +130,7 @@ function slots(maximum) {
|
|
|
130
130
|
active++;
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
|
+
onWait?.();
|
|
133
134
|
await new Promise((resolve, reject) => {
|
|
134
135
|
const ready = () => {
|
|
135
136
|
signal?.removeEventListener("abort", abort);
|
|
@@ -203,8 +204,9 @@ export class Transport {
|
|
|
203
204
|
this.#maxRetries = options.maxRetries ?? 4;
|
|
204
205
|
this.#client = options.client ?? `sdk-js/${VERSION}`;
|
|
205
206
|
const most = Math.max(1, options.maxConnections ?? DEFAULT_MAX_CONNECTIONS);
|
|
206
|
-
|
|
207
|
-
this.#
|
|
207
|
+
const queued = options.onQueued && (() => options.onQueued(most));
|
|
208
|
+
this.#slots = slots(most, queued);
|
|
209
|
+
this.#streams = slots(Math.max(1, most - KEPT_FOR_CALLS), queued);
|
|
208
210
|
this.waitForCapacityMs = Math.max(0, options.waitForCapacityMs ?? 120_000);
|
|
209
211
|
}
|
|
210
212
|
/** The key, found once on first use when it was not given. */
|