withruntime 0.9.0 → 0.11.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/DAYTONA.md +52 -40
- package/E2B.md +65 -62
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +34 -22
- package/compatibility-lock.json +16 -16
- 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 +2 -1
- package/dist/blaxel/preview.js +15 -13
- package/dist/blaxel/process.js +56 -10
- 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.d.ts +2 -0
- package/dist/cli.js +262 -56
- package/dist/client.d.ts +41 -3
- package/dist/client.js +67 -11
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +9 -0
- package/dist/cloudflare/index.js +47 -9
- package/dist/cloudflare/worker.d.ts +4 -0
- package/dist/cloudflare/worker.js +166 -16
- package/dist/codesandbox/guest-service.js +57 -20
- package/dist/codesandbox/index.js +42 -8
- package/dist/compat/core.d.ts +9 -8
- package/dist/compat/core.js +60 -21
- package/dist/compat/files.js +22 -4
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/computer-use.d.ts +162 -0
- package/dist/daytona/computer-use.js +324 -0
- package/dist/daytona/context.d.ts +10 -0
- package/dist/daytona/context.js +24 -0
- package/dist/daytona/daytona.d.ts +20 -0
- package/dist/daytona/daytona.js +134 -20
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +129 -38
- package/dist/daytona/index.d.ts +3 -27
- package/dist/daytona/index.js +2 -7
- package/dist/daytona/process.d.ts +63 -6
- package/dist/daytona/process.js +224 -23
- package/dist/daytona/sandbox.d.ts +20 -6
- package/dist/daytona/sandbox.js +55 -32
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +6 -2
- package/dist/e2b/commands.d.ts +43 -12
- package/dist/e2b/commands.js +217 -64
- package/dist/e2b/errors.d.ts +7 -0
- package/dist/e2b/errors.js +20 -0
- package/dist/e2b/filesystem.d.ts +2 -1
- package/dist/e2b/filesystem.js +43 -6
- package/dist/e2b/index.d.ts +3 -2
- package/dist/e2b/index.js +2 -1
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +83 -0
- package/dist/e2b/sandbox.d.ts +17 -6
- package/dist/e2b/sandbox.js +239 -43
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +9 -1
- package/dist/freestyle/index.js +38 -5
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +139 -43
- package/dist/openai-agents/index.js +9 -45
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/browser.d.ts +60 -0
- package/dist/products/browser.js +37 -0
- package/dist/products/desktop.d.ts +2 -2
- package/dist/products/desktop.js +2 -2
- package/dist/products/images.d.ts +3 -1
- package/dist/products/images.js +14 -2
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +8 -2
- package/dist/products/previews.js +11 -3
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.js +61 -8
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +13 -27
- package/dist/runloop/index.js +149 -52
- package/dist/sandbox.d.ts +59 -8
- package/dist/sandbox.js +479 -68
- package/dist/snapshots.d.ts +9 -4
- package/dist/sprites/index.js +69 -33
- package/dist/tar.d.ts +3 -1
- package/dist/tar.js +140 -14
- package/dist/transport.d.ts +4 -7
- package/dist/transport.js +205 -122
- package/dist/types.d.ts +5 -2
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +3 -2
- package/dist/vercel/errors.d.ts +1 -1
- package/dist/vercel/errors.js +7 -1
- package/dist/vercel/index.d.ts +1 -7
- package/dist/vercel/index.js +1 -3
- package/dist/vercel/sandbox-user.d.ts +70 -0
- package/dist/vercel/sandbox-user.js +206 -0
- package/dist/vercel/sandbox.d.ts +55 -20
- package/dist/vercel/sandbox.js +353 -99
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +2 -1
package/dist/e2b/pty.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CommandHandle, commandRequest, connectionRequest, killProcess, resolveProcess, } from "./commands.js";
|
|
2
|
+
import { guard, InvalidArgumentError, NotSupportedError } from "./errors.js";
|
|
3
|
+
import { runAs, shellAs } from "./users.js";
|
|
4
|
+
function dimensions(size) {
|
|
5
|
+
for (const value of [size.cols, size.rows])
|
|
6
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
7
|
+
throw new InvalidArgumentError("PTY rows and columns must be positive whole numbers.");
|
|
8
|
+
return { cols: size.cols, rows: size.rows };
|
|
9
|
+
}
|
|
10
|
+
/** E2B's PTY methods over native terminal processes with lossless output. */
|
|
11
|
+
export class Pty {
|
|
12
|
+
ctx;
|
|
13
|
+
constructor(ctx) {
|
|
14
|
+
this.ctx = ctx;
|
|
15
|
+
}
|
|
16
|
+
async create(opts) {
|
|
17
|
+
const pty = dimensions(opts);
|
|
18
|
+
const opening = connectionRequest(opts, this.ctx.requestTimeoutMs);
|
|
19
|
+
if (typeof opts.onData !== "function")
|
|
20
|
+
throw new InvalidArgumentError("onData must be a function.");
|
|
21
|
+
const env = { TERM: "xterm-256color", LANG: "C.UTF-8", LC_ALL: "C.UTF-8", ...opts.envs };
|
|
22
|
+
const process = await guard("sandbox", async () => {
|
|
23
|
+
// Another user's terminal is a login shell of theirs, through sudo -u.
|
|
24
|
+
const user = await runAs(this.ctx, opts.user, opening.request);
|
|
25
|
+
await this.ctx.ensureHome(opts.cwd, opening.request);
|
|
26
|
+
opening.request.signal?.throwIfAborted();
|
|
27
|
+
const shell = user ? shellAs(user, opts.cwd !== undefined) : ["/bin/bash", "-i", "-l"];
|
|
28
|
+
return this.ctx.runtime.spawn(shell, {
|
|
29
|
+
pty,
|
|
30
|
+
stdin: "pipe",
|
|
31
|
+
outputEncoding: "base64",
|
|
32
|
+
env,
|
|
33
|
+
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
34
|
+
request: opening.request,
|
|
35
|
+
});
|
|
36
|
+
}).catch(opening.failure);
|
|
37
|
+
return new CommandHandle(process, {
|
|
38
|
+
stdin: false,
|
|
39
|
+
pty: true,
|
|
40
|
+
onData: opts.onData,
|
|
41
|
+
timeoutMs: opening.timeoutMs,
|
|
42
|
+
deadline: opening.deadline,
|
|
43
|
+
signal: opts.signal,
|
|
44
|
+
requestTimeoutMs: this.ctx.requestTimeoutMs,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async connect(pid, opts) {
|
|
48
|
+
const opening = connectionRequest(opts ?? {}, this.ctx.requestTimeoutMs);
|
|
49
|
+
const process = await resolveProcess(this.ctx, pid, opening.request).catch(opening.failure);
|
|
50
|
+
if (!process.info.pty || process.info.outputEncoding !== "base64")
|
|
51
|
+
throw new NotSupportedError("Connecting to a legacy text-output PTY", "Create the PTY through sandbox.pty.create first.");
|
|
52
|
+
return new CommandHandle(process, {
|
|
53
|
+
stdin: false,
|
|
54
|
+
pty: true,
|
|
55
|
+
onData: opts?.onData,
|
|
56
|
+
timeoutMs: opening.timeoutMs,
|
|
57
|
+
deadline: opening.deadline,
|
|
58
|
+
cursor: process.info.outputBytes,
|
|
59
|
+
signal: opts?.signal,
|
|
60
|
+
requestTimeoutMs: this.ctx.requestTimeoutMs,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async sendInput(pid, data, opts = {}) {
|
|
64
|
+
const request = commandRequest(opts, this.ctx.requestTimeoutMs);
|
|
65
|
+
const process = await resolveProcess(this.ctx, pid, request);
|
|
66
|
+
if (!process.info.pty)
|
|
67
|
+
throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
|
|
68
|
+
request.signal?.throwIfAborted();
|
|
69
|
+
await guard("sandbox", () => process.write(data, request));
|
|
70
|
+
}
|
|
71
|
+
async resize(pid, size, opts = {}) {
|
|
72
|
+
const pty = dimensions(size);
|
|
73
|
+
const request = commandRequest(opts, this.ctx.requestTimeoutMs);
|
|
74
|
+
const process = await resolveProcess(this.ctx, pid, request);
|
|
75
|
+
if (!process.info.pty)
|
|
76
|
+
throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
|
|
77
|
+
request.signal?.throwIfAborted();
|
|
78
|
+
await guard("sandbox", () => process.resize(pty.cols, pty.rows, request));
|
|
79
|
+
}
|
|
80
|
+
kill(pid, opts = {}) {
|
|
81
|
+
return killProcess(this.ctx, pid, commandRequest(opts, this.ctx.requestTimeoutMs));
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/e2b/sandbox.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Runtime } from "../client.js";
|
|
|
2
2
|
import { type ConnectionOpts, type RuntimeOpts, type RuntimeSandbox } from "./client.js";
|
|
3
3
|
import { Commands } from "./commands.js";
|
|
4
4
|
import { Filesystem } from "./filesystem.js";
|
|
5
|
+
import { Pty } from "./pty.js";
|
|
5
6
|
/** E2B's default machine: 2 vCPU and 512 MiB (docs.e2b.dev/billing, checked
|
|
6
7
|
* 23 September 2026). Pass `runtime: { create: { memoryMiB } }` for another. */
|
|
7
8
|
export declare const DEFAULT_SHAPE: {
|
|
@@ -29,8 +30,9 @@ export interface SandboxOpts extends ConnectionOpts {
|
|
|
29
30
|
/** The sandbox's own environment: every command, terminal and code run in it
|
|
30
31
|
* gets these, whoever connects. Values are never shown again. */
|
|
31
32
|
envs?: Record<string, string>;
|
|
32
|
-
/** Default 300 000. Runtime's leases run 60 s to 1 hour
|
|
33
|
-
* to 60 s (with a warning)
|
|
33
|
+
/** Default 300 000, at most 24 hours. Runtime's leases run 60 s to 1 hour:
|
|
34
|
+
* shorter rounds up to 60 s (with a warning); longer gets an hour, moved on
|
|
35
|
+
* while this process runs, up to the time asked for. */
|
|
34
36
|
timeoutMs?: number;
|
|
35
37
|
/** Accepted and ignored, as E2B ignores it. */
|
|
36
38
|
secure?: boolean;
|
|
@@ -87,12 +89,17 @@ export type SandboxListOpts = SandboxApiOpts & {
|
|
|
87
89
|
limit?: number;
|
|
88
90
|
nextToken?: string;
|
|
89
91
|
};
|
|
92
|
+
/** Test hook: renew every kept lease now, as the five-minute timer would. */
|
|
93
|
+
export declare function renewLeases(): Promise<void>;
|
|
94
|
+
/** Test hook: the end each kept sandbox is kept until. */
|
|
95
|
+
export declare function keptLeases(): Map<string, number>;
|
|
90
96
|
export declare class Sandbox {
|
|
91
97
|
#private;
|
|
92
98
|
protected readonly requestTimeoutMs: number;
|
|
93
99
|
protected static readonly defaultTemplate: string;
|
|
94
100
|
readonly files: Filesystem;
|
|
95
101
|
readonly commands: Commands;
|
|
102
|
+
readonly pty: Pty;
|
|
96
103
|
/** Use Sandbox.create or Sandbox.connect. */
|
|
97
104
|
constructor(runtime: RuntimeSandbox, client: Runtime, requestTimeoutMs?: number);
|
|
98
105
|
get sandboxId(): string;
|
|
@@ -116,8 +123,9 @@ export declare class Sandbox {
|
|
|
116
123
|
/** Stops the sandbox. False when it was not found or had already ended. */
|
|
117
124
|
static kill(sandboxId: string, opts?: SandboxApiOpts): Promise<boolean>;
|
|
118
125
|
kill(opts?: Pick<ConnectionOpts, "requestTimeoutMs" | "signal">): Promise<boolean>;
|
|
119
|
-
/** Sets the sandbox to end `timeoutMs` from now
|
|
120
|
-
*
|
|
126
|
+
/** Sets the sandbox to end `timeoutMs` from now, up to 24 hours; past an
|
|
127
|
+
* hour the lease is moved on while this process runs. Runtime cannot end a
|
|
128
|
+
* lease early, so an end sooner than the lease's is refused. */
|
|
121
129
|
static setTimeout(sandboxId: string, timeoutMs: number, opts?: SandboxApiOpts): Promise<void>;
|
|
122
130
|
setTimeout(timeoutMs: number, opts?: Pick<ConnectionOpts, "requestTimeoutMs" | "signal">): Promise<void>;
|
|
123
131
|
static getInfo(sandboxId: string, opts?: SandboxApiOpts): Promise<SandboxInfo>;
|
|
@@ -167,7 +175,6 @@ export declare class Sandbox {
|
|
|
167
175
|
* its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
|
|
168
176
|
* the address can reach it; a browser sees a one-time page naming Runtime. */
|
|
169
177
|
getPublicHost(port: number): Promise<string>;
|
|
170
|
-
get pty(): never;
|
|
171
178
|
get git(): never;
|
|
172
179
|
/** E2B's metrics, from Runtime's measured readings (`sbx.metrics()`): CPU
|
|
173
180
|
* as a percent of the sandbox's vCPUs and resident memory, one entry per
|
|
@@ -182,11 +189,15 @@ export declare class Sandbox {
|
|
|
182
189
|
getMcpUrl(): never;
|
|
183
190
|
getMcpToken(): Promise<never>;
|
|
184
191
|
}
|
|
185
|
-
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
192
|
+
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
193
|
+
* Runtime filters by state and metadata itself. A template, `startedAfter`,
|
|
194
|
+
* newest-first order or a saved `nextToken` are done here: every match is
|
|
195
|
+
* read once, then served a page at a time. */
|
|
186
196
|
export declare class SandboxPaginator {
|
|
187
197
|
#private;
|
|
188
198
|
constructor(client: Runtime, opts: SandboxListOpts);
|
|
189
199
|
get hasNext(): boolean;
|
|
200
|
+
/** Where the next page starts; `Sandbox.list({ nextToken })` resumes there. */
|
|
190
201
|
get nextToken(): string | undefined;
|
|
191
202
|
nextItems(): Promise<SandboxInfo[]>;
|
|
192
203
|
}
|
package/dist/e2b/sandbox.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { clientFor, request, } from "./client.js";
|
|
2
2
|
import { Commands } from "./commands.js";
|
|
3
|
-
import { guard, InvalidArgumentError, NotSupportedError, SandboxError, SandboxNotFoundError, TemplateError, } from "./errors.js";
|
|
3
|
+
import { guard, InvalidArgumentError, NotSupportedError, PublicPreviewNotAllowedError, SandboxError, SandboxNotFoundError, TemplateError, } from "./errors.js";
|
|
4
4
|
import { Filesystem } from "./filesystem.js";
|
|
5
|
+
import { Pty } from "./pty.js";
|
|
5
6
|
/* E2B's Sandbox over Runtime's. Every call is one or a few calls of the
|
|
6
7
|
withruntime SDK; nothing here speaks HTTP. What Runtime cannot do the same
|
|
7
8
|
way throws NotSupportedError before anything happens. */
|
|
@@ -13,16 +14,23 @@ export const DEFAULT_TIMEOUT_MS = 300_000;
|
|
|
13
14
|
/** Runtime's lease bounds (MAX_TIMEOUT_SECONDS in the API). */
|
|
14
15
|
const MIN_LEASE_SECONDS = 60;
|
|
15
16
|
const MAX_LEASE_SECONDS = 3600;
|
|
17
|
+
/** E2B's longest sandbox timeout, 24 hours (its Pro plan). */
|
|
18
|
+
const LONGEST_TIMEOUT_MS = 86_400_000;
|
|
16
19
|
/** E2B template names that mean "the stock environment": Runtime's default image. */
|
|
17
20
|
const STOCK_TEMPLATES = new Set(["base", "code-interpreter-v1", "code-interpreter"]);
|
|
18
21
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19
22
|
let warnedShortLease = false;
|
|
23
|
+
/** E2B's own bound since 2.52.0: refused before anything happens. */
|
|
24
|
+
const MAX_FORK_COUNT = 20;
|
|
25
|
+
function checkForkCount(count) {
|
|
26
|
+
if (count !== undefined && (!Number.isInteger(count) || count < 1 || count > MAX_FORK_COUNT))
|
|
27
|
+
throw new InvalidArgumentError(`count must be an integer between 1 and ${MAX_FORK_COUNT}`);
|
|
28
|
+
}
|
|
29
|
+
/** The first lease for `timeoutMs`: up to an hour; `keepUntil` carries a
|
|
30
|
+
* longer timeout on from there. */
|
|
20
31
|
function leaseSeconds(timeoutMs) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const seconds = Math.ceil(timeoutMs / 1000);
|
|
24
|
-
if (seconds > MAX_LEASE_SECONDS)
|
|
25
|
-
throw new NotSupportedError(`A sandbox timeout of ${timeoutMs} ms (over one hour)`, "Runtime leases last up to an hour (3_600_000 ms); call sandbox.setTimeout(...) before it ends to keep going, as often as needed.");
|
|
32
|
+
checkTimeout(timeoutMs);
|
|
33
|
+
const seconds = Math.min(Math.ceil(timeoutMs / 1000), MAX_LEASE_SECONDS);
|
|
26
34
|
if (seconds < MIN_LEASE_SECONDS) {
|
|
27
35
|
if (!warnedShortLease && typeof process !== "undefined") {
|
|
28
36
|
warnedShortLease = true;
|
|
@@ -32,6 +40,85 @@ function leaseSeconds(timeoutMs) {
|
|
|
32
40
|
}
|
|
33
41
|
return seconds;
|
|
34
42
|
}
|
|
43
|
+
function checkTimeout(timeoutMs) {
|
|
44
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
45
|
+
throw new InvalidArgumentError(`timeoutMs must be a positive number, not ${timeoutMs}.`);
|
|
46
|
+
if (timeoutMs > LONGEST_TIMEOUT_MS)
|
|
47
|
+
throw new InvalidArgumentError(`timeoutMs is at most 24 hours (86_400_000 ms), as on E2B, not ${timeoutMs}.`);
|
|
48
|
+
}
|
|
49
|
+
/* Timeouts over an hour. Runtime's lease reaches at most an hour ahead of
|
|
50
|
+
now, and E2B's timeout up to a day. While this process runs, the adapter
|
|
51
|
+
moves the lease on every five minutes, as far as an hour ahead and never
|
|
52
|
+
past the end asked for. One keeper per sandbox, whichever object asked.
|
|
53
|
+
If the process ends first, the sandbox ends when its lease does, at most
|
|
54
|
+
an hour later; it is never kept past the time asked for. */
|
|
55
|
+
const KEEP_EVERY_MS = 5 * 60_000;
|
|
56
|
+
/** Kept under the API's hour, for the clocks and the trip. */
|
|
57
|
+
const KEEP_AHEAD_MS = (MAX_LEASE_SECONDS - 30) * 1000;
|
|
58
|
+
const keepers = new Map();
|
|
59
|
+
let warnedKeeper = false;
|
|
60
|
+
/** Keeps `runtime` until `until` (epoch ms), or stops keeping it when that
|
|
61
|
+
* is within the hour its lease can already reach. */
|
|
62
|
+
function keepUntil(runtime, until) {
|
|
63
|
+
const keeper = keepers.get(runtime.id);
|
|
64
|
+
if (until - Date.now() <= MAX_LEASE_SECONDS * 1000)
|
|
65
|
+
return stopKeeping(runtime.id);
|
|
66
|
+
if (keeper) {
|
|
67
|
+
keeper.until = until;
|
|
68
|
+
keeper.runtime = runtime;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const made = { until, runtime };
|
|
72
|
+
keepers.set(runtime.id, made);
|
|
73
|
+
schedule(made);
|
|
74
|
+
}
|
|
75
|
+
function stopKeeping(sandboxId) {
|
|
76
|
+
const keeper = keepers.get(sandboxId);
|
|
77
|
+
if (keeper?.timer !== undefined)
|
|
78
|
+
clearTimeout(keeper.timer);
|
|
79
|
+
keepers.delete(sandboxId);
|
|
80
|
+
}
|
|
81
|
+
function schedule(keeper) {
|
|
82
|
+
keeper.timer = setTimeout(() => void renew(keeper), KEEP_EVERY_MS);
|
|
83
|
+
// Never what keeps a Node or Bun process running.
|
|
84
|
+
keeper.timer.unref?.();
|
|
85
|
+
}
|
|
86
|
+
async function renew(keeper) {
|
|
87
|
+
const id = keeper.runtime.id;
|
|
88
|
+
if (keepers.get(id) !== keeper)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
await keeper.runtime.refresh();
|
|
92
|
+
const current = state(keeper.runtime);
|
|
93
|
+
if (current === "stopped")
|
|
94
|
+
return stopKeeping(id);
|
|
95
|
+
if (current === "running") {
|
|
96
|
+
const end = Date.parse(keeper.runtime.info.expiresAt);
|
|
97
|
+
const want = Math.min(keeper.until, Date.now() + KEEP_AHEAD_MS);
|
|
98
|
+
const later = Math.floor((want - end) / 1000);
|
|
99
|
+
if (later >= 1)
|
|
100
|
+
await keeper.runtime.extend(later);
|
|
101
|
+
if (Date.parse(keeper.runtime.info.expiresAt) >= keeper.until - 1000)
|
|
102
|
+
return stopKeeping(id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!warnedKeeper && typeof process !== "undefined" && process.emitWarning) {
|
|
107
|
+
warnedKeeper = true;
|
|
108
|
+
process.emitWarning(`Could not extend sandbox ${id}'s lease toward its timeout: ${error instanceof Error ? error.message : String(error)}. Trying again in five minutes.`, { code: "RUNTIME_E2B_LEASE" });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (keepers.get(id) === keeper)
|
|
112
|
+
schedule(keeper);
|
|
113
|
+
}
|
|
114
|
+
/** Test hook: renew every kept lease now, as the five-minute timer would. */
|
|
115
|
+
export async function renewLeases() {
|
|
116
|
+
await Promise.all([...keepers.values()].map((keeper) => renew(keeper)));
|
|
117
|
+
}
|
|
118
|
+
/** Test hook: the end each kept sandbox is kept until. */
|
|
119
|
+
export function keptLeases() {
|
|
120
|
+
return new Map([...keepers].map(([id, keeper]) => [id, keeper.until]));
|
|
121
|
+
}
|
|
35
122
|
function onLeaseEnd(lifecycle) {
|
|
36
123
|
if (!lifecycle)
|
|
37
124
|
return "stop";
|
|
@@ -65,12 +152,13 @@ function refuseCreate(opts) {
|
|
|
65
152
|
}
|
|
66
153
|
/** Where a template sends the create: the stock image, a Runtime image by
|
|
67
154
|
* name or id, or a Runtime snapshot by id. */
|
|
68
|
-
async function resolveTemplate(client, template) {
|
|
155
|
+
async function resolveTemplate(client, template, options) {
|
|
156
|
+
options.signal?.throwIfAborted();
|
|
69
157
|
if (STOCK_TEMPLATES.has(template))
|
|
70
158
|
return {};
|
|
71
159
|
if (UUID.test(template)) {
|
|
72
160
|
try {
|
|
73
|
-
await client.images.get(template);
|
|
161
|
+
await client.images.get(template, options);
|
|
74
162
|
return { image: template };
|
|
75
163
|
}
|
|
76
164
|
catch (error) {
|
|
@@ -79,7 +167,7 @@ async function resolveTemplate(client, template) {
|
|
|
79
167
|
}
|
|
80
168
|
return { snapshot: template };
|
|
81
169
|
}
|
|
82
|
-
const page = await client.images.list({ name: template, state: "ready", limit: 1 });
|
|
170
|
+
const page = await client.images.list({ name: template, state: "ready", limit: 1 }, options);
|
|
83
171
|
const image = page.data[0];
|
|
84
172
|
if (image)
|
|
85
173
|
return { image: image.id };
|
|
@@ -135,6 +223,7 @@ export class Sandbox {
|
|
|
135
223
|
static defaultTemplate = "base";
|
|
136
224
|
files;
|
|
137
225
|
commands;
|
|
226
|
+
pty;
|
|
138
227
|
#client;
|
|
139
228
|
#runtime;
|
|
140
229
|
#home;
|
|
@@ -146,12 +235,14 @@ export class Sandbox {
|
|
|
146
235
|
this.#runtime = runtime;
|
|
147
236
|
this.#client = client;
|
|
148
237
|
const ctx = {
|
|
149
|
-
ensureHome: (text) => this.#ensureHome(text),
|
|
238
|
+
ensureHome: (text, options) => this.#ensureHome(text, options),
|
|
239
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
150
240
|
};
|
|
151
241
|
// A getter, so the modules see the sandbox as it is after each refresh.
|
|
152
242
|
Object.defineProperty(ctx, "runtime", { get: () => this.#runtime });
|
|
153
243
|
this.files = new Filesystem(ctx);
|
|
154
244
|
this.commands = new Commands(ctx);
|
|
245
|
+
this.pty = new Pty(ctx);
|
|
155
246
|
}
|
|
156
247
|
get sandboxId() {
|
|
157
248
|
return this.#runtime.id;
|
|
@@ -167,23 +258,50 @@ export class Sandbox {
|
|
|
167
258
|
/** E2B's home is /home/user and Runtime's is /workspace. The first time a
|
|
168
259
|
* path or command names /home/user, it is made a link to /workspace (unless
|
|
169
260
|
* something is already there), so paths written for E2B work. */
|
|
170
|
-
#ensureHome(text) {
|
|
261
|
+
#ensureHome(text, options = {}) {
|
|
262
|
+
const signal = options.signal;
|
|
263
|
+
signal?.throwIfAborted();
|
|
264
|
+
const wait = (pending) => {
|
|
265
|
+
if (!signal)
|
|
266
|
+
return pending;
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- rejects with the caller's abort reason as is, as the upstream SDK does
|
|
269
|
+
const abort = () => reject(signal.reason);
|
|
270
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
271
|
+
pending.then(() => {
|
|
272
|
+
signal.removeEventListener("abort", abort);
|
|
273
|
+
resolve();
|
|
274
|
+
}, (error) => {
|
|
275
|
+
signal.removeEventListener("abort", abort);
|
|
276
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- passes the pending call's own failure through unchanged
|
|
277
|
+
reject(error);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
};
|
|
171
281
|
if (this.#home || !text?.includes("/home/user"))
|
|
172
|
-
return this.#home ?? Promise.resolve();
|
|
173
|
-
|
|
174
|
-
const result = await guard("sandbox", () => this.#runtime.exec("[ -e /home/user ] || sudo ln -s /workspace /home/user"));
|
|
282
|
+
return wait(this.#home ?? Promise.resolve());
|
|
283
|
+
const pending = (async () => {
|
|
284
|
+
const result = await guard("sandbox", () => this.#runtime.exec("[ -e /home/user ] || sudo ln -s /workspace /home/user", { signal }));
|
|
175
285
|
if (result.exitCode !== 0 && typeof process !== "undefined")
|
|
176
286
|
process.emitWarning(`Could not link /home/user to /workspace: ${result.stderr.trim()}`, {
|
|
177
287
|
code: "RUNTIME_E2B_HOME",
|
|
178
288
|
});
|
|
179
289
|
})();
|
|
180
|
-
|
|
290
|
+
this.#home = pending;
|
|
291
|
+
pending.catch(() => {
|
|
292
|
+
if (this.#home === pending)
|
|
293
|
+
this.#home = undefined;
|
|
294
|
+
});
|
|
295
|
+
return wait(pending);
|
|
181
296
|
}
|
|
182
297
|
static async create(templateOrOpts, maybeOpts) {
|
|
183
298
|
const opts = (typeof templateOrOpts === "string" ? maybeOpts : templateOrOpts) ?? {};
|
|
184
299
|
const template = (typeof templateOrOpts === "string" ? templateOrOpts : opts.template) ?? this.defaultTemplate;
|
|
300
|
+
const options = request(opts);
|
|
301
|
+
options.signal?.throwIfAborted();
|
|
185
302
|
refuseCreate(opts);
|
|
186
303
|
const client = clientFor(opts);
|
|
304
|
+
const asked = Date.now() + (opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
187
305
|
const input = {
|
|
188
306
|
timeoutSeconds: leaseSeconds(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
189
307
|
onLeaseEnd: onLeaseEnd(opts.lifecycle),
|
|
@@ -196,7 +314,8 @@ export class Sandbox {
|
|
|
196
314
|
...(opts.envs && Object.keys(opts.envs).length ? { env: opts.envs } : {}),
|
|
197
315
|
...(opts.allowInternetAccess === false ? { network: { internet: false } } : {}),
|
|
198
316
|
};
|
|
199
|
-
const source = await guard("other", () => resolveTemplate(client, template));
|
|
317
|
+
const source = await guard("other", () => resolveTemplate(client, template, options));
|
|
318
|
+
options.signal?.throwIfAborted();
|
|
200
319
|
const create = {
|
|
201
320
|
// A snapshot carries its own machine; everything else gets E2B's shape.
|
|
202
321
|
...(source.snapshot ? {} : DEFAULT_SHAPE),
|
|
@@ -204,7 +323,8 @@ export class Sandbox {
|
|
|
204
323
|
...source,
|
|
205
324
|
...opts.runtime?.create,
|
|
206
325
|
};
|
|
207
|
-
const runtime = await guard("sandbox", () => client.sandboxes.create(create,
|
|
326
|
+
const runtime = await guard("sandbox", () => client.sandboxes.create(create, options));
|
|
327
|
+
keepUntil(runtime, asked);
|
|
208
328
|
return new this(runtime, client, opts.requestTimeoutMs);
|
|
209
329
|
}
|
|
210
330
|
/** Connects to a sandbox by id, waking it if it is paused, as E2B does. A
|
|
@@ -229,6 +349,7 @@ export class Sandbox {
|
|
|
229
349
|
/** Stops the sandbox. False when it was not found or had already ended. */
|
|
230
350
|
static async kill(sandboxId, opts = {}) {
|
|
231
351
|
const client = clientFor(opts);
|
|
352
|
+
stopKeeping(sandboxId);
|
|
232
353
|
try {
|
|
233
354
|
const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
|
|
234
355
|
return await stop(runtime, opts);
|
|
@@ -240,6 +361,7 @@ export class Sandbox {
|
|
|
240
361
|
}
|
|
241
362
|
}
|
|
242
363
|
async kill(opts = {}) {
|
|
364
|
+
stopKeeping(this.sandboxId);
|
|
243
365
|
try {
|
|
244
366
|
return await stop(this.#runtime, opts);
|
|
245
367
|
}
|
|
@@ -249,8 +371,9 @@ export class Sandbox {
|
|
|
249
371
|
throw error;
|
|
250
372
|
}
|
|
251
373
|
}
|
|
252
|
-
/** Sets the sandbox to end `timeoutMs` from now
|
|
253
|
-
*
|
|
374
|
+
/** Sets the sandbox to end `timeoutMs` from now, up to 24 hours; past an
|
|
375
|
+
* hour the lease is moved on while this process runs. Runtime cannot end a
|
|
376
|
+
* lease early, so an end sooner than the lease's is refused. */
|
|
254
377
|
static async setTimeout(sandboxId, timeoutMs, opts = {}) {
|
|
255
378
|
const client = clientFor(opts);
|
|
256
379
|
const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
|
|
@@ -298,6 +421,7 @@ export class Sandbox {
|
|
|
298
421
|
/** Copies of this sandbox, memory and all. While Runtime's forks are
|
|
299
422
|
* switched off this throws NotSupportedError with Runtime's own words. */
|
|
300
423
|
static async fork(sandboxId, opts = {}) {
|
|
424
|
+
checkForkCount(opts.count);
|
|
301
425
|
const { count: _count, timeoutMs: _timeoutMs, ...connection } = opts;
|
|
302
426
|
const source = await this.connect(sandboxId, connection);
|
|
303
427
|
return source.fork(opts);
|
|
@@ -307,6 +431,7 @@ export class Sandbox {
|
|
|
307
431
|
// A fork's lease is Runtime's to set; one shorter than asked could not be
|
|
308
432
|
// honoured after the forks exist, so this is refused before any are made.
|
|
309
433
|
throw new NotSupportedError("A timeout for forks (fork timeoutMs)", "Fork without it, then call setTimeout(ms) on each fork.");
|
|
434
|
+
checkForkCount(opts.count);
|
|
310
435
|
const copies = await guard("sandbox", () => this.#runtime.fork({ count: opts.count ?? 1, ...request(opts) }));
|
|
311
436
|
// Copies keep the source's environment on Runtime's side.
|
|
312
437
|
const Kind = this.constructor;
|
|
@@ -346,6 +471,10 @@ export class Sandbox {
|
|
|
346
471
|
getHost(port) {
|
|
347
472
|
if (!Number.isInteger(port) || port < 1 || port > 65_535)
|
|
348
473
|
throw new InvalidArgumentError(`port must be a whole number from 1 to 65535, not ${port}.`);
|
|
474
|
+
// A trial sandbox's ports are private: a host alone would answer 404 or
|
|
475
|
+
// 401 to everyone. Say so now, with the address that works.
|
|
476
|
+
if (this.#runtime.info.funding === "trial")
|
|
477
|
+
throw new PublicPreviewNotAllowedError(this.sandboxId, port);
|
|
349
478
|
void this.#share(port).catch((error) => {
|
|
350
479
|
if (typeof process !== "undefined" && process.emitWarning)
|
|
351
480
|
process.emitWarning(`Sharing port ${port} for getHost failed: ${error instanceof Error ? error.message : String(error)}. getPublicHost(${port}) says why.`, { code: "RUNTIME_E2B_GET_HOST" });
|
|
@@ -356,6 +485,8 @@ export class Sandbox {
|
|
|
356
485
|
* its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
|
|
357
486
|
* the address can reach it; a browser sees a one-time page naming Runtime. */
|
|
358
487
|
async getPublicHost(port) {
|
|
488
|
+
if (this.#runtime.info.funding === "trial")
|
|
489
|
+
throw new PublicPreviewNotAllowedError(this.sandboxId, port);
|
|
359
490
|
return this.#share(port);
|
|
360
491
|
}
|
|
361
492
|
#share(port) {
|
|
@@ -369,9 +500,6 @@ export class Sandbox {
|
|
|
369
500
|
return share;
|
|
370
501
|
}
|
|
371
502
|
// ---- what Runtime does differently -----------------------------------
|
|
372
|
-
get pty() {
|
|
373
|
-
throw new NotSupportedError("E2B's pty module", "Use `await sandbox.runtime.terminal({ cols, rows, onData })` for an interactive terminal, or runtime.spawn(cmd, { pty: {} }).");
|
|
374
|
-
}
|
|
375
503
|
get git() {
|
|
376
504
|
throw new NotSupportedError("E2B's git module (deprecated by E2B too)", "Run git with sandbox.commands.run('git ...'); git is installed.");
|
|
377
505
|
}
|
|
@@ -410,18 +538,21 @@ async function resume(runtime, opts) {
|
|
|
410
538
|
const current = state(runtime);
|
|
411
539
|
if (current === "stopped")
|
|
412
540
|
throw new SandboxNotFoundError(`Sandbox ${runtime.id} has ended.`);
|
|
541
|
+
const asked = opts.timeoutMs === undefined ? undefined : Date.now() + opts.timeoutMs;
|
|
413
542
|
if (current === "paused") {
|
|
414
543
|
await guard("sandbox", () => runtime.wake({
|
|
415
544
|
...(opts.timeoutMs === undefined ? {} : { timeoutSeconds: leaseSeconds(opts.timeoutMs) }),
|
|
416
545
|
...request(opts),
|
|
417
546
|
}));
|
|
418
|
-
return;
|
|
419
547
|
}
|
|
420
|
-
if (opts.timeoutMs !== undefined) {
|
|
421
|
-
|
|
548
|
+
else if (opts.timeoutMs !== undefined) {
|
|
549
|
+
checkTimeout(opts.timeoutMs);
|
|
550
|
+
const later = Math.min(asked, Date.now() + KEEP_AHEAD_MS) - Date.parse(runtime.info.expiresAt);
|
|
422
551
|
if (later > 1000)
|
|
423
552
|
await guard("sandbox", () => runtime.extend(Math.ceil(later / 1000), request(opts)));
|
|
424
553
|
}
|
|
554
|
+
if (asked !== undefined && asked > (keepers.get(runtime.id)?.until ?? 0))
|
|
555
|
+
keepUntil(runtime, asked);
|
|
425
556
|
}
|
|
426
557
|
async function stop(runtime, opts) {
|
|
427
558
|
if (state(runtime) === "stopped")
|
|
@@ -430,13 +561,15 @@ async function stop(runtime, opts) {
|
|
|
430
561
|
return true;
|
|
431
562
|
}
|
|
432
563
|
async function extendTo(runtime, timeoutMs, opts) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
if (
|
|
437
|
-
throw new NotSupportedError("
|
|
564
|
+
checkTimeout(timeoutMs);
|
|
565
|
+
const asked = Date.now() + timeoutMs;
|
|
566
|
+
const end = Date.parse(runtime.info.expiresAt);
|
|
567
|
+
if (asked - end < -1000)
|
|
568
|
+
throw new NotSupportedError("Ending a sandbox sooner than its lease (a shorter setTimeout)", "Runtime cannot end a lease early. Call kill() when the work is done.");
|
|
569
|
+
const later = Math.min(asked, Date.now() + KEEP_AHEAD_MS) - end;
|
|
438
570
|
if (later > 1000)
|
|
439
571
|
await guard("sandbox", () => runtime.extend(Math.ceil(later / 1000), request(opts)));
|
|
572
|
+
keepUntil(runtime, asked);
|
|
440
573
|
}
|
|
441
574
|
async function pause(runtime, opts) {
|
|
442
575
|
if (opts.keepMemory === false)
|
|
@@ -451,46 +584,109 @@ async function pause(runtime, opts) {
|
|
|
451
584
|
}
|
|
452
585
|
const RUNNING = ["starting", "running", "resuming"];
|
|
453
586
|
const PAUSED = ["pausing", "paused"];
|
|
454
|
-
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
587
|
+
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
588
|
+
* Runtime filters by state and metadata itself. A template, `startedAfter`,
|
|
589
|
+
* newest-first order or a saved `nextToken` are done here: every match is
|
|
590
|
+
* read once, then served a page at a time. */
|
|
455
591
|
export class SandboxPaginator {
|
|
456
592
|
#client;
|
|
593
|
+
#request;
|
|
457
594
|
#filter;
|
|
595
|
+
#opts;
|
|
596
|
+
#pageSize;
|
|
597
|
+
#local;
|
|
458
598
|
#page;
|
|
599
|
+
#all;
|
|
600
|
+
#offset;
|
|
459
601
|
#hasNext = true;
|
|
460
602
|
constructor(client, opts) {
|
|
461
603
|
this.#client = client;
|
|
604
|
+
this.#request = request(opts);
|
|
605
|
+
this.#opts = opts;
|
|
462
606
|
const query = opts.query ?? {};
|
|
463
|
-
if (
|
|
464
|
-
throw new
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
607
|
+
if (opts.limit !== undefined && (!Number.isSafeInteger(opts.limit) || opts.limit < 1))
|
|
608
|
+
throw new InvalidArgumentError(`limit must be a positive whole number, not ${opts.limit}.`);
|
|
609
|
+
this.#pageSize = opts.limit ?? 100;
|
|
610
|
+
this.#offset = opts.nextToken === undefined ? 0 : tokenOffset(opts.nextToken);
|
|
611
|
+
this.#local =
|
|
612
|
+
query.template !== undefined ||
|
|
613
|
+
query.startedAfter !== undefined ||
|
|
614
|
+
opts.order === "desc" ||
|
|
615
|
+
opts.nextToken !== undefined;
|
|
471
616
|
const states = query.state?.length ? query.state : ["running", "paused"];
|
|
472
617
|
this.#filter = {
|
|
473
618
|
state: states.flatMap((one) => (one === "running" ? [...RUNNING] : [...PAUSED])),
|
|
474
619
|
...(query.metadata && Object.keys(query.metadata).length ? { labels: query.metadata } : {}),
|
|
475
|
-
|
|
620
|
+
limit: this.#local ? 100 : Math.min(this.#pageSize, 100),
|
|
476
621
|
};
|
|
477
622
|
}
|
|
478
623
|
get hasNext() {
|
|
479
624
|
return this.#hasNext;
|
|
480
625
|
}
|
|
626
|
+
/** Where the next page starts; `Sandbox.list({ nextToken })` resumes there. */
|
|
481
627
|
get nextToken() {
|
|
482
|
-
return this.#
|
|
628
|
+
return this.#hasNext && this.#offset > 0 ? `runtime:${this.#offset}` : undefined;
|
|
483
629
|
}
|
|
484
630
|
async nextItems() {
|
|
631
|
+
this.#request.signal?.throwIfAborted();
|
|
485
632
|
if (!this.#hasNext)
|
|
486
633
|
throw new SandboxError("No more items to fetch.");
|
|
634
|
+
if (this.#local) {
|
|
635
|
+
const all = (this.#all ??= await guard("other", () => this.#everyMatch()));
|
|
636
|
+
const items = all.slice(this.#offset, this.#offset + this.#pageSize);
|
|
637
|
+
this.#offset += items.length;
|
|
638
|
+
this.#hasNext = this.#offset < all.length;
|
|
639
|
+
return items;
|
|
640
|
+
}
|
|
487
641
|
const previous = this.#page;
|
|
488
|
-
const page = await guard("other", async () => previous
|
|
642
|
+
const page = await guard("other", async () => previous
|
|
643
|
+
? await previous.next()
|
|
644
|
+
: await this.#client.sandboxes.list(this.#filter, this.#request));
|
|
489
645
|
this.#page = page ?? undefined;
|
|
490
646
|
this.#hasNext = Boolean(page?.hasMore);
|
|
491
|
-
|
|
647
|
+
const items = (page?.data ?? []).map(infoOf);
|
|
648
|
+
this.#offset += items.length;
|
|
649
|
+
return items;
|
|
650
|
+
}
|
|
651
|
+
async #everyMatch() {
|
|
652
|
+
const query = this.#opts.query ?? {};
|
|
653
|
+
const templates = query.template === undefined
|
|
654
|
+
? undefined
|
|
655
|
+
: await templateIds(this.#client, query.template, this.#request);
|
|
656
|
+
const after = query.startedAfter?.getTime();
|
|
657
|
+
const all = [];
|
|
658
|
+
let page = await this.#client.sandboxes.list(this.#filter, this.#request);
|
|
659
|
+
while (page) {
|
|
660
|
+
for (const one of page.data) {
|
|
661
|
+
const info = infoOf(one);
|
|
662
|
+
if (templates && !templates.has(info.templateId))
|
|
663
|
+
continue;
|
|
664
|
+
if (after !== undefined && info.startedAt.getTime() < after)
|
|
665
|
+
continue;
|
|
666
|
+
all.push(info);
|
|
667
|
+
}
|
|
668
|
+
page = page.hasMore ? await page.next() : null;
|
|
669
|
+
}
|
|
670
|
+
const sign = this.#opts.order === "desc" ? -1 : 1;
|
|
671
|
+
return all.sort((a, b) => sign * (a.startedAt.getTime() - b.startedAt.getTime()));
|
|
492
672
|
}
|
|
493
673
|
}
|
|
674
|
+
function tokenOffset(token) {
|
|
675
|
+
const match = /^runtime:(\d+)$/.exec(token);
|
|
676
|
+
if (!match)
|
|
677
|
+
throw new InvalidArgumentError(`"${token}" is not a nextToken this package gave; pass the paginator's own nextToken.`);
|
|
678
|
+
return Number(match[1]);
|
|
679
|
+
}
|
|
680
|
+
/** The template ids a template name stands for: "base" for the stock names,
|
|
681
|
+
* the id itself for a UUID, and every Runtime image of that name. */
|
|
682
|
+
async function templateIds(client, template, options) {
|
|
683
|
+
if (STOCK_TEMPLATES.has(template))
|
|
684
|
+
return new Set(["base"]);
|
|
685
|
+
if (UUID.test(template))
|
|
686
|
+
return new Set([template]);
|
|
687
|
+
const page = await client.images.list({ name: template, limit: 100 }, options);
|
|
688
|
+
return new Set(page.data.map((image) => image.id));
|
|
689
|
+
}
|
|
494
690
|
async function e2bMetrics(runtime, opts) {
|
|
495
691
|
const start = opts.start === undefined ? Date.now() - 15 * 60_000 : new Date(opts.start).getTime();
|
|
496
692
|
const end = opts.end === undefined ? Date.now() : new Date(opts.end).getTime();
|