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/commands.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { CommandExitError, guard, InvalidArgumentError,
|
|
1
|
+
import { CommandExitError, guard, InvalidArgumentError, SandboxError, TimeoutError, translate, } from "./errors.js";
|
|
2
|
+
import { commandAs, runAs } from "./users.js";
|
|
2
3
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
4
|
+
/** How long a command may run on Runtime: a day, E2B's longest sandbox. E2B's
|
|
5
|
+
* own `timeoutMs` bounds only the connection, so the process gets this. */
|
|
6
|
+
const PROCESS_LIFETIME_MS = 86_400_000;
|
|
3
7
|
/** E2B's pids are numbers; Runtime's process ids are strings. The number is a
|
|
4
8
|
* 31-bit FNV-1a hash of the id, so every client derives the same one. */
|
|
5
9
|
export function pidOf(processId) {
|
|
@@ -10,18 +14,53 @@ export function pidOf(processId) {
|
|
|
10
14
|
}
|
|
11
15
|
return hash >>> 1 || 1;
|
|
12
16
|
}
|
|
13
|
-
function
|
|
14
|
-
if (user !== undefined && user !== "user")
|
|
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
|
-
}
|
|
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,116 @@ export class Commands {
|
|
|
62
120
|
return envs && Object.keys(envs).length ? { env: envs } : {};
|
|
63
121
|
}
|
|
64
122
|
async run(cmd, opts = {}) {
|
|
65
|
-
opts.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
123
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
124
|
+
const handle = await (async () => {
|
|
125
|
+
const user = await runAs(this.#ctx, opts.user, opening.request);
|
|
126
|
+
await this.#ctx.ensureHome(`${cmd}\n${opts.cwd ?? ""}`, opening.request);
|
|
127
|
+
opening.request.signal?.throwIfAborted();
|
|
128
|
+
const command = user ? commandAs(user, cmd, opts.cwd !== undefined) : cmd;
|
|
129
|
+
// A command waited on here is read from its first byte by the request
|
|
130
|
+
// that starts it, so Runtime holds a fast writer back rather than
|
|
131
|
+
// dropping what it printed before the first read: the result is whole.
|
|
132
|
+
return opts.background || opts.stdin
|
|
133
|
+
? this.#start(command, opts, opening.timeoutMs, opening.request, opening.deadline)
|
|
134
|
+
: this.#stream(command, opts, opening.timeoutMs, opening.request, opening.deadline);
|
|
135
|
+
})().catch(opening.failure);
|
|
70
136
|
return opts.background ? handle : handle.wait();
|
|
71
137
|
}
|
|
72
|
-
async #
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
138
|
+
async #stream(cmd, opts, timeoutMs, request, deadline) {
|
|
139
|
+
const abort = new AbortController();
|
|
140
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, abort.signal]) : abort.signal;
|
|
141
|
+
const stopOpening = () => abort.abort(request.signal?.reason);
|
|
142
|
+
request.signal?.addEventListener("abort", stopOpening, { once: true });
|
|
143
|
+
const stream = this.#ctx.runtime.execStream(cmd, {
|
|
144
|
+
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
145
|
+
...this.#env(opts.envs),
|
|
146
|
+
timeoutMs: PROCESS_LIFETIME_MS,
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
const events = stream[Symbol.asyncIterator]();
|
|
150
|
+
try {
|
|
151
|
+
const first = await guard("sandbox", () => events.next());
|
|
152
|
+
if (first.done || first.value.type !== "start")
|
|
153
|
+
throw new SandboxError("The command's output began without a start.");
|
|
154
|
+
const runtime = this.#ctx.runtime;
|
|
155
|
+
const processId = first.value.processId;
|
|
156
|
+
return new CommandHandle({
|
|
157
|
+
started: true,
|
|
158
|
+
id: processId,
|
|
159
|
+
events,
|
|
160
|
+
abort,
|
|
161
|
+
process: () => runtime.processes.get(processId, commandRequest({}, this.#ctx.requestTimeoutMs)),
|
|
162
|
+
}, {
|
|
163
|
+
stdin: false,
|
|
164
|
+
timeoutMs,
|
|
165
|
+
deadline,
|
|
166
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
167
|
+
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
168
|
+
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
abort.abort();
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
request.signal?.removeEventListener("abort", stopOpening);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async #start(cmd, opts, timeoutMs, request, deadline) {
|
|
180
|
+
request.signal?.throwIfAborted();
|
|
78
181
|
const process = await guard("sandbox", () => this.#ctx.runtime.spawn(cmd, {
|
|
79
182
|
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
80
183
|
...this.#env(opts.envs),
|
|
81
184
|
...(opts.stdin ? { stdin: "pipe" } : {}),
|
|
82
|
-
|
|
185
|
+
request,
|
|
83
186
|
}));
|
|
84
187
|
return new CommandHandle(process, {
|
|
85
188
|
stdin: opts.stdin === true,
|
|
86
189
|
timeoutMs,
|
|
190
|
+
deadline,
|
|
191
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
87
192
|
signal: opts.signal,
|
|
88
193
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
89
194
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
90
195
|
});
|
|
91
196
|
}
|
|
92
197
|
/** Running commands. */
|
|
93
|
-
async list(
|
|
94
|
-
const
|
|
198
|
+
async list(opts = {}) {
|
|
199
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
200
|
+
const processes = await guard("sandbox", () => this.#ctx.runtime.processes.list(request));
|
|
95
201
|
return processes.filter((info) => info.state === "running").map(describe);
|
|
96
202
|
}
|
|
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
203
|
/** 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;
|
|
204
|
+
async kill(pid, opts = {}) {
|
|
205
|
+
return killProcess(this.#ctx, pid, commandRequest(opts, this.#ctx.requestTimeoutMs));
|
|
115
206
|
}
|
|
116
207
|
/** Sends input to a command started with `stdin: true`. */
|
|
117
|
-
async sendStdin(pid, data,
|
|
118
|
-
const
|
|
208
|
+
async sendStdin(pid, data, opts = {}) {
|
|
209
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
210
|
+
const process = await resolveProcess(this.#ctx, pid, request);
|
|
119
211
|
if (!process.info.stdinOpen)
|
|
120
212
|
throw new InvalidArgumentError(`The command with pid ${pid} was not started with stdin: true, so its input is closed.`);
|
|
121
|
-
|
|
213
|
+
request.signal?.throwIfAborted();
|
|
214
|
+
await guard("sandbox", () => process.write(data, request));
|
|
122
215
|
}
|
|
123
|
-
async closeStdin(pid,
|
|
124
|
-
const
|
|
125
|
-
|
|
216
|
+
async closeStdin(pid, opts = {}) {
|
|
217
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
218
|
+
const process = await resolveProcess(this.#ctx, pid, request);
|
|
219
|
+
request.signal?.throwIfAborted();
|
|
220
|
+
await guard("sandbox", () => process.write("", { ...request, eof: true }));
|
|
126
221
|
}
|
|
127
222
|
/** Attaches to a running command; output from now on reaches the handle. */
|
|
128
223
|
async connect(pid, opts = {}) {
|
|
129
|
-
const
|
|
224
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
225
|
+
const process = await resolveProcess(this.#ctx, pid, opening.request).catch(opening.failure);
|
|
130
226
|
return new CommandHandle(process, {
|
|
131
227
|
stdin: process.info.stdinOpen,
|
|
132
|
-
timeoutMs:
|
|
228
|
+
timeoutMs: opening.timeoutMs,
|
|
229
|
+
deadline: opening.deadline,
|
|
133
230
|
signal: opts.signal,
|
|
134
231
|
cursor: process.info.outputBytes,
|
|
232
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
135
233
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
136
234
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
137
235
|
});
|
|
@@ -146,15 +244,39 @@ function describe(info) {
|
|
|
146
244
|
cwd: info.cwd,
|
|
147
245
|
};
|
|
148
246
|
}
|
|
247
|
+
/** Reader cancellation also releases a callback whose promise never settles. */
|
|
248
|
+
async function deliver(signal, callback) {
|
|
249
|
+
signal.throwIfAborted();
|
|
250
|
+
let cancelled;
|
|
251
|
+
const aborted = new Promise((_, reject) => {
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- rejects with the caller's abort reason as is, as the upstream SDK does
|
|
253
|
+
cancelled = () => reject(signal.reason);
|
|
254
|
+
signal.addEventListener("abort", cancelled, { once: true });
|
|
255
|
+
});
|
|
256
|
+
try {
|
|
257
|
+
await Promise.race([
|
|
258
|
+
Promise.resolve().then(() => {
|
|
259
|
+
signal.throwIfAborted();
|
|
260
|
+
return callback();
|
|
261
|
+
}),
|
|
262
|
+
aborted,
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
signal.removeEventListener("abort", cancelled);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
149
269
|
/** A command started in the background: E2B's CommandHandle over a Runtime
|
|
150
270
|
* process. Output streams into `stdout`/`stderr` and the callbacks as it
|
|
151
271
|
* arrives. */
|
|
152
272
|
export class CommandHandle {
|
|
153
273
|
pid;
|
|
274
|
+
#source;
|
|
154
275
|
#process;
|
|
155
276
|
#stdin;
|
|
156
277
|
#timeoutMs;
|
|
157
|
-
#
|
|
278
|
+
#requestTimeoutMs;
|
|
279
|
+
#abort;
|
|
158
280
|
#done;
|
|
159
281
|
#stdout = "";
|
|
160
282
|
#stderr = "";
|
|
@@ -163,18 +285,22 @@ export class CommandHandle {
|
|
|
163
285
|
#failure;
|
|
164
286
|
#disconnected = false;
|
|
165
287
|
#deadline;
|
|
166
|
-
constructor(
|
|
167
|
-
this.#
|
|
168
|
-
this
|
|
288
|
+
constructor(source, options) {
|
|
289
|
+
this.#source = source;
|
|
290
|
+
this.#abort = "started" in source ? source.abort : new AbortController();
|
|
291
|
+
this.pid = pidOf(source.id);
|
|
169
292
|
this.#stdin = options.stdin;
|
|
170
293
|
this.#timeoutMs = options.timeoutMs;
|
|
294
|
+
this.#requestTimeoutMs = options.requestTimeoutMs;
|
|
171
295
|
// E2B's timeout limits the subscription. It never asks us to kill the
|
|
172
296
|
// process: the sandbox lease still bounds the workload after disconnect.
|
|
173
297
|
if (options.timeoutMs > 0) {
|
|
174
298
|
this.#deadline = setTimeout(() => {
|
|
175
299
|
this.#failure = timedOut(options.timeoutMs);
|
|
176
300
|
this.#abort.abort();
|
|
177
|
-
}, options.
|
|
301
|
+
}, options.deadline === undefined
|
|
302
|
+
? options.timeoutMs
|
|
303
|
+
: Math.max(0, options.deadline - performance.now()));
|
|
178
304
|
this.#deadline.unref?.();
|
|
179
305
|
}
|
|
180
306
|
this.#done = this.#follow(options);
|
|
@@ -183,26 +309,39 @@ export class CommandHandle {
|
|
|
183
309
|
const signal = options.signal
|
|
184
310
|
? AbortSignal.any([options.signal, this.#abort.signal])
|
|
185
311
|
: this.#abort.signal;
|
|
312
|
+
const source = this.#source;
|
|
186
313
|
try {
|
|
187
|
-
|
|
314
|
+
const outputOptions = {
|
|
188
315
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
189
316
|
signal,
|
|
190
|
-
}
|
|
191
|
-
|
|
317
|
+
};
|
|
318
|
+
const output = "started" in source
|
|
319
|
+
? { [Symbol.asyncIterator]: () => source.events }
|
|
320
|
+
: options.pty
|
|
321
|
+
? source.outputBytes(outputOptions)
|
|
322
|
+
: source.output(outputOptions);
|
|
323
|
+
for await (const event of output) {
|
|
324
|
+
if (this.#disconnected)
|
|
192
325
|
break;
|
|
193
|
-
|
|
326
|
+
signal.throwIfAborted();
|
|
327
|
+
if ((event.type === "stdout" || event.type === "stderr") &&
|
|
328
|
+
event.data instanceof Uint8Array) {
|
|
329
|
+
await deliver(signal, () => options.onData?.(event.data));
|
|
330
|
+
}
|
|
331
|
+
else if (event.type === "stdout" && typeof event.data === "string") {
|
|
194
332
|
this.#stdout += event.data;
|
|
195
|
-
await options.onStdout?.(event.data);
|
|
333
|
+
await deliver(signal, () => options.onStdout?.(event.data));
|
|
196
334
|
}
|
|
197
|
-
else if (event.type === "stderr") {
|
|
335
|
+
else if (event.type === "stderr" && typeof event.data === "string") {
|
|
198
336
|
this.#stderr += event.data;
|
|
199
|
-
await options.onStderr?.(event.data);
|
|
337
|
+
await deliver(signal, () => options.onStderr?.(event.data));
|
|
200
338
|
}
|
|
201
339
|
else if (event.type === "truncated") {
|
|
202
340
|
this.#truncated = true;
|
|
203
341
|
}
|
|
204
342
|
else if (event.type === "exit") {
|
|
205
|
-
|
|
343
|
+
const exit = event;
|
|
344
|
+
this.#exit = { exitCode: exit.exitCode, timedOut: exit.timedOut };
|
|
206
345
|
}
|
|
207
346
|
}
|
|
208
347
|
}
|
|
@@ -262,12 +401,22 @@ export class CommandHandle {
|
|
|
262
401
|
this.#abort.abort();
|
|
263
402
|
await Promise.resolve();
|
|
264
403
|
}
|
|
404
|
+
/** The Runtime process, fetched once when a started stream needs it. */
|
|
405
|
+
#runtimeProcess() {
|
|
406
|
+
const source = this.#source;
|
|
407
|
+
if (!("started" in source))
|
|
408
|
+
return Promise.resolve(source);
|
|
409
|
+
this.#process ??= guard("sandbox", source.process);
|
|
410
|
+
this.#process.catch(() => (this.#process = undefined));
|
|
411
|
+
return this.#process;
|
|
412
|
+
}
|
|
265
413
|
/** Kills the command with SIGKILL. False when it had already ended. */
|
|
266
414
|
async kill() {
|
|
267
415
|
if (this.#exit)
|
|
268
416
|
return false;
|
|
269
417
|
try {
|
|
270
|
-
|
|
418
|
+
const process = await this.#runtimeProcess();
|
|
419
|
+
await guard("sandbox", () => process.kill("SIGKILL", commandRequest({}, this.#requestTimeoutMs)));
|
|
271
420
|
return true;
|
|
272
421
|
}
|
|
273
422
|
catch (error) {
|
|
@@ -276,14 +425,18 @@ export class CommandHandle {
|
|
|
276
425
|
throw error;
|
|
277
426
|
}
|
|
278
427
|
}
|
|
279
|
-
async sendStdin(data,
|
|
428
|
+
async sendStdin(data, opts = {}) {
|
|
429
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
280
430
|
if (!this.#stdin)
|
|
281
431
|
throw new InvalidArgumentError("The command was not started with stdin: true, so its input is closed.");
|
|
282
|
-
|
|
432
|
+
const process = await this.#runtimeProcess();
|
|
433
|
+
await guard("sandbox", () => process.write(data, request));
|
|
283
434
|
}
|
|
284
|
-
async closeStdin(
|
|
435
|
+
async closeStdin(opts = {}) {
|
|
436
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
285
437
|
if (!this.#stdin)
|
|
286
438
|
throw new InvalidArgumentError("The command was not started with stdin: true.");
|
|
287
|
-
|
|
439
|
+
const process = await this.#runtimeProcess();
|
|
440
|
+
await guard("sandbox", () => process.write("", { ...request, eof: true }));
|
|
288
441
|
}
|
|
289
442
|
}
|
package/dist/e2b/errors.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ export declare class NotSupportedError extends SandboxError {
|
|
|
46
46
|
readonly alternative: string;
|
|
47
47
|
constructor(feature: string, alternative: string, message?: string);
|
|
48
48
|
}
|
|
49
|
+
/** Runtime's addition, for `getHost` and `getPublicHost`: a free-trial
|
|
50
|
+
* sandbox shares a port only privately, so a request needs the port's token,
|
|
51
|
+
* and a host name alone cannot carry one. The message says what works. A
|
|
52
|
+
* NotSupportedError, so code that catches those catches this too. */
|
|
53
|
+
export declare class PublicPreviewNotAllowedError extends NotSupportedError {
|
|
54
|
+
constructor(sandboxId: string, port: number | undefined, message?: string);
|
|
55
|
+
}
|
|
49
56
|
/** What `commands.run` resolves with, as in E2B. */
|
|
50
57
|
export interface CommandResult {
|
|
51
58
|
exitCode: number;
|
package/dist/e2b/errors.js
CHANGED
|
@@ -72,6 +72,23 @@ export class NotSupportedError extends SandboxError {
|
|
|
72
72
|
this.code = "not_supported";
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
/** Runtime's addition, for `getHost` and `getPublicHost`: a free-trial
|
|
76
|
+
* sandbox shares a port only privately, so a request needs the port's token,
|
|
77
|
+
* and a host name alone cannot carry one. The message says what works. A
|
|
78
|
+
* NotSupportedError, so code that catches those catches this too. */
|
|
79
|
+
export class PublicPreviewNotAllowedError extends NotSupportedError {
|
|
80
|
+
constructor(sandboxId, port, message) {
|
|
81
|
+
const where = port === undefined ? "<port>" : String(port);
|
|
82
|
+
const alternative = `For an address that works on the trial, use (await sandbox.runtime.previews.create(${where})).urlWithToken: ` +
|
|
83
|
+
"it carries the token, in a browser, fetch or curl. For other paths on it, send the token as the " +
|
|
84
|
+
"x-runtime-preview-token header or the runtime_preview_token query parameter. A public host needs a paid " +
|
|
85
|
+
"sandbox, which is the account owner's decision.";
|
|
86
|
+
super("A public address on a free-trial sandbox", alternative, message ??
|
|
87
|
+
`Sandbox ${sandboxId} runs on the free trial, where a shared port is private: every request needs the ` +
|
|
88
|
+
`port's token, and a host name alone cannot carry one. ${alternative}`);
|
|
89
|
+
this.code = "public_preview_not_allowed";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
75
92
|
/** Thrown by `commands.run` and `CommandHandle.wait` when the command exits
|
|
76
93
|
* with a code other than 0. It carries the result, as in E2B. */
|
|
77
94
|
export class CommandExitError extends SandboxError {
|
|
@@ -113,6 +130,9 @@ export function translate(error, subject = "other") {
|
|
|
113
130
|
// A product switched off on purpose (forks, previews): Runtime's own words
|
|
114
131
|
// say what is off and what to use meanwhile.
|
|
115
132
|
out = new NotSupportedError(error.code.replace(/_unavailable$/, ""), error.hint ?? "", message);
|
|
133
|
+
else if (error.code === "public_preview_not_allowed")
|
|
134
|
+
// A 403 that is about the sandbox's funding, not the key.
|
|
135
|
+
out = new PublicPreviewNotAllowedError("", undefined, message);
|
|
116
136
|
else if (error.status === 401 || error.status === 403)
|
|
117
137
|
out = new AuthenticationError(message);
|
|
118
138
|
else if (error.code === "file_not_found" || (error.status === 404 && subject === "file"))
|
package/dist/e2b/filesystem.d.ts
CHANGED
|
@@ -51,7 +51,8 @@ export type WriteEntry = {
|
|
|
51
51
|
export interface FilesystemRequestOpts {
|
|
52
52
|
requestTimeoutMs?: number;
|
|
53
53
|
signal?: AbortSignal;
|
|
54
|
-
/**
|
|
54
|
+
/** "user" (the default) acts as the sandbox's own user through Runtime's
|
|
55
|
+
* file API; any other existing user, `root` included, through `sudo -u`. */
|
|
55
56
|
user?: Username;
|
|
56
57
|
}
|
|
57
58
|
export interface FilesystemReadOpts extends FilesystemRequestOpts {
|
package/dist/e2b/filesystem.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { request } from "./client.js";
|
|
2
2
|
import { FileNotFoundError, guard, InvalidArgumentError, NotSupportedError, TimeoutError, } from "./errors.js";
|
|
3
|
+
import { FilesAs, runAs } from "./users.js";
|
|
3
4
|
export var FileType;
|
|
4
5
|
(function (FileType) {
|
|
5
6
|
FileType["FILE"] = "file";
|
|
@@ -20,8 +21,6 @@ function absolute(path) {
|
|
|
20
21
|
return path.startsWith("/") ? path : `${HOME}/${path.replace(/^\.\//, "")}`;
|
|
21
22
|
}
|
|
22
23
|
function refuse(opts) {
|
|
23
|
-
if (opts.user !== undefined && opts.user !== "user")
|
|
24
|
-
throw new NotSupportedError(`File access as the user "${opts.user}"`, "Runtime's file calls act as the sandbox owner; use commands.run('sudo ...') for root-owned paths.");
|
|
25
24
|
if (opts.metadata && Object.keys(opts.metadata).length)
|
|
26
25
|
throw new NotSupportedError("File metadata (user.e2b.* extended attributes)", "Keep the metadata beside the file, for example in a JSON file.");
|
|
27
26
|
}
|
|
@@ -80,12 +79,25 @@ export class Filesystem {
|
|
|
80
79
|
opts.signal?.throwIfAborted();
|
|
81
80
|
return absolute(path);
|
|
82
81
|
}
|
|
82
|
+
/** The calls as another Linux user, or undefined for the sandbox's own. */
|
|
83
|
+
async #as(opts) {
|
|
84
|
+
if (opts.user === undefined || opts.user === "user")
|
|
85
|
+
return undefined;
|
|
86
|
+
const options = request(opts);
|
|
87
|
+
const user = await runAs(this.#ctx, opts.user, options);
|
|
88
|
+
return user ? new FilesAs(this.#ctx, user, options) : undefined;
|
|
89
|
+
}
|
|
83
90
|
async read(path, opts = {}) {
|
|
84
91
|
const file = await this.#path(path, opts);
|
|
92
|
+
const as = await this.#as(opts);
|
|
85
93
|
// A real stream, any size, never the whole file held first.
|
|
86
|
-
if (opts.format === "stream")
|
|
94
|
+
if (opts.format === "stream" && !as)
|
|
87
95
|
return guard("file", () => this.#files.readStream(file, request(opts)));
|
|
88
|
-
const bytes =
|
|
96
|
+
const bytes = as
|
|
97
|
+
? await as.read(file)
|
|
98
|
+
: await guard("file", () => this.#files.read(file, request(opts)));
|
|
99
|
+
if (opts.format === "stream")
|
|
100
|
+
return new Blob([bytes]).stream();
|
|
89
101
|
switch (opts.format) {
|
|
90
102
|
case "bytes":
|
|
91
103
|
return bytes;
|
|
@@ -109,19 +121,29 @@ export class Filesystem {
|
|
|
109
121
|
}
|
|
110
122
|
async #writeOne(path, data, opts) {
|
|
111
123
|
const file = await this.#path(path, opts);
|
|
124
|
+
const as = await this.#as(opts);
|
|
112
125
|
const bytes = await bytesOf(data);
|
|
113
|
-
|
|
126
|
+
if (as)
|
|
127
|
+
await as.write(file, bytes);
|
|
128
|
+
else
|
|
129
|
+
await guard("file", () => this.#files.write(file, bytes, request(opts)));
|
|
114
130
|
return { name: basename(file), type: FileType.FILE, path: file };
|
|
115
131
|
}
|
|
116
132
|
/** A directory's entries, hidden ones included; `depth` goes deeper. */
|
|
117
133
|
async list(path, opts = {}) {
|
|
118
134
|
const dir = await this.#path(path, opts);
|
|
135
|
+
const as = await this.#as(opts);
|
|
136
|
+
if (as)
|
|
137
|
+
return (await as.list(dir, opts.depth ?? 1)).map(entryInfo);
|
|
119
138
|
const entries = await guard("file", () => this.#files.list(dir, { depth: opts.depth ?? 1, hidden: true, ...request(opts) }));
|
|
120
139
|
return entries.map(entryInfo);
|
|
121
140
|
}
|
|
122
141
|
/** Makes a directory and its parents. False when it already existed. */
|
|
123
142
|
async makeDir(path, opts = {}) {
|
|
124
143
|
const dir = await this.#path(path, opts);
|
|
144
|
+
const as = await this.#as(opts);
|
|
145
|
+
if (as)
|
|
146
|
+
return as.makeDir(dir);
|
|
125
147
|
if (await guard("file", () => this.#files.exists(dir, request(opts))))
|
|
126
148
|
return false;
|
|
127
149
|
await guard("file", () => this.#files.mkdir(dir, { parents: true, ...request(opts) }));
|
|
@@ -131,20 +153,33 @@ export class Filesystem {
|
|
|
131
153
|
async rename(oldPath, newPath, opts = {}) {
|
|
132
154
|
const from = await this.#path(oldPath, opts);
|
|
133
155
|
const to = await this.#path(newPath, opts);
|
|
134
|
-
|
|
156
|
+
const as = await this.#as(opts);
|
|
157
|
+
if (as)
|
|
158
|
+
await as.rename(from, to);
|
|
159
|
+
else
|
|
160
|
+
await guard("file", () => this.#files.rename(from, to, { overwrite: true, ...request(opts) }));
|
|
135
161
|
return this.getInfo(to, opts);
|
|
136
162
|
}
|
|
137
163
|
/** Removes a file, or a directory with everything in it. */
|
|
138
164
|
async remove(path, opts = {}) {
|
|
139
165
|
const target = await this.#path(path, opts);
|
|
166
|
+
const as = await this.#as(opts);
|
|
167
|
+
if (as)
|
|
168
|
+
return as.remove(target);
|
|
140
169
|
await guard("file", () => this.#files.remove(target, { recursive: true, ...request(opts) }));
|
|
141
170
|
}
|
|
142
171
|
async exists(path, opts = {}) {
|
|
143
172
|
const target = await this.#path(path, opts);
|
|
173
|
+
const as = await this.#as(opts);
|
|
174
|
+
if (as)
|
|
175
|
+
return as.exists(target);
|
|
144
176
|
return guard("file", () => this.#files.exists(target, request(opts)));
|
|
145
177
|
}
|
|
146
178
|
async getInfo(path, opts = {}) {
|
|
147
179
|
const target = await this.#path(path, opts);
|
|
180
|
+
const as = await this.#as(opts);
|
|
181
|
+
if (as)
|
|
182
|
+
return entryInfo(await as.stat(target));
|
|
148
183
|
const stat = await guard("file", () => this.#files.stat(target, request(opts)));
|
|
149
184
|
if (!stat.exists)
|
|
150
185
|
throw new FileNotFoundError(`${target} does not exist.`);
|
|
@@ -154,6 +189,8 @@ export class Filesystem {
|
|
|
154
189
|
* subsecond subscriptions; 0 runs until stopped or the sandbox ends. */
|
|
155
190
|
async watchDir(path, onEvent, opts = {}) {
|
|
156
191
|
opts.signal?.throwIfAborted();
|
|
192
|
+
if (opts.user !== undefined && opts.user !== "user")
|
|
193
|
+
throw new NotSupportedError(`Watching a directory as the user "${opts.user}"`, "Watch it as the sandbox's own user (leave user out); Runtime's watch reports changes made by anyone.");
|
|
157
194
|
if (opts.allowNetworkMounts)
|
|
158
195
|
throw new NotSupportedError("Watching network mounts", "Watch a local sandbox directory instead.");
|
|
159
196
|
const lifetime = opts.timeoutMs ?? 60000;
|
package/dist/e2b/index.d.ts
CHANGED
|
@@ -10,8 +10,9 @@ import { Sandbox } from "./sandbox.js";
|
|
|
10
10
|
export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, type SandboxOpts, type SandboxConnectOpts, type SandboxApiOpts, type SandboxInfo, type SandboxListOpts, type SandboxState, type SandboxLifecycle, type SandboxOnTimeout, type SnapshotInfo, } from "./sandbox.js";
|
|
11
11
|
export { Commands, CommandHandle, pidOf, type CommandStartOpts, type CommandConnectOpts, type CommandRequestOpts, type ProcessInfo, type Username, } from "./commands.js";
|
|
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
|
-
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, type CommandResult, } from "./errors.js";
|
|
14
|
-
export type { ConnectionOpts, RuntimeOpts } from "./client.js";
|
|
13
|
+
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, PublicPreviewNotAllowedError, type CommandResult, } from "./errors.js";
|
|
14
|
+
export type { ConnectionOpts, HttpVersion, 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
|
@@ -3,7 +3,8 @@ import { Sandbox } from "./sandbox.js";
|
|
|
3
3
|
export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, } from "./sandbox.js";
|
|
4
4
|
export { Commands, CommandHandle, pidOf, } from "./commands.js";
|
|
5
5
|
export { Filesystem, FileType, FilesystemEventType, } from "./filesystem.js";
|
|
6
|
-
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, } from "./errors.js";
|
|
6
|
+
export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, PublicPreviewNotAllowedError, } 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
|
+
}
|