withruntime 0.11.0 → 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 +8 -9
- package/DAYTONA.md +8 -9
- package/E2B.md +63 -21
- package/README.md +6 -5
- package/README.src.md +6 -5
- package/VERCEL.md +3 -2
- package/compatibility-lock.json +69 -14
- package/dist/api-defaults.d.ts +1 -1
- package/dist/api-defaults.js +1 -1
- package/dist/blaxel/errors.js +4 -4
- package/dist/blaxel/lookup.d.ts +2 -1
- package/dist/blaxel/lookup.js +2 -1
- package/dist/blaxel/sandbox.d.ts +8 -7
- package/dist/blaxel/sandbox.js +16 -12
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +99 -27
- package/dist/client.d.ts +16 -60
- package/dist/client.js +14 -103
- package/dist/compat/files.js +5 -1
- package/dist/compat/lease.d.ts +1 -0
- package/dist/compat/lease.js +14 -8
- package/dist/credentials.d.ts +9 -0
- package/dist/credentials.js +26 -4
- package/dist/daytona/daytona.d.ts +1 -2
- package/dist/daytona/daytona.js +30 -14
- package/dist/daytona/sandbox.d.ts +9 -4
- package/dist/daytona/sandbox.js +24 -5
- 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 +25 -12
- package/dist/e2b/sandbox.js +196 -165
- 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 +10 -4
- package/dist/errors.js +18 -10
- package/dist/identity.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/machine.d.ts +180 -0
- package/dist/machine.js +504 -0
- package/dist/modal/index.js +6 -2
- package/dist/network-cli.js +7 -7
- package/dist/openai-agents/index.d.ts +1 -1
- package/dist/openai-agents/index.js +1 -1
- package/dist/products/browser.d.ts +2 -1
- package/dist/products/browser.js +9 -19
- package/dist/products/desktop.d.ts +2 -1
- package/dist/products/desktop.js +5 -30
- package/dist/products/jobs.d.ts +6 -5
- package/dist/products/jobs.js +6 -3
- package/dist/products/limits.d.ts +4 -10
- package/dist/products/mcp.d.ts +5 -1
- package/dist/products/mcp.js +29 -8
- package/dist/products/network-products.d.ts +1 -1
- package/dist/products/network-products.js +1 -1
- package/dist/products/network.d.ts +1 -1
- package/dist/products/observability.d.ts +1 -1
- package/dist/products/private-network.d.ts +1 -1
- package/dist/products/secrets.d.ts +3 -3
- package/dist/products/switching.d.ts +2 -3
- package/dist/runloop/index.js +6 -2
- package/dist/sandbox.d.ts +7 -84
- package/dist/sandbox.js +58 -345
- package/dist/tar.d.ts +2 -0
- package/dist/tar.js +74 -16
- package/dist/transport.d.ts +36 -8
- package/dist/transport.js +174 -37
- package/dist/types.d.ts +102 -12
- package/dist/vercel/filesystem.js +3 -1
- package/dist/vercel/sandbox.d.ts +18 -12
- package/dist/vercel/sandbox.js +27 -15
- package/dist/wait.d.ts +12 -0
- package/dist/wait.js +47 -0
- package/package.json +1 -1
package/dist/e2b/sandbox.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clientFor, request, } from "./client.js";
|
|
1
|
+
import { clientFor, keySource, request, } from "./client.js";
|
|
2
2
|
import { Commands } from "./commands.js";
|
|
3
3
|
import { guard, InvalidArgumentError, NotSupportedError, PublicPreviewNotAllowedError, SandboxError, SandboxNotFoundError, TemplateError, } from "./errors.js";
|
|
4
4
|
import { Filesystem } from "./filesystem.js";
|
|
@@ -9,13 +9,22 @@ import { Pty } from "./pty.js";
|
|
|
9
9
|
/** E2B's default machine: 2 vCPU and 512 MiB (docs.e2b.dev/billing, checked
|
|
10
10
|
* 23 September 2026). Pass `runtime: { create: { memoryMiB } }` for another. */
|
|
11
11
|
export const DEFAULT_SHAPE = { vcpu: 2, memoryMiB: 512 };
|
|
12
|
-
/** E2B's default sandbox timeout, 300 s.
|
|
12
|
+
/** E2B's default sandbox timeout, 300 s. A sandbox created without timeoutMs
|
|
13
|
+
* gets it, and is paused when it runs out rather than killed, since nobody
|
|
14
|
+
* asked for it to end: nothing in it is lost, and the next call wakes it. On
|
|
15
|
+
* a pilot account the server keeps every sandbox running instead (0254). */
|
|
13
16
|
export const DEFAULT_TIMEOUT_MS = 300_000;
|
|
14
|
-
/** Runtime's
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/** Runtime's time limit bounds (MIN_TIMEOUT_SECONDS and MAX_TIMEOUT_SECONDS
|
|
18
|
+
* in the API): a minute to 24 hours, E2B's longest (its Pro plan). The server
|
|
19
|
+
* keeps the deadline: nothing in this process has to stay running for it. */
|
|
20
|
+
const MIN_TIMEOUT_SECONDS = 60;
|
|
18
21
|
const LONGEST_TIMEOUT_MS = 86_400_000;
|
|
22
|
+
/** Makes E2B's home lead to Runtime's unless the image has its own, and
|
|
23
|
+
* prints "same" when /home/user is then /workspace. */
|
|
24
|
+
const HOME_LINK = "[ -e /home/user ] || sudo ln -s /workspace /home/user || exit 1; if [ /home/user -ef /workspace ]; then echo same; fi";
|
|
25
|
+
/** The label that keeps the template a sandbox was made from, so getInfo
|
|
26
|
+
* and list name it whichever client asks; it is not shown in `metadata`. */
|
|
27
|
+
export const TEMPLATE_LABEL = "e2b-template";
|
|
19
28
|
/** E2B template names that mean "the stock environment": Runtime's default image. */
|
|
20
29
|
const STOCK_TEMPLATES = new Set(["base", "code-interpreter-v1", "code-interpreter"]);
|
|
21
30
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -26,17 +35,17 @@ function checkForkCount(count) {
|
|
|
26
35
|
if (count !== undefined && (!Number.isInteger(count) || count < 1 || count > MAX_FORK_COUNT))
|
|
27
36
|
throw new InvalidArgumentError(`count must be an integer between 1 and ${MAX_FORK_COUNT}`);
|
|
28
37
|
}
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
31
|
-
function
|
|
38
|
+
/** Runtime's time limit for `timeoutMs`, in whole seconds: at least a
|
|
39
|
+
* minute (with a warning once), at most 24 hours. */
|
|
40
|
+
function timeoutSeconds(timeoutMs) {
|
|
32
41
|
checkTimeout(timeoutMs);
|
|
33
|
-
const seconds = Math.
|
|
34
|
-
if (seconds <
|
|
42
|
+
const seconds = Math.ceil(timeoutMs / 1000);
|
|
43
|
+
if (seconds < MIN_TIMEOUT_SECONDS) {
|
|
35
44
|
if (!warnedShortLease && typeof process !== "undefined") {
|
|
36
45
|
warnedShortLease = true;
|
|
37
|
-
process.emitWarning(`timeoutMs ${timeoutMs} is under Runtime's shortest
|
|
46
|
+
process.emitWarning(`timeoutMs ${timeoutMs} is under Runtime's shortest time limit; the sandbox gets 60 s. Call kill() when done.`, { code: "RUNTIME_E2B_SHORT_TIMEOUT" });
|
|
38
47
|
}
|
|
39
|
-
return
|
|
48
|
+
return MIN_TIMEOUT_SECONDS;
|
|
40
49
|
}
|
|
41
50
|
return seconds;
|
|
42
51
|
}
|
|
@@ -46,86 +55,18 @@ function checkTimeout(timeoutMs) {
|
|
|
46
55
|
if (timeoutMs > LONGEST_TIMEOUT_MS)
|
|
47
56
|
throw new InvalidArgumentError(`timeoutMs is at most 24 hours (86_400_000 ms), as on E2B, not ${timeoutMs}.`);
|
|
48
57
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
}
|
|
122
|
-
function onLeaseEnd(lifecycle) {
|
|
58
|
+
/** What the server does when the time limit runs out. E2B kills: a caller
|
|
59
|
+
* that asked for an end (a timeoutMs, or onTimeout "kill") gets a delete, as
|
|
60
|
+
* E2B's kill destroys the sandbox. One that asked for nothing gets E2B's
|
|
61
|
+
* 300 s default as a pause, which keeps everything, since a sandbox nobody
|
|
62
|
+
* asked to end is never ended. */
|
|
63
|
+
function onTimeout(lifecycle, timeoutAsked) {
|
|
123
64
|
if (!lifecycle)
|
|
124
|
-
return "
|
|
65
|
+
return timeoutAsked ? "delete" : "pause";
|
|
125
66
|
const action = typeof lifecycle.onTimeout === "string" ? lifecycle.onTimeout : lifecycle.onTimeout.action;
|
|
126
67
|
if (typeof lifecycle.onTimeout === "object" && lifecycle.onTimeout.keepMemory === false)
|
|
127
68
|
throw new NotSupportedError("A files-only pause (keepMemory: false)", "Runtime's pause keeps memory and files; leave keepMemory out.");
|
|
128
|
-
return action === "pause" ? "pause" : "
|
|
69
|
+
return action === "pause" ? "pause" : "delete";
|
|
129
70
|
}
|
|
130
71
|
function refuseCreate(opts) {
|
|
131
72
|
const refusals = [
|
|
@@ -150,8 +91,16 @@ function refuseCreate(opts) {
|
|
|
150
91
|
if (opts[field] !== undefined)
|
|
151
92
|
throw new NotSupportedError(feature, alternative);
|
|
152
93
|
}
|
|
94
|
+
/** The Runtime image name an E2B template name stands for: E2B's
|
|
95
|
+
* `team/name:tag` without the team, since a Runtime account's images are its
|
|
96
|
+
* own. `name`, `name:tag` and `name@version` are Runtime image names too. */
|
|
97
|
+
export function imageNameFor(template) {
|
|
98
|
+
const slash = template.lastIndexOf("/");
|
|
99
|
+
return slash < 0 ? template : template.slice(slash + 1);
|
|
100
|
+
}
|
|
153
101
|
/** Where a template sends the create: the stock image, a Runtime image by
|
|
154
|
-
* name or id, or a Runtime snapshot by id.
|
|
102
|
+
* name (with its tag or version) or id, or a Runtime snapshot by id. A name
|
|
103
|
+
* is resolved by the create itself, in the same request. */
|
|
155
104
|
async function resolveTemplate(client, template, options) {
|
|
156
105
|
options.signal?.throwIfAborted();
|
|
157
106
|
if (STOCK_TEMPLATES.has(template))
|
|
@@ -167,16 +116,36 @@ async function resolveTemplate(client, template, options) {
|
|
|
167
116
|
}
|
|
168
117
|
return { snapshot: template };
|
|
169
118
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
119
|
+
return { image: imageNameFor(template) };
|
|
120
|
+
}
|
|
121
|
+
/** The labels a create sends: E2B's metadata, and the template it was made
|
|
122
|
+
* from, while there is room for it among Runtime's 32 labels. */
|
|
123
|
+
function labelsFor(metadata, template) {
|
|
124
|
+
const labels = { ...metadata };
|
|
125
|
+
if (!STOCK_TEMPLATES.has(template) && Object.keys(labels).length < 32 && template.length <= 256)
|
|
126
|
+
labels[TEMPLATE_LABEL] = template;
|
|
127
|
+
return Object.keys(labels).length ? { labels } : {};
|
|
128
|
+
}
|
|
129
|
+
/** " in the account …", for an error a key of another account explains: the
|
|
130
|
+
* account's name and where its key came from. Nothing when the lookup fails. */
|
|
131
|
+
async function accountOf(client, opts) {
|
|
132
|
+
const me = await client.me({ timeoutMs: 5_000 }).catch(() => undefined);
|
|
133
|
+
if (!me)
|
|
134
|
+
return undefined;
|
|
135
|
+
const source = opts.runtime?.client ? "" : ` (key: ${keySource(opts.apiKey)})`;
|
|
136
|
+
return ` in the account "${me.orgName || me.orgId}"${source}`;
|
|
137
|
+
}
|
|
138
|
+
/** The create's refusal of a template no Runtime image answers to, as E2B's
|
|
139
|
+
* TemplateError, saying how to build it. */
|
|
140
|
+
function templateMissing(template, cause, account) {
|
|
141
|
+
const name = imageNameFor(template).replace(/[:@].*$/, "");
|
|
142
|
+
const error = new TemplateError(`No Runtime image is named "${imageNameFor(template)}"${account ?? ""}. E2B templates do not run on Runtime; build the same ` +
|
|
175
143
|
`environment as a Runtime image with that name and this call starts from it: ` +
|
|
176
|
-
`\`npx withruntime image build --dockerfile e2b.Dockerfile --name ${
|
|
177
|
-
`runtime.images.build({ name: "${
|
|
144
|
+
`\`npx withruntime image build --dockerfile e2b.Dockerfile --name ${name}\`, or ` +
|
|
145
|
+
`runtime.images.build({ name: "${name}", dockerfile }).`);
|
|
178
146
|
error.code = "template_not_found";
|
|
179
|
-
|
|
147
|
+
error.cause = cause;
|
|
148
|
+
return error;
|
|
180
149
|
}
|
|
181
150
|
function state(runtime) {
|
|
182
151
|
const current = runtime.state;
|
|
@@ -186,27 +155,57 @@ function state(runtime) {
|
|
|
186
155
|
return "stopped";
|
|
187
156
|
return "running";
|
|
188
157
|
}
|
|
189
|
-
|
|
158
|
+
/** Whether a paused sandbox is, to code written for E2B, still running:
|
|
159
|
+
* Runtime paused it itself for being idle (E2B never does) and the next
|
|
160
|
+
* call wakes it, so every E2B call on it works as on a running one. */
|
|
161
|
+
function idleAsleep(runtime) {
|
|
190
162
|
const info = runtime.info;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
: "base";
|
|
163
|
+
return state(runtime) === "paused" && info.stopReason === "idle" && info.autoWake !== false;
|
|
164
|
+
}
|
|
165
|
+
/** The sandbox's state as E2B would say it. */
|
|
166
|
+
function e2bState(runtime) {
|
|
196
167
|
const current = state(runtime);
|
|
168
|
+
if (current === "running" || idleAsleep(runtime))
|
|
169
|
+
return "running";
|
|
170
|
+
return "paused";
|
|
171
|
+
}
|
|
172
|
+
/** E2B's endAt: when the sandbox ends by itself. One that never will (no
|
|
173
|
+
* time limit, as a persistent or a pilot's sandbox) reads 24 hours ahead, the
|
|
174
|
+
* furthest an E2B sandbox's end may be, so code that waits until the end, or
|
|
175
|
+
* extends near it, behaves as for the longest E2B sandbox: a JavaScript timer
|
|
176
|
+
* set past 24.8 days would fire at once. (5 October 2026: it read where the
|
|
177
|
+
* sandbox was paid up to, minutes ahead, so a pilot's looked about to end.) */
|
|
178
|
+
function endOf(runtime) {
|
|
179
|
+
const { endsAt, expiresAt } = runtime.info;
|
|
180
|
+
if (endsAt)
|
|
181
|
+
return new Date(endsAt);
|
|
182
|
+
if (endsAt === null && e2bState(runtime) === "running")
|
|
183
|
+
return new Date(Date.now() + LONGEST_TIMEOUT_MS);
|
|
184
|
+
return new Date(expiresAt);
|
|
185
|
+
}
|
|
186
|
+
function infoOf(runtime) {
|
|
187
|
+
const info = runtime.info;
|
|
188
|
+
const { [TEMPLATE_LABEL]: template, ...metadata } = info.labels ?? {};
|
|
189
|
+
const templateId = template ??
|
|
190
|
+
(typeof info.image === "string"
|
|
191
|
+
? info.image
|
|
192
|
+
: typeof info.snapshot === "string"
|
|
193
|
+
? info.snapshot
|
|
194
|
+
: "base");
|
|
195
|
+
const action = info.onTimeout ?? info.onLeaseEnd;
|
|
197
196
|
return {
|
|
198
197
|
sandboxId: info.id,
|
|
199
198
|
templateId,
|
|
200
199
|
...(info.name ? { name: info.name } : {}),
|
|
201
|
-
metadata
|
|
200
|
+
metadata,
|
|
202
201
|
startedAt: new Date(info.createdAt),
|
|
203
|
-
endAt:
|
|
204
|
-
state:
|
|
202
|
+
endAt: endOf(runtime),
|
|
203
|
+
state: e2bState(runtime),
|
|
205
204
|
cpuCount: info.vcpu,
|
|
206
205
|
memoryMB: info.memoryMiB,
|
|
207
206
|
envdVersion: "runtime",
|
|
208
207
|
lifecycle: {
|
|
209
|
-
onTimeout:
|
|
208
|
+
onTimeout: action === "pause" ? "pause" : "kill",
|
|
210
209
|
autoResume: info.autoWake === true,
|
|
211
210
|
},
|
|
212
211
|
};
|
|
@@ -226,6 +225,8 @@ export class Sandbox {
|
|
|
226
225
|
pty;
|
|
227
226
|
#client;
|
|
228
227
|
#runtime;
|
|
228
|
+
#killed = false;
|
|
229
|
+
/** The /home/user link, once asked for: whether /home/user leads to /workspace. */
|
|
229
230
|
#home;
|
|
230
231
|
/** Ports shared as public previews, by port: the one share each asked for. */
|
|
231
232
|
#shares = new Map();
|
|
@@ -236,6 +237,7 @@ export class Sandbox {
|
|
|
236
237
|
this.#client = client;
|
|
237
238
|
const ctx = {
|
|
238
239
|
ensureHome: (text, options) => this.#ensureHome(text, options),
|
|
240
|
+
homeIsWorkspace: () => (this.#home ?? this.#linkHome()).catch(() => false),
|
|
239
241
|
requestTimeoutMs: this.requestTimeoutMs,
|
|
240
242
|
};
|
|
241
243
|
// A getter, so the modules see the sandbox as it is after each refresh.
|
|
@@ -278,21 +280,32 @@ export class Sandbox {
|
|
|
278
280
|
});
|
|
279
281
|
});
|
|
280
282
|
};
|
|
283
|
+
const done = (home) => wait(home.then(() => undefined));
|
|
281
284
|
if (this.#home || !text?.includes("/home/user"))
|
|
282
|
-
return
|
|
285
|
+
return done(this.#home ?? Promise.resolve(false));
|
|
286
|
+
return done(this.#linkHome(signal));
|
|
287
|
+
}
|
|
288
|
+
/** Links /home/user to /workspace unless something is already there, and
|
|
289
|
+
* says whether /home/user then leads to /workspace: an image may have its
|
|
290
|
+
* own /home/user, and E2B's paths are shown only where they are true. */
|
|
291
|
+
#linkHome(signal) {
|
|
283
292
|
const pending = (async () => {
|
|
284
|
-
const result = await guard("sandbox", () => this.#runtime.exec(
|
|
285
|
-
if (result.exitCode !== 0
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
const result = await guard("sandbox", () => this.#runtime.exec(HOME_LINK, { signal }));
|
|
294
|
+
if (result.exitCode !== 0) {
|
|
295
|
+
if (typeof process !== "undefined")
|
|
296
|
+
process.emitWarning(`Could not link /home/user to /workspace: ${result.stderr.trim()}`, {
|
|
297
|
+
code: "RUNTIME_E2B_HOME",
|
|
298
|
+
});
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
return result.stdout.trim() === "same";
|
|
289
302
|
})();
|
|
290
303
|
this.#home = pending;
|
|
291
304
|
pending.catch(() => {
|
|
292
305
|
if (this.#home === pending)
|
|
293
306
|
this.#home = undefined;
|
|
294
307
|
});
|
|
295
|
-
return
|
|
308
|
+
return pending;
|
|
296
309
|
}
|
|
297
310
|
static async create(templateOrOpts, maybeOpts) {
|
|
298
311
|
const opts = (typeof templateOrOpts === "string" ? maybeOpts : templateOrOpts) ?? {};
|
|
@@ -301,14 +314,15 @@ export class Sandbox {
|
|
|
301
314
|
options.signal?.throwIfAborted();
|
|
302
315
|
refuseCreate(opts);
|
|
303
316
|
const client = clientFor(opts);
|
|
304
|
-
|
|
317
|
+
// The server keeps the deadline, up to 24 hours (0381). Without one asked
|
|
318
|
+
// for, E2B's 300 s ends in a pause, never a delete.
|
|
305
319
|
const input = {
|
|
306
|
-
timeoutSeconds:
|
|
307
|
-
onLeaseEnd:
|
|
320
|
+
timeoutSeconds: timeoutSeconds(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
321
|
+
onLeaseEnd: onTimeout(opts.lifecycle, opts.timeoutMs !== undefined),
|
|
308
322
|
// With a lifecycle, E2B resumes a paused sandbox on traffic only when
|
|
309
323
|
// asked (autoResume); Runtime's automatic wake is the same thing (0093).
|
|
310
324
|
...(opts.lifecycle ? { autoWake: opts.lifecycle.autoResume === true } : {}),
|
|
311
|
-
...(opts.metadata
|
|
325
|
+
...labelsFor(opts.metadata, template),
|
|
312
326
|
// Runtime keeps them with the sandbox: every command, terminal and
|
|
313
327
|
// interpreter in it gets them, from this client or any other.
|
|
314
328
|
...(opts.envs && Object.keys(opts.envs).length ? { env: opts.envs } : {}),
|
|
@@ -323,8 +337,11 @@ export class Sandbox {
|
|
|
323
337
|
...source,
|
|
324
338
|
...opts.runtime?.create,
|
|
325
339
|
};
|
|
326
|
-
const runtime = await guard("sandbox", () => client.sandboxes.create(create, options))
|
|
327
|
-
|
|
340
|
+
const runtime = await guard("sandbox", () => client.sandboxes.create(create, options)).catch(async (error) => {
|
|
341
|
+
if (error.code === "image_not_found")
|
|
342
|
+
throw templateMissing(template, error, await accountOf(client, opts));
|
|
343
|
+
throw error;
|
|
344
|
+
});
|
|
328
345
|
return new this(runtime, client, opts.requestTimeoutMs);
|
|
329
346
|
}
|
|
330
347
|
/** Connects to a sandbox by id, waking it if it is paused, as E2B does. A
|
|
@@ -346,13 +363,17 @@ export class Sandbox {
|
|
|
346
363
|
return new SandboxPaginator(clientFor(opts), opts);
|
|
347
364
|
}
|
|
348
365
|
// ---- lifecycle --------------------------------------------------------
|
|
349
|
-
/**
|
|
366
|
+
/** Deletes the sandbox for good, as E2B's kill destroys it: its disk,
|
|
367
|
+
* memory and shared ports go. False when it was not found or was already
|
|
368
|
+
* deleted. */
|
|
350
369
|
static async kill(sandboxId, opts = {}) {
|
|
351
370
|
const client = clientFor(opts);
|
|
352
|
-
stopKeeping(sandboxId);
|
|
353
371
|
try {
|
|
372
|
+
// Read first: a delete answers the same however often it is sent, and
|
|
373
|
+
// E2B says false for one already gone.
|
|
354
374
|
const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
|
|
355
|
-
|
|
375
|
+
await guard("sandbox", () => runtime.delete(request(opts)));
|
|
376
|
+
return true;
|
|
356
377
|
}
|
|
357
378
|
catch (error) {
|
|
358
379
|
if (error instanceof SandboxNotFoundError)
|
|
@@ -361,9 +382,12 @@ export class Sandbox {
|
|
|
361
382
|
}
|
|
362
383
|
}
|
|
363
384
|
async kill(opts = {}) {
|
|
364
|
-
|
|
385
|
+
if (this.#killed)
|
|
386
|
+
return false;
|
|
365
387
|
try {
|
|
366
|
-
|
|
388
|
+
await guard("sandbox", () => this.#runtime.delete(request(opts)));
|
|
389
|
+
this.#killed = true;
|
|
390
|
+
return true;
|
|
367
391
|
}
|
|
368
392
|
catch (error) {
|
|
369
393
|
if (error instanceof SandboxNotFoundError)
|
|
@@ -371,9 +395,11 @@ export class Sandbox {
|
|
|
371
395
|
throw error;
|
|
372
396
|
}
|
|
373
397
|
}
|
|
374
|
-
/** Sets the sandbox to end `timeoutMs` from now, up to 24 hours
|
|
375
|
-
*
|
|
376
|
-
*
|
|
398
|
+
/** Sets the sandbox to end `timeoutMs` from now, up to 24 hours, as E2B
|
|
399
|
+
* does; the server keeps the deadline. Runtime cannot bring an end
|
|
400
|
+
* sooner, so that is refused. A sandbox with no time limit (one on a pilot
|
|
401
|
+
* account, kept running by the server) has no end to move, and nothing
|
|
402
|
+
* changes. */
|
|
377
403
|
static async setTimeout(sandboxId, timeoutMs, opts = {}) {
|
|
378
404
|
const client = clientFor(opts);
|
|
379
405
|
const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
|
|
@@ -391,6 +417,9 @@ export class Sandbox {
|
|
|
391
417
|
await guard("sandbox", () => this.#runtime.refresh(request(opts)));
|
|
392
418
|
return infoOf(this.#runtime);
|
|
393
419
|
}
|
|
420
|
+
/** Whether the sandbox answers calls, as E2B's health check says: true
|
|
421
|
+
* while it runs, and while Runtime has paused it for being idle, since the
|
|
422
|
+
* next call wakes it. False once paused on request, ended or deleted. */
|
|
394
423
|
async isRunning(opts = {}) {
|
|
395
424
|
try {
|
|
396
425
|
await guard("sandbox", () => this.#runtime.refresh(request(opts)));
|
|
@@ -400,7 +429,7 @@ export class Sandbox {
|
|
|
400
429
|
return false;
|
|
401
430
|
throw error;
|
|
402
431
|
}
|
|
403
|
-
return this.#runtime.state === "running";
|
|
432
|
+
return this.#runtime.state === "running" || idleAsleep(this.#runtime);
|
|
404
433
|
}
|
|
405
434
|
/** Pauses the sandbox, keeping memory and files. False when it was paused. */
|
|
406
435
|
static async pause(sandboxId, opts = {}) {
|
|
@@ -471,7 +500,7 @@ export class Sandbox {
|
|
|
471
500
|
getHost(port) {
|
|
472
501
|
if (!Number.isInteger(port) || port < 1 || port > 65_535)
|
|
473
502
|
throw new InvalidArgumentError(`port must be a whole number from 1 to 65535, not ${port}.`);
|
|
474
|
-
// A
|
|
503
|
+
// A sandbox without credit shares ports privately: a host alone would answer 404 or
|
|
475
504
|
// 401 to everyone. Say so now, with the address that works.
|
|
476
505
|
if (this.#runtime.info.funding === "trial")
|
|
477
506
|
throw new PublicPreviewNotAllowedError(this.sandboxId, port);
|
|
@@ -538,38 +567,38 @@ async function resume(runtime, opts) {
|
|
|
538
567
|
const current = state(runtime);
|
|
539
568
|
if (current === "stopped")
|
|
540
569
|
throw new SandboxNotFoundError(`Sandbox ${runtime.id} has ended.`);
|
|
541
|
-
const asked = opts.timeoutMs === undefined ? undefined : Date.now() + opts.timeoutMs;
|
|
542
570
|
if (current === "paused") {
|
|
543
571
|
await guard("sandbox", () => runtime.wake({
|
|
544
|
-
...(opts.timeoutMs === undefined ? {} : { timeoutSeconds:
|
|
572
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutSeconds: timeoutSeconds(opts.timeoutMs) }),
|
|
545
573
|
...request(opts),
|
|
546
574
|
}));
|
|
547
575
|
}
|
|
548
576
|
else if (opts.timeoutMs !== undefined) {
|
|
549
577
|
checkTimeout(opts.timeoutMs);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
578
|
+
// A running one's end moves later, never sooner, as E2B's connect.
|
|
579
|
+
const later = secondsLater(runtime, opts.timeoutMs);
|
|
580
|
+
if (later > 1)
|
|
581
|
+
await guard("sandbox", () => runtime.extend(Math.ceil(later), request(opts)));
|
|
553
582
|
}
|
|
554
|
-
if (asked !== undefined && asked > (keepers.get(runtime.id)?.until ?? 0))
|
|
555
|
-
keepUntil(runtime, asked);
|
|
556
583
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
584
|
+
/** How many seconds past its current end `now + timeoutMs` is; 0 for a
|
|
585
|
+
* sandbox with no time limit, which has no end to move. A second either way
|
|
586
|
+
* is the clocks and the trip, not a new end. */
|
|
587
|
+
function secondsLater(runtime, timeoutMs) {
|
|
588
|
+
if (runtime.info.endsAt === null)
|
|
589
|
+
return 0;
|
|
590
|
+
const end = Date.parse(runtime.info.endsAt ?? runtime.info.expiresAt);
|
|
591
|
+
return (Date.now() + timeoutMs - end) / 1000;
|
|
562
592
|
}
|
|
563
593
|
async function extendTo(runtime, timeoutMs, opts) {
|
|
564
594
|
checkTimeout(timeoutMs);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (later >
|
|
571
|
-
await guard("sandbox", () => runtime.extend(Math.ceil(later
|
|
572
|
-
keepUntil(runtime, asked);
|
|
595
|
+
if (runtime.info.endsAt === null)
|
|
596
|
+
return;
|
|
597
|
+
const later = secondsLater(runtime, timeoutMs);
|
|
598
|
+
if (later < -1)
|
|
599
|
+
throw new NotSupportedError("Ending a sandbox sooner than its time limit (a shorter setTimeout)", "Runtime cannot bring a time limit forward. Call kill() when the work is done.");
|
|
600
|
+
if (later > 1)
|
|
601
|
+
await guard("sandbox", () => runtime.extend(Math.ceil(later), request(opts)));
|
|
573
602
|
}
|
|
574
603
|
async function pause(runtime, opts) {
|
|
575
604
|
if (opts.keepMemory === false)
|
|
@@ -595,6 +624,10 @@ export class SandboxPaginator {
|
|
|
595
624
|
#opts;
|
|
596
625
|
#pageSize;
|
|
597
626
|
#local;
|
|
627
|
+
/** One E2B state asked for alone: Runtime's states are read together and
|
|
628
|
+
* sorted here, since a sandbox Runtime paused for being idle is running
|
|
629
|
+
* to E2B. */
|
|
630
|
+
#only;
|
|
598
631
|
#page;
|
|
599
632
|
#all;
|
|
600
633
|
#offset;
|
|
@@ -613,9 +646,10 @@ export class SandboxPaginator {
|
|
|
613
646
|
query.startedAfter !== undefined ||
|
|
614
647
|
opts.order === "desc" ||
|
|
615
648
|
opts.nextToken !== undefined;
|
|
616
|
-
const
|
|
649
|
+
const asked = new Set(query.state?.length ? query.state : ["running", "paused"]);
|
|
650
|
+
this.#only = asked.size === 1 ? [...asked][0] : undefined;
|
|
617
651
|
this.#filter = {
|
|
618
|
-
state:
|
|
652
|
+
state: [...RUNNING, ...PAUSED],
|
|
619
653
|
...(query.metadata && Object.keys(query.metadata).length ? { labels: query.metadata } : {}),
|
|
620
654
|
limit: this.#local ? 100 : Math.min(this.#pageSize, 100),
|
|
621
655
|
};
|
|
@@ -644,21 +678,28 @@ export class SandboxPaginator {
|
|
|
644
678
|
: await this.#client.sandboxes.list(this.#filter, this.#request));
|
|
645
679
|
this.#page = page ?? undefined;
|
|
646
680
|
this.#hasNext = Boolean(page?.hasMore);
|
|
647
|
-
const items = (page?.data ?? []).map(infoOf);
|
|
681
|
+
const items = (page?.data ?? []).map(infoOf).filter((info) => this.#wanted(info));
|
|
648
682
|
this.#offset += items.length;
|
|
649
683
|
return items;
|
|
650
684
|
}
|
|
685
|
+
#wanted(info) {
|
|
686
|
+
return this.#only === undefined || info.state === this.#only;
|
|
687
|
+
}
|
|
651
688
|
async #everyMatch() {
|
|
652
689
|
const query = this.#opts.query ?? {};
|
|
653
690
|
const templates = query.template === undefined
|
|
654
691
|
? undefined
|
|
655
|
-
:
|
|
692
|
+
: STOCK_TEMPLATES.has(query.template)
|
|
693
|
+
? new Set(["base", ...STOCK_TEMPLATES])
|
|
694
|
+
: new Set([query.template]);
|
|
656
695
|
const after = query.startedAfter?.getTime();
|
|
657
696
|
const all = [];
|
|
658
697
|
let page = await this.#client.sandboxes.list(this.#filter, this.#request);
|
|
659
698
|
while (page) {
|
|
660
699
|
for (const one of page.data) {
|
|
661
700
|
const info = infoOf(one);
|
|
701
|
+
if (!this.#wanted(info))
|
|
702
|
+
continue;
|
|
662
703
|
if (templates && !templates.has(info.templateId))
|
|
663
704
|
continue;
|
|
664
705
|
if (after !== undefined && info.startedAt.getTime() < after)
|
|
@@ -677,16 +718,6 @@ function tokenOffset(token) {
|
|
|
677
718
|
throw new InvalidArgumentError(`"${token}" is not a nextToken this package gave; pass the paginator's own nextToken.`);
|
|
678
719
|
return Number(match[1]);
|
|
679
720
|
}
|
|
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
|
-
}
|
|
690
721
|
async function e2bMetrics(runtime, opts) {
|
|
691
722
|
const start = opts.start === undefined ? Date.now() - 15 * 60_000 : new Date(opts.start).getTime();
|
|
692
723
|
const end = opts.end === undefined ? Date.now() : new Date(opts.end).getTime();
|
|
@@ -17,3 +17,22 @@ export declare const waitForFile: Unsupported;
|
|
|
17
17
|
export declare const waitForTimeout: Unsupported;
|
|
18
18
|
export declare const Volume: Unsupported;
|
|
19
19
|
export declare const Secret: Unsupported;
|
|
20
|
+
export declare const SecretPaginator: Unsupported;
|
|
21
|
+
export declare const Git: Unsupported;
|
|
22
|
+
export declare const ReadyCmd: Unsupported;
|
|
23
|
+
export declare const LogEntry: Unsupported;
|
|
24
|
+
export declare const LogEntryStart: Unsupported;
|
|
25
|
+
export declare const LogEntryEnd: Unsupported;
|
|
26
|
+
export declare const defaultBuildLogger: Unsupported;
|
|
27
|
+
export declare const ConnectionConfig: Unsupported;
|
|
28
|
+
export declare const ApiClient: Unsupported;
|
|
29
|
+
export declare const getSignature: Unsupported;
|
|
30
|
+
/** E2B's "every address" network selector, as E2B defines it. */
|
|
31
|
+
export declare const ALL_TRAFFIC = "0.0.0.0/0";
|
|
32
|
+
/** E2B's volume entry types, as E2B defines them. */
|
|
33
|
+
export declare enum VolumeFileType {
|
|
34
|
+
UNKNOWN = "unknown",
|
|
35
|
+
FILE = "file",
|
|
36
|
+
DIRECTORY = "directory",
|
|
37
|
+
SYMLINK = "symlink"
|
|
38
|
+
}
|
package/dist/e2b/unsupported.js
CHANGED
|
@@ -25,3 +25,24 @@ export const waitForFile = unsupportedExport("waitForFile (a template ready chec
|
|
|
25
25
|
export const waitForTimeout = unsupportedExport("waitForTimeout (a template ready check)", TEMPLATES);
|
|
26
26
|
export const Volume = unsupportedExport("Volume", "Use runtime.volumes (Runtime volumes) and mount one with runtime: { create: { volumes: [{ volumeId, path }] } }.");
|
|
27
27
|
export const Secret = unsupportedExport("Secret", "Use a Runtime secret: `npx withruntime secrets set NAME --host api.example.com`. The sandbox sees a placeholder, and the egress proxy adds the value on HTTPS to that host.");
|
|
28
|
+
export const SecretPaginator = unsupportedExport("SecretPaginator", "List Runtime secrets with `npx withruntime secrets ls` or runtime.secrets.list().");
|
|
29
|
+
export const Git = unsupportedExport("git module (deprecated by E2B too)", "Run git with sandbox.commands.run('git ...'); git is installed.");
|
|
30
|
+
export const ReadyCmd = unsupportedExport("ReadyCmd (a template ready check)", TEMPLATES);
|
|
31
|
+
export const LogEntry = unsupportedExport("LogEntry (template build logs)", TEMPLATES);
|
|
32
|
+
export const LogEntryStart = unsupportedExport("LogEntryStart (template build logs)", TEMPLATES);
|
|
33
|
+
export const LogEntryEnd = unsupportedExport("LogEntryEnd (template build logs)", TEMPLATES);
|
|
34
|
+
export const defaultBuildLogger = unsupportedExport("defaultBuildLogger (template build logs)", TEMPLATES);
|
|
35
|
+
const ENDPOINTS = "This package talks only to Runtime's API through the sandbox's own methods; sandbox.runtime is the Runtime sandbox underneath.";
|
|
36
|
+
export const ConnectionConfig = unsupportedExport("ConnectionConfig", ENDPOINTS);
|
|
37
|
+
export const ApiClient = unsupportedExport("ApiClient (E2B's REST client)", ENDPOINTS);
|
|
38
|
+
export const getSignature = unsupportedExport("getSignature (signed file URLs)", "Use sandbox.files.read and sandbox.files.write.");
|
|
39
|
+
/** E2B's "every address" network selector, as E2B defines it. */
|
|
40
|
+
export const ALL_TRAFFIC = "0.0.0.0/0";
|
|
41
|
+
/** E2B's volume entry types, as E2B defines them. */
|
|
42
|
+
export var VolumeFileType;
|
|
43
|
+
(function (VolumeFileType) {
|
|
44
|
+
VolumeFileType["UNKNOWN"] = "unknown";
|
|
45
|
+
VolumeFileType["FILE"] = "file";
|
|
46
|
+
VolumeFileType["DIRECTORY"] = "directory";
|
|
47
|
+
VolumeFileType["SYMLINK"] = "symlink";
|
|
48
|
+
})(VolumeFileType || (VolumeFileType = {}));
|
package/dist/e2b/users.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ export declare function runAs(ctx: SandboxContext, user: Username | undefined, o
|
|
|
9
9
|
export declare function commandAs(user: string, script: string, cwdGiven: boolean): string[];
|
|
10
10
|
/** An interactive login shell as `user`, for a PTY. */
|
|
11
11
|
export declare function shellAs(user: string, cwdGiven: boolean): string[];
|
|
12
|
+
/** A running command as E2B lists it, from the command line Runtime keeps
|
|
13
|
+
* (its words joined by spaces, at most 256 characters): `/bin/bash -l -c
|
|
14
|
+
* <script>` for a command, `/bin/bash -i -l` for a PTY, as E2B starts them,
|
|
15
|
+
* whether it runs as the sandbox user or through `commandAs`. Anything else,
|
|
16
|
+
* started outside this package, is its first word and the rest. */
|
|
17
|
+
export declare function listedAs(command: string): {
|
|
18
|
+
cmd: string;
|
|
19
|
+
args: string[];
|
|
20
|
+
};
|
|
12
21
|
/** E2B's file calls as another Linux user: each one a short script run with
|
|
13
22
|
* `sudo -u`, since Runtime's file API acts as the sandbox's own user. Output
|
|
14
23
|
* streams, so a read of any size comes back whole. */
|