withruntime 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BLAXEL.md +4 -4
- package/DAYTONA.md +13 -7
- package/E2B.md +12 -8
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +13 -3
- package/compatibility-lock.json +84 -8
- 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 +189 -54
- package/dist/client.d.ts +29 -3
- package/dist/client.js +40 -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/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/daytona.js +11 -4
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +121 -38
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/sandbox.d.ts +1 -1
- package/dist/daytona/sandbox.js +16 -7
- package/dist/e2b/client.js +1 -1
- package/dist/e2b/commands.d.ts +30 -8
- package/dist/e2b/commands.js +142 -54
- package/dist/e2b/index.d.ts +1 -0
- package/dist/e2b/index.js +1 -0
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +80 -0
- package/dist/e2b/sandbox.d.ts +2 -1
- package/dist/e2b/sandbox.js +49 -15
- 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 +2 -1
- package/dist/login.js +16 -10
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +111 -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/index.d.ts +12 -0
- package/dist/products/index.js +10 -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/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 +37 -8
- package/dist/sandbox.js +432 -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/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/sandbox.d.ts +8 -8
- package/dist/vercel/sandbox.js +154 -59
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +2 -1
package/dist/e2b/commands.js
CHANGED
|
@@ -10,18 +10,57 @@ export function pidOf(processId) {
|
|
|
10
10
|
}
|
|
11
11
|
return hash >>> 1 || 1;
|
|
12
12
|
}
|
|
13
|
-
function refuseUser(user) {
|
|
13
|
+
export function refuseUser(user) {
|
|
14
14
|
if (user !== undefined && user !== "user")
|
|
15
15
|
throw new NotSupportedError(`Running as the user "${user}"`, 'Runtime runs commands as its sandbox owner, with passwordless sudo: prefix the command with "sudo" to run it as root.');
|
|
16
16
|
}
|
|
17
|
-
function timeoutFor(timeoutMs) {
|
|
17
|
+
export function timeoutFor(timeoutMs) {
|
|
18
18
|
if (timeoutMs === undefined)
|
|
19
19
|
return DEFAULT_TIMEOUT_MS;
|
|
20
20
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)
|
|
21
21
|
throw new InvalidArgumentError("timeoutMs must be a nonnegative whole number.");
|
|
22
22
|
return timeoutMs;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
/** One budget for lookup and mutation, including retries and queued input. */
|
|
25
|
+
export function commandRequest(opts, defaultTimeoutMs = 60_000) {
|
|
26
|
+
opts.signal?.throwIfAborted();
|
|
27
|
+
const timeoutMs = timeoutFor(opts.requestTimeoutMs ?? defaultTimeoutMs);
|
|
28
|
+
const deadline = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
29
|
+
const signal = opts.signal && deadline ? AbortSignal.any([opts.signal, deadline]) : (opts.signal ?? deadline);
|
|
30
|
+
return { timeoutMs: 0, ...(signal ? { signal } : {}) };
|
|
31
|
+
}
|
|
32
|
+
export async function findProcess(ctx, pid, options) {
|
|
33
|
+
options.signal?.throwIfAborted();
|
|
34
|
+
const processes = await guard("sandbox", () => ctx.runtime.processes.list(options));
|
|
35
|
+
options.signal?.throwIfAborted();
|
|
36
|
+
const matches = processes.filter((info) => pidOf(info.id) === pid && info.state === "running");
|
|
37
|
+
if (matches.length > 1)
|
|
38
|
+
throw new SandboxError(`More than one running command has pid ${pid}; use the native process id instead.`);
|
|
39
|
+
return matches[0];
|
|
40
|
+
}
|
|
41
|
+
export async function resolveProcess(ctx, pid, options) {
|
|
42
|
+
const info = await findProcess(ctx, pid, options);
|
|
43
|
+
if (!info)
|
|
44
|
+
throw new SandboxError(`No running command with pid ${pid}.`);
|
|
45
|
+
return guard("sandbox", () => ctx.runtime.processes.get(info.id, options));
|
|
46
|
+
}
|
|
47
|
+
export async function killProcess(ctx, pid, options) {
|
|
48
|
+
const info = await findProcess(ctx, pid, options);
|
|
49
|
+
if (!info)
|
|
50
|
+
return false;
|
|
51
|
+
try {
|
|
52
|
+
const process = await guard("sandbox", () => ctx.runtime.processes.get(info.id, options));
|
|
53
|
+
options.signal?.throwIfAborted();
|
|
54
|
+
await guard("sandbox", () => process.kill("SIGKILL", options));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof SandboxError && error.statusCode === 404)
|
|
59
|
+
return false;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function timedOut(timeoutMs) {
|
|
25
64
|
return new TimeoutError(`Command timed out after ${timeoutMs} ms: this error is likely due to exceeding 'timeoutMs'. ` +
|
|
26
65
|
"Pass a larger 'timeoutMs', or 0 for no limit.");
|
|
27
66
|
}
|
|
@@ -42,6 +81,25 @@ function toResult(result) {
|
|
|
42
81
|
...(result.truncated ? { truncated: true } : {}),
|
|
43
82
|
};
|
|
44
83
|
}
|
|
84
|
+
/** The subscription deadline includes the opening handshake. */
|
|
85
|
+
export function connectionRequest(opts, defaultTimeoutMs) {
|
|
86
|
+
const timeoutMs = timeoutFor(opts.timeoutMs);
|
|
87
|
+
const deadline = timeoutMs > 0 ? performance.now() + timeoutMs : undefined;
|
|
88
|
+
const connectionTimeout = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
89
|
+
const signal = connectionTimeout && opts.signal
|
|
90
|
+
? AbortSignal.any([connectionTimeout, opts.signal])
|
|
91
|
+
: (connectionTimeout ?? opts.signal);
|
|
92
|
+
return {
|
|
93
|
+
timeoutMs,
|
|
94
|
+
deadline,
|
|
95
|
+
request: commandRequest({ ...opts, signal }, defaultTimeoutMs),
|
|
96
|
+
failure: (error) => {
|
|
97
|
+
if (connectionTimeout?.aborted)
|
|
98
|
+
throw timedOut(timeoutMs);
|
|
99
|
+
throw error;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
45
103
|
function settle(result, timeoutMs) {
|
|
46
104
|
if (result.timedOut)
|
|
47
105
|
throw timedOut(timeoutMs);
|
|
@@ -62,76 +120,69 @@ export class Commands {
|
|
|
62
120
|
return envs && Object.keys(envs).length ? { env: envs } : {};
|
|
63
121
|
}
|
|
64
122
|
async run(cmd, opts = {}) {
|
|
65
|
-
opts.
|
|
123
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
66
124
|
refuseUser(opts.user);
|
|
67
|
-
await
|
|
68
|
-
|
|
69
|
-
|
|
125
|
+
const handle = await (async () => {
|
|
126
|
+
await this.#ctx.ensureHome(`${cmd}\n${opts.cwd ?? ""}`, opening.request);
|
|
127
|
+
opening.request.signal?.throwIfAborted();
|
|
128
|
+
return this.#start(cmd, opts, opening.timeoutMs, opening.request, opening.deadline);
|
|
129
|
+
})().catch(opening.failure);
|
|
70
130
|
return opts.background ? handle : handle.wait();
|
|
71
131
|
}
|
|
72
|
-
async #start(cmd, opts, timeoutMs) {
|
|
73
|
-
|
|
74
|
-
const requestDeadline = opts.requestTimeoutMs
|
|
75
|
-
? AbortSignal.timeout(opts.requestTimeoutMs)
|
|
76
|
-
: undefined;
|
|
77
|
-
const signals = [opts.signal, requestDeadline].filter((signal) => signal !== undefined);
|
|
132
|
+
async #start(cmd, opts, timeoutMs, request, deadline) {
|
|
133
|
+
request.signal?.throwIfAborted();
|
|
78
134
|
const process = await guard("sandbox", () => this.#ctx.runtime.spawn(cmd, {
|
|
79
135
|
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
80
136
|
...this.#env(opts.envs),
|
|
81
137
|
...(opts.stdin ? { stdin: "pipe" } : {}),
|
|
82
|
-
|
|
138
|
+
request,
|
|
83
139
|
}));
|
|
84
140
|
return new CommandHandle(process, {
|
|
85
141
|
stdin: opts.stdin === true,
|
|
86
142
|
timeoutMs,
|
|
143
|
+
deadline,
|
|
144
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
87
145
|
signal: opts.signal,
|
|
88
146
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
89
147
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
90
148
|
});
|
|
91
149
|
}
|
|
92
150
|
/** Running commands. */
|
|
93
|
-
async list(
|
|
94
|
-
const
|
|
151
|
+
async list(opts = {}) {
|
|
152
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
153
|
+
const processes = await guard("sandbox", () => this.#ctx.runtime.processes.list(request));
|
|
95
154
|
return processes.filter((info) => info.state === "running").map(describe);
|
|
96
155
|
}
|
|
97
|
-
async #find(pid) {
|
|
98
|
-
const processes = await guard("sandbox", () => this.#ctx.runtime.processes.list());
|
|
99
|
-
return processes.find((info) => pidOf(info.id) === pid && info.state === "running");
|
|
100
|
-
}
|
|
101
|
-
async #process(pid) {
|
|
102
|
-
const info = await this.#find(pid);
|
|
103
|
-
if (!info)
|
|
104
|
-
throw new SandboxError(`No running command with pid ${pid}.`);
|
|
105
|
-
return guard("sandbox", () => this.#ctx.runtime.processes.get(info.id));
|
|
106
|
-
}
|
|
107
156
|
/** Kills a command with SIGKILL, as E2B does. False when there is none. */
|
|
108
|
-
async kill(pid,
|
|
109
|
-
|
|
110
|
-
if (!info)
|
|
111
|
-
return false;
|
|
112
|
-
const process = await guard("sandbox", () => this.#ctx.runtime.processes.get(info.id));
|
|
113
|
-
await guard("sandbox", () => process.kill("SIGKILL"));
|
|
114
|
-
return true;
|
|
157
|
+
async kill(pid, opts = {}) {
|
|
158
|
+
return killProcess(this.#ctx, pid, commandRequest(opts, this.#ctx.requestTimeoutMs));
|
|
115
159
|
}
|
|
116
160
|
/** Sends input to a command started with `stdin: true`. */
|
|
117
|
-
async sendStdin(pid, data,
|
|
118
|
-
const
|
|
161
|
+
async sendStdin(pid, data, opts = {}) {
|
|
162
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
163
|
+
const process = await resolveProcess(this.#ctx, pid, request);
|
|
119
164
|
if (!process.info.stdinOpen)
|
|
120
165
|
throw new InvalidArgumentError(`The command with pid ${pid} was not started with stdin: true, so its input is closed.`);
|
|
121
|
-
|
|
166
|
+
request.signal?.throwIfAborted();
|
|
167
|
+
await guard("sandbox", () => process.write(data, request));
|
|
122
168
|
}
|
|
123
|
-
async closeStdin(pid,
|
|
124
|
-
const
|
|
125
|
-
|
|
169
|
+
async closeStdin(pid, opts = {}) {
|
|
170
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
171
|
+
const process = await resolveProcess(this.#ctx, pid, request);
|
|
172
|
+
request.signal?.throwIfAborted();
|
|
173
|
+
await guard("sandbox", () => process.write("", { ...request, eof: true }));
|
|
126
174
|
}
|
|
127
175
|
/** Attaches to a running command; output from now on reaches the handle. */
|
|
128
176
|
async connect(pid, opts = {}) {
|
|
129
|
-
const
|
|
177
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
178
|
+
const process = await resolveProcess(this.#ctx, pid, opening.request).catch(opening.failure);
|
|
130
179
|
return new CommandHandle(process, {
|
|
131
180
|
stdin: process.info.stdinOpen,
|
|
132
|
-
timeoutMs:
|
|
181
|
+
timeoutMs: opening.timeoutMs,
|
|
182
|
+
deadline: opening.deadline,
|
|
133
183
|
signal: opts.signal,
|
|
134
184
|
cursor: process.info.outputBytes,
|
|
185
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
135
186
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
136
187
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
137
188
|
});
|
|
@@ -146,6 +197,28 @@ function describe(info) {
|
|
|
146
197
|
cwd: info.cwd,
|
|
147
198
|
};
|
|
148
199
|
}
|
|
200
|
+
/** Reader cancellation also releases a callback whose promise never settles. */
|
|
201
|
+
async function deliver(signal, callback) {
|
|
202
|
+
signal.throwIfAborted();
|
|
203
|
+
let cancelled;
|
|
204
|
+
const aborted = new Promise((_, reject) => {
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- rejects with the caller's abort reason as is, as the upstream SDK does
|
|
206
|
+
cancelled = () => reject(signal.reason);
|
|
207
|
+
signal.addEventListener("abort", cancelled, { once: true });
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
await Promise.race([
|
|
211
|
+
Promise.resolve().then(() => {
|
|
212
|
+
signal.throwIfAborted();
|
|
213
|
+
return callback();
|
|
214
|
+
}),
|
|
215
|
+
aborted,
|
|
216
|
+
]);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
signal.removeEventListener("abort", cancelled);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
149
222
|
/** A command started in the background: E2B's CommandHandle over a Runtime
|
|
150
223
|
* process. Output streams into `stdout`/`stderr` and the callbacks as it
|
|
151
224
|
* arrives. */
|
|
@@ -154,6 +227,7 @@ export class CommandHandle {
|
|
|
154
227
|
#process;
|
|
155
228
|
#stdin;
|
|
156
229
|
#timeoutMs;
|
|
230
|
+
#requestTimeoutMs;
|
|
157
231
|
#abort = new AbortController();
|
|
158
232
|
#done;
|
|
159
233
|
#stdout = "";
|
|
@@ -168,13 +242,16 @@ export class CommandHandle {
|
|
|
168
242
|
this.pid = pidOf(process.id);
|
|
169
243
|
this.#stdin = options.stdin;
|
|
170
244
|
this.#timeoutMs = options.timeoutMs;
|
|
245
|
+
this.#requestTimeoutMs = options.requestTimeoutMs;
|
|
171
246
|
// E2B's timeout limits the subscription. It never asks us to kill the
|
|
172
247
|
// process: the sandbox lease still bounds the workload after disconnect.
|
|
173
248
|
if (options.timeoutMs > 0) {
|
|
174
249
|
this.#deadline = setTimeout(() => {
|
|
175
250
|
this.#failure = timedOut(options.timeoutMs);
|
|
176
251
|
this.#abort.abort();
|
|
177
|
-
}, options.
|
|
252
|
+
}, options.deadline === undefined
|
|
253
|
+
? options.timeoutMs
|
|
254
|
+
: Math.max(0, options.deadline - performance.now()));
|
|
178
255
|
this.#deadline.unref?.();
|
|
179
256
|
}
|
|
180
257
|
this.#done = this.#follow(options);
|
|
@@ -184,19 +261,28 @@ export class CommandHandle {
|
|
|
184
261
|
? AbortSignal.any([options.signal, this.#abort.signal])
|
|
185
262
|
: this.#abort.signal;
|
|
186
263
|
try {
|
|
187
|
-
|
|
264
|
+
const outputOptions = {
|
|
188
265
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
189
266
|
signal,
|
|
190
|
-
}
|
|
191
|
-
|
|
267
|
+
};
|
|
268
|
+
const output = options.pty
|
|
269
|
+
? this.#process.outputBytes(outputOptions)
|
|
270
|
+
: this.#process.output(outputOptions);
|
|
271
|
+
for await (const event of output) {
|
|
272
|
+
if (this.#disconnected)
|
|
192
273
|
break;
|
|
193
|
-
|
|
274
|
+
signal.throwIfAborted();
|
|
275
|
+
if ((event.type === "stdout" || event.type === "stderr") &&
|
|
276
|
+
event.data instanceof Uint8Array) {
|
|
277
|
+
await deliver(signal, () => options.onData?.(event.data));
|
|
278
|
+
}
|
|
279
|
+
else if (event.type === "stdout" && typeof event.data === "string") {
|
|
194
280
|
this.#stdout += event.data;
|
|
195
|
-
await options.onStdout?.(event.data);
|
|
281
|
+
await deliver(signal, () => options.onStdout?.(event.data));
|
|
196
282
|
}
|
|
197
|
-
else if (event.type === "stderr") {
|
|
283
|
+
else if (event.type === "stderr" && typeof event.data === "string") {
|
|
198
284
|
this.#stderr += event.data;
|
|
199
|
-
await options.onStderr?.(event.data);
|
|
285
|
+
await deliver(signal, () => options.onStderr?.(event.data));
|
|
200
286
|
}
|
|
201
287
|
else if (event.type === "truncated") {
|
|
202
288
|
this.#truncated = true;
|
|
@@ -267,7 +353,7 @@ export class CommandHandle {
|
|
|
267
353
|
if (this.#exit)
|
|
268
354
|
return false;
|
|
269
355
|
try {
|
|
270
|
-
await guard("sandbox", () => this.#process.kill("SIGKILL"));
|
|
356
|
+
await guard("sandbox", () => this.#process.kill("SIGKILL", commandRequest({}, this.#requestTimeoutMs)));
|
|
271
357
|
return true;
|
|
272
358
|
}
|
|
273
359
|
catch (error) {
|
|
@@ -276,14 +362,16 @@ export class CommandHandle {
|
|
|
276
362
|
throw error;
|
|
277
363
|
}
|
|
278
364
|
}
|
|
279
|
-
async sendStdin(data,
|
|
365
|
+
async sendStdin(data, opts = {}) {
|
|
366
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
280
367
|
if (!this.#stdin)
|
|
281
368
|
throw new InvalidArgumentError("The command was not started with stdin: true, so its input is closed.");
|
|
282
|
-
await guard("sandbox", () => this.#process.write(data));
|
|
369
|
+
await guard("sandbox", () => this.#process.write(data, request));
|
|
283
370
|
}
|
|
284
|
-
async closeStdin(
|
|
371
|
+
async closeStdin(opts = {}) {
|
|
372
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
285
373
|
if (!this.#stdin)
|
|
286
374
|
throw new InvalidArgumentError("The command was not started with stdin: true.");
|
|
287
|
-
await guard("sandbox", () => this.#process.write("", { eof: true }));
|
|
375
|
+
await guard("sandbox", () => this.#process.write("", { ...request, eof: true }));
|
|
288
376
|
}
|
|
289
377
|
}
|
package/dist/e2b/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { Commands, CommandHandle, pidOf, type CommandStartOpts, type CommandConn
|
|
|
12
12
|
export { Filesystem, FileType, type EntryInfo, type WriteInfo, type WriteEntry, type FilesystemRequestOpts, type FilesystemReadOpts, type FilesystemWriteOpts, type FilesystemListOpts, FilesystemEventType, type FilesystemEvent, type WatchOpts, type E2BWatchHandle as WatchHandle, } from "./filesystem.js";
|
|
13
13
|
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, type CommandResult, } from "./errors.js";
|
|
14
14
|
export type { ConnectionOpts, RuntimeOpts } from "./client.js";
|
|
15
|
+
export { Pty, type PtyCreateOpts, type PtyConnectOpts } from "./pty.js";
|
|
15
16
|
export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
|
|
16
17
|
export default Sandbox;
|
|
17
18
|
/** E2B's client with bound options: `new E2B({ apiKey }).Sandbox.create()`. */
|
package/dist/e2b/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, } from ".
|
|
|
4
4
|
export { Commands, CommandHandle, pidOf, } from "./commands.js";
|
|
5
5
|
export { Filesystem, FileType, FilesystemEventType, } from "./filesystem.js";
|
|
6
6
|
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, } from "./errors.js";
|
|
7
|
+
export { Pty } from "./pty.js";
|
|
7
8
|
export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
|
|
8
9
|
export default Sandbox;
|
|
9
10
|
/** E2B's client with bound options: `new E2B({ apiKey }).Sandbox.create()`. */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CommandHandle, type CommandRequestOpts, type SandboxContext, type Username } from "./commands.js";
|
|
2
|
+
export interface PtyCreateOpts extends CommandRequestOpts {
|
|
3
|
+
cols: number;
|
|
4
|
+
rows: number;
|
|
5
|
+
onData: (data: Uint8Array) => void | Promise<void>;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
user?: Username;
|
|
8
|
+
envs?: Record<string, string>;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
}
|
|
11
|
+
export type PtyConnectOpts = CommandRequestOpts & Pick<PtyCreateOpts, "onData" | "timeoutMs">;
|
|
12
|
+
/** E2B's PTY methods over native terminal processes with lossless output. */
|
|
13
|
+
export declare class Pty {
|
|
14
|
+
private readonly ctx;
|
|
15
|
+
constructor(ctx: SandboxContext);
|
|
16
|
+
create(opts: PtyCreateOpts): Promise<CommandHandle>;
|
|
17
|
+
connect(pid: number, opts?: PtyConnectOpts): Promise<CommandHandle>;
|
|
18
|
+
sendInput(pid: number, data: Uint8Array, opts?: CommandRequestOpts): Promise<void>;
|
|
19
|
+
resize(pid: number, size: {
|
|
20
|
+
cols: number;
|
|
21
|
+
rows: number;
|
|
22
|
+
}, opts?: CommandRequestOpts): Promise<void>;
|
|
23
|
+
kill(pid: number, opts?: CommandRequestOpts): Promise<boolean>;
|
|
24
|
+
}
|
package/dist/e2b/pty.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CommandHandle, commandRequest, connectionRequest, killProcess, refuseUser, resolveProcess, } from "./commands.js";
|
|
2
|
+
import { guard, InvalidArgumentError, NotSupportedError } from "./errors.js";
|
|
3
|
+
function dimensions(size) {
|
|
4
|
+
for (const value of [size.cols, size.rows])
|
|
5
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
6
|
+
throw new InvalidArgumentError("PTY rows and columns must be positive whole numbers.");
|
|
7
|
+
return { cols: size.cols, rows: size.rows };
|
|
8
|
+
}
|
|
9
|
+
/** E2B's PTY methods over native terminal processes with lossless output. */
|
|
10
|
+
export class Pty {
|
|
11
|
+
ctx;
|
|
12
|
+
constructor(ctx) {
|
|
13
|
+
this.ctx = ctx;
|
|
14
|
+
}
|
|
15
|
+
async create(opts) {
|
|
16
|
+
refuseUser(opts.user);
|
|
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
|
+
await this.ctx.ensureHome(opts.cwd, opening.request);
|
|
24
|
+
opening.request.signal?.throwIfAborted();
|
|
25
|
+
return this.ctx.runtime.spawn(["/bin/bash", "-i", "-l"], {
|
|
26
|
+
pty,
|
|
27
|
+
stdin: "pipe",
|
|
28
|
+
outputEncoding: "base64",
|
|
29
|
+
env,
|
|
30
|
+
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
31
|
+
request: opening.request,
|
|
32
|
+
});
|
|
33
|
+
}).catch(opening.failure);
|
|
34
|
+
return new CommandHandle(process, {
|
|
35
|
+
stdin: false,
|
|
36
|
+
pty: true,
|
|
37
|
+
onData: opts.onData,
|
|
38
|
+
timeoutMs: opening.timeoutMs,
|
|
39
|
+
deadline: opening.deadline,
|
|
40
|
+
signal: opts.signal,
|
|
41
|
+
requestTimeoutMs: this.ctx.requestTimeoutMs,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async connect(pid, opts) {
|
|
45
|
+
const opening = connectionRequest(opts ?? {}, this.ctx.requestTimeoutMs);
|
|
46
|
+
const process = await resolveProcess(this.ctx, pid, opening.request).catch(opening.failure);
|
|
47
|
+
if (!process.info.pty || process.info.outputEncoding !== "base64")
|
|
48
|
+
throw new NotSupportedError("Connecting to a legacy text-output PTY", "Create the PTY through sandbox.pty.create first.");
|
|
49
|
+
return new CommandHandle(process, {
|
|
50
|
+
stdin: false,
|
|
51
|
+
pty: true,
|
|
52
|
+
onData: opts?.onData,
|
|
53
|
+
timeoutMs: opening.timeoutMs,
|
|
54
|
+
deadline: opening.deadline,
|
|
55
|
+
cursor: process.info.outputBytes,
|
|
56
|
+
signal: opts?.signal,
|
|
57
|
+
requestTimeoutMs: this.ctx.requestTimeoutMs,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async sendInput(pid, data, opts = {}) {
|
|
61
|
+
const request = commandRequest(opts, this.ctx.requestTimeoutMs);
|
|
62
|
+
const process = await resolveProcess(this.ctx, pid, request);
|
|
63
|
+
if (!process.info.pty)
|
|
64
|
+
throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
|
|
65
|
+
request.signal?.throwIfAborted();
|
|
66
|
+
await guard("sandbox", () => process.write(data, request));
|
|
67
|
+
}
|
|
68
|
+
async resize(pid, size, opts = {}) {
|
|
69
|
+
const pty = dimensions(size);
|
|
70
|
+
const request = commandRequest(opts, this.ctx.requestTimeoutMs);
|
|
71
|
+
const process = await resolveProcess(this.ctx, pid, request);
|
|
72
|
+
if (!process.info.pty)
|
|
73
|
+
throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
|
|
74
|
+
request.signal?.throwIfAborted();
|
|
75
|
+
await guard("sandbox", () => process.resize(pty.cols, pty.rows, request));
|
|
76
|
+
}
|
|
77
|
+
kill(pid, opts = {}) {
|
|
78
|
+
return killProcess(this.ctx, pid, commandRequest(opts, this.ctx.requestTimeoutMs));
|
|
79
|
+
}
|
|
80
|
+
}
|
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: {
|
|
@@ -93,6 +94,7 @@ export declare class Sandbox {
|
|
|
93
94
|
protected static readonly defaultTemplate: string;
|
|
94
95
|
readonly files: Filesystem;
|
|
95
96
|
readonly commands: Commands;
|
|
97
|
+
readonly pty: Pty;
|
|
96
98
|
/** Use Sandbox.create or Sandbox.connect. */
|
|
97
99
|
constructor(runtime: RuntimeSandbox, client: Runtime, requestTimeoutMs?: number);
|
|
98
100
|
get sandboxId(): string;
|
|
@@ -167,7 +169,6 @@ export declare class Sandbox {
|
|
|
167
169
|
* its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
|
|
168
170
|
* the address can reach it; a browser sees a one-time page naming Runtime. */
|
|
169
171
|
getPublicHost(port: number): Promise<string>;
|
|
170
|
-
get pty(): never;
|
|
171
172
|
get git(): never;
|
|
172
173
|
/** E2B's metrics, from Runtime's measured readings (`sbx.metrics()`): CPU
|
|
173
174
|
* as a percent of the sandbox's vCPUs and resident memory, one entry per
|
package/dist/e2b/sandbox.js
CHANGED
|
@@ -2,6 +2,7 @@ import { clientFor, request, } from "./client.js";
|
|
|
2
2
|
import { Commands } from "./commands.js";
|
|
3
3
|
import { guard, InvalidArgumentError, NotSupportedError, 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. */
|
|
@@ -65,12 +66,13 @@ function refuseCreate(opts) {
|
|
|
65
66
|
}
|
|
66
67
|
/** Where a template sends the create: the stock image, a Runtime image by
|
|
67
68
|
* name or id, or a Runtime snapshot by id. */
|
|
68
|
-
async function resolveTemplate(client, template) {
|
|
69
|
+
async function resolveTemplate(client, template, options) {
|
|
70
|
+
options.signal?.throwIfAborted();
|
|
69
71
|
if (STOCK_TEMPLATES.has(template))
|
|
70
72
|
return {};
|
|
71
73
|
if (UUID.test(template)) {
|
|
72
74
|
try {
|
|
73
|
-
await client.images.get(template);
|
|
75
|
+
await client.images.get(template, options);
|
|
74
76
|
return { image: template };
|
|
75
77
|
}
|
|
76
78
|
catch (error) {
|
|
@@ -79,7 +81,7 @@ async function resolveTemplate(client, template) {
|
|
|
79
81
|
}
|
|
80
82
|
return { snapshot: template };
|
|
81
83
|
}
|
|
82
|
-
const page = await client.images.list({ name: template, state: "ready", limit: 1 });
|
|
84
|
+
const page = await client.images.list({ name: template, state: "ready", limit: 1 }, options);
|
|
83
85
|
const image = page.data[0];
|
|
84
86
|
if (image)
|
|
85
87
|
return { image: image.id };
|
|
@@ -135,6 +137,7 @@ export class Sandbox {
|
|
|
135
137
|
static defaultTemplate = "base";
|
|
136
138
|
files;
|
|
137
139
|
commands;
|
|
140
|
+
pty;
|
|
138
141
|
#client;
|
|
139
142
|
#runtime;
|
|
140
143
|
#home;
|
|
@@ -146,12 +149,14 @@ export class Sandbox {
|
|
|
146
149
|
this.#runtime = runtime;
|
|
147
150
|
this.#client = client;
|
|
148
151
|
const ctx = {
|
|
149
|
-
ensureHome: (text) => this.#ensureHome(text),
|
|
152
|
+
ensureHome: (text, options) => this.#ensureHome(text, options),
|
|
153
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
150
154
|
};
|
|
151
155
|
// A getter, so the modules see the sandbox as it is after each refresh.
|
|
152
156
|
Object.defineProperty(ctx, "runtime", { get: () => this.#runtime });
|
|
153
157
|
this.files = new Filesystem(ctx);
|
|
154
158
|
this.commands = new Commands(ctx);
|
|
159
|
+
this.pty = new Pty(ctx);
|
|
155
160
|
}
|
|
156
161
|
get sandboxId() {
|
|
157
162
|
return this.#runtime.id;
|
|
@@ -167,21 +172,47 @@ export class Sandbox {
|
|
|
167
172
|
/** E2B's home is /home/user and Runtime's is /workspace. The first time a
|
|
168
173
|
* path or command names /home/user, it is made a link to /workspace (unless
|
|
169
174
|
* something is already there), so paths written for E2B work. */
|
|
170
|
-
#ensureHome(text) {
|
|
175
|
+
#ensureHome(text, options = {}) {
|
|
176
|
+
const signal = options.signal;
|
|
177
|
+
signal?.throwIfAborted();
|
|
178
|
+
const wait = (pending) => {
|
|
179
|
+
if (!signal)
|
|
180
|
+
return pending;
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- rejects with the caller's abort reason as is, as the upstream SDK does
|
|
183
|
+
const abort = () => reject(signal.reason);
|
|
184
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
185
|
+
pending.then(() => {
|
|
186
|
+
signal.removeEventListener("abort", abort);
|
|
187
|
+
resolve();
|
|
188
|
+
}, (error) => {
|
|
189
|
+
signal.removeEventListener("abort", abort);
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- passes the pending call's own failure through unchanged
|
|
191
|
+
reject(error);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
};
|
|
171
195
|
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"));
|
|
196
|
+
return wait(this.#home ?? Promise.resolve());
|
|
197
|
+
const pending = (async () => {
|
|
198
|
+
const result = await guard("sandbox", () => this.#runtime.exec("[ -e /home/user ] || sudo ln -s /workspace /home/user", { signal }));
|
|
175
199
|
if (result.exitCode !== 0 && typeof process !== "undefined")
|
|
176
200
|
process.emitWarning(`Could not link /home/user to /workspace: ${result.stderr.trim()}`, {
|
|
177
201
|
code: "RUNTIME_E2B_HOME",
|
|
178
202
|
});
|
|
179
203
|
})();
|
|
180
|
-
|
|
204
|
+
this.#home = pending;
|
|
205
|
+
pending.catch(() => {
|
|
206
|
+
if (this.#home === pending)
|
|
207
|
+
this.#home = undefined;
|
|
208
|
+
});
|
|
209
|
+
return wait(pending);
|
|
181
210
|
}
|
|
182
211
|
static async create(templateOrOpts, maybeOpts) {
|
|
183
212
|
const opts = (typeof templateOrOpts === "string" ? maybeOpts : templateOrOpts) ?? {};
|
|
184
213
|
const template = (typeof templateOrOpts === "string" ? templateOrOpts : opts.template) ?? this.defaultTemplate;
|
|
214
|
+
const options = request(opts);
|
|
215
|
+
options.signal?.throwIfAborted();
|
|
185
216
|
refuseCreate(opts);
|
|
186
217
|
const client = clientFor(opts);
|
|
187
218
|
const input = {
|
|
@@ -196,7 +227,8 @@ export class Sandbox {
|
|
|
196
227
|
...(opts.envs && Object.keys(opts.envs).length ? { env: opts.envs } : {}),
|
|
197
228
|
...(opts.allowInternetAccess === false ? { network: { internet: false } } : {}),
|
|
198
229
|
};
|
|
199
|
-
const source = await guard("other", () => resolveTemplate(client, template));
|
|
230
|
+
const source = await guard("other", () => resolveTemplate(client, template, options));
|
|
231
|
+
options.signal?.throwIfAborted();
|
|
200
232
|
const create = {
|
|
201
233
|
// A snapshot carries its own machine; everything else gets E2B's shape.
|
|
202
234
|
...(source.snapshot ? {} : DEFAULT_SHAPE),
|
|
@@ -204,7 +236,7 @@ export class Sandbox {
|
|
|
204
236
|
...source,
|
|
205
237
|
...opts.runtime?.create,
|
|
206
238
|
};
|
|
207
|
-
const runtime = await guard("sandbox", () => client.sandboxes.create(create,
|
|
239
|
+
const runtime = await guard("sandbox", () => client.sandboxes.create(create, options));
|
|
208
240
|
return new this(runtime, client, opts.requestTimeoutMs);
|
|
209
241
|
}
|
|
210
242
|
/** Connects to a sandbox by id, waking it if it is paused, as E2B does. A
|
|
@@ -369,9 +401,6 @@ export class Sandbox {
|
|
|
369
401
|
return share;
|
|
370
402
|
}
|
|
371
403
|
// ---- 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
404
|
get git() {
|
|
376
405
|
throw new NotSupportedError("E2B's git module (deprecated by E2B too)", "Run git with sandbox.commands.run('git ...'); git is installed.");
|
|
377
406
|
}
|
|
@@ -454,11 +483,13 @@ const PAUSED = ["pausing", "paused"];
|
|
|
454
483
|
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`. */
|
|
455
484
|
export class SandboxPaginator {
|
|
456
485
|
#client;
|
|
486
|
+
#request;
|
|
457
487
|
#filter;
|
|
458
488
|
#page;
|
|
459
489
|
#hasNext = true;
|
|
460
490
|
constructor(client, opts) {
|
|
461
491
|
this.#client = client;
|
|
492
|
+
this.#request = request(opts);
|
|
462
493
|
const query = opts.query ?? {};
|
|
463
494
|
if (query.template !== undefined && !STOCK_TEMPLATES.has(query.template))
|
|
464
495
|
throw new NotSupportedError("Listing by template", "Filter by metadata instead: give sandboxes a metadata key when you create them.");
|
|
@@ -482,10 +513,13 @@ export class SandboxPaginator {
|
|
|
482
513
|
return this.#page?.nextCursor ?? undefined;
|
|
483
514
|
}
|
|
484
515
|
async nextItems() {
|
|
516
|
+
this.#request.signal?.throwIfAborted();
|
|
485
517
|
if (!this.#hasNext)
|
|
486
518
|
throw new SandboxError("No more items to fetch.");
|
|
487
519
|
const previous = this.#page;
|
|
488
|
-
const page = await guard("other", async () => previous
|
|
520
|
+
const page = await guard("other", async () => previous
|
|
521
|
+
? await previous.next()
|
|
522
|
+
: await this.#client.sandboxes.list(this.#filter, this.#request));
|
|
489
523
|
this.#page = page ?? undefined;
|
|
490
524
|
this.#hasNext = Boolean(page?.hasMore);
|
|
491
525
|
return (page?.data ?? []).map(infoOf);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StreamUploadSource = string | Blob | AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>;
|
|
2
|
+
export type UploadScope = {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
check(): void;
|
|
5
|
+
wait<T>(pending: Promise<T>): Promise<T>;
|
|
6
|
+
};
|
|
7
|
+
export type PreparedFileSource = {
|
|
8
|
+
size: number;
|
|
9
|
+
sha256: string;
|
|
10
|
+
chunkAt(offset: number, length: number): Promise<Uint8Array>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
/** ARCHITECTURE.md section 10, Folders over HTTP: unknown-length inputs use
|
|
14
|
+
* bounded private disk staging; known files and Blobs remain repeatable inputs.
|
|
15
|
+
* Size and digest are established before the remote upload can begin. */
|
|
16
|
+
export declare function prepareFileSource(input: StreamUploadSource, scope: UploadScope): Promise<PreparedFileSource>;
|