withruntime 0.10.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/DAYTONA.md +40 -34
- package/E2B.md +56 -57
- package/VERCEL.md +21 -19
- package/compatibility-lock.json +8 -84
- package/dist/cli.js +73 -2
- package/dist/client.d.ts +12 -0
- package/dist/client.js +27 -0
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- 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 +123 -16
- package/dist/daytona/filesystem.js +8 -0
- package/dist/daytona/index.d.ts +2 -26
- 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 +19 -5
- package/dist/daytona/sandbox.js +39 -25
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +5 -1
- package/dist/e2b/commands.d.ts +15 -6
- package/dist/e2b/commands.js +83 -18
- 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 +2 -2
- package/dist/e2b/index.js +1 -1
- package/dist/e2b/pty.js +6 -3
- package/dist/e2b/sandbox.d.ts +15 -5
- package/dist/e2b/sandbox.js +190 -28
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/index.d.ts +1 -0
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/network-cli.js +28 -0
- 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 +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/sandbox.d.ts +22 -0
- package/dist/sandbox.js +47 -0
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/types.d.ts +5 -2
- 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 +47 -12
- package/dist/vercel/sandbox.js +201 -42
- package/package.json +1 -1
package/dist/e2b/client.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Runtime } from "../client.js";
|
|
|
3
3
|
export type RuntimeSandbox = Awaited<ReturnType<Runtime["sandboxes"]["create"]>>;
|
|
4
4
|
/** What `Sandbox.create` sends to Runtime. */
|
|
5
5
|
export type RuntimeCreate = NonNullable<Parameters<Runtime["sandboxes"]["create"]>[0]>;
|
|
6
|
+
/** E2B's HTTP version for its API and sandboxes (e2b 2.52.0). */
|
|
7
|
+
export type HttpVersion = "1.1" | "2";
|
|
6
8
|
/** E2B's connection options, as far as they mean anything on Runtime. */
|
|
7
9
|
export interface ConnectionOpts {
|
|
8
10
|
/** A Runtime key. Default: RUNTIME_API_KEY, then E2B_API_KEY if it holds a
|
|
@@ -18,6 +20,9 @@ export interface ConnectionOpts {
|
|
|
18
20
|
retries?: number;
|
|
19
21
|
/** Accepted and ignored: Runtime's SDK does not take a logger. */
|
|
20
22
|
logger?: unknown;
|
|
23
|
+
/** Checked as E2B checks it (and E2B_HTTP_VERSION), then left to Runtime's
|
|
24
|
+
* SDK, which picks its own transport. */
|
|
25
|
+
httpVersion?: HttpVersion;
|
|
21
26
|
/** E2B endpoints. Refused: this package only talks to Runtime. */
|
|
22
27
|
domain?: string;
|
|
23
28
|
apiUrl?: string;
|
package/dist/e2b/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Runtime } from "../client.js";
|
|
2
|
-
import { AuthenticationError, NotSupportedError } from "./errors.js";
|
|
2
|
+
import { AuthenticationError, InvalidArgumentError, NotSupportedError } from "./errors.js";
|
|
3
3
|
const env = (name) => typeof process === "undefined" ? undefined : process.env[name];
|
|
4
4
|
const isE2BKey = (key) => key.startsWith("e2b_");
|
|
5
5
|
/** Refuses the E2B-only connection options: they point at E2B, and quietly
|
|
@@ -18,6 +18,10 @@ export function refuseE2BConnection(opts = {}) {
|
|
|
18
18
|
? "Set HTTPS_PROXY in the environment instead; Runtime's SDK reads it."
|
|
19
19
|
: "Remove it: this package talks only to Runtime (RUNTIME_API_URL overrides the API origin).");
|
|
20
20
|
}
|
|
21
|
+
// E2B refuses any other version, from the option or the environment.
|
|
22
|
+
const version = opts.httpVersion ?? (env("E2B_HTTP_VERSION") || undefined);
|
|
23
|
+
if (version !== undefined && version !== "1.1" && version !== "2")
|
|
24
|
+
throw new InvalidArgumentError(`${opts.httpVersion === undefined ? "E2B_HTTP_VERSION" : "httpVersion"} must be '1.1' or '2', got '${version}'`);
|
|
21
25
|
if (opts.debug)
|
|
22
26
|
throw new NotSupportedError("E2B's debug mode (a local envd)", "Remove debug: true; there is no local Runtime guest to connect to.");
|
|
23
27
|
}
|
package/dist/e2b/commands.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Process } from "../sandbox.js";
|
|
2
|
-
import type { ProcessInfo as RuntimeProcessInfo } from "../types.js";
|
|
2
|
+
import type { OutputEvent, ProcessInfo as RuntimeProcessInfo } from "../types.js";
|
|
3
3
|
import type { RequestOptions } from "../transport.js";
|
|
4
4
|
import type { RuntimeSandbox } from "./client.js";
|
|
5
5
|
import { TimeoutError, type CommandResult } from "./errors.js";
|
|
@@ -12,9 +12,9 @@ export interface CommandRequestOpts {
|
|
|
12
12
|
export interface CommandStartOpts extends CommandRequestOpts {
|
|
13
13
|
background?: boolean;
|
|
14
14
|
cwd?: string;
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
/** "user" (the default) is the sandbox's own user, `runtime`; any other
|
|
16
|
+
* user that exists in the sandbox, `root` included, runs it through
|
|
17
|
+
* `sudo -u`. An unknown user is refused, never created. */
|
|
18
18
|
user?: Username;
|
|
19
19
|
envs?: Record<string, string>;
|
|
20
20
|
onStdout?: (data: string) => void | Promise<void>;
|
|
@@ -47,7 +47,6 @@ export interface SandboxContext {
|
|
|
47
47
|
/** E2B's pids are numbers; Runtime's process ids are strings. The number is a
|
|
48
48
|
* 31-bit FNV-1a hash of the id, so every client derives the same one. */
|
|
49
49
|
export declare function pidOf(processId: string): number;
|
|
50
|
-
export declare function refuseUser(user: Username | undefined): void;
|
|
51
50
|
export declare function timeoutFor(timeoutMs: number | undefined): number;
|
|
52
51
|
/** One budget for lookup and mutation, including retries and queued input. */
|
|
53
52
|
export declare function commandRequest(opts: CommandRequestOpts, defaultTimeoutMs?: number): RequestOptions;
|
|
@@ -88,13 +87,23 @@ export declare class Commands {
|
|
|
88
87
|
/** Attaches to a running command; output from now on reaches the handle. */
|
|
89
88
|
connect(pid: number, opts?: CommandConnectOpts): Promise<CommandHandle>;
|
|
90
89
|
}
|
|
90
|
+
/** A command whose output this handle reads from the request that started
|
|
91
|
+
* it: its process id, the rest of its events, the controller that ends the
|
|
92
|
+
* read, and the Runtime process, fetched only when something needs it. */
|
|
93
|
+
export interface StartedStream {
|
|
94
|
+
started: true;
|
|
95
|
+
id: string;
|
|
96
|
+
events: AsyncIterator<OutputEvent>;
|
|
97
|
+
abort: AbortController;
|
|
98
|
+
process: () => Promise<Process>;
|
|
99
|
+
}
|
|
91
100
|
/** A command started in the background: E2B's CommandHandle over a Runtime
|
|
92
101
|
* process. Output streams into `stdout`/`stderr` and the callbacks as it
|
|
93
102
|
* arrives. */
|
|
94
103
|
export declare class CommandHandle {
|
|
95
104
|
#private;
|
|
96
105
|
readonly pid: number;
|
|
97
|
-
constructor(
|
|
106
|
+
constructor(source: Process | StartedStream, options: {
|
|
98
107
|
stdin: boolean;
|
|
99
108
|
timeoutMs: number;
|
|
100
109
|
cursor?: number;
|
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,10 +14,6 @@ export function pidOf(processId) {
|
|
|
10
14
|
}
|
|
11
15
|
return hash >>> 1 || 1;
|
|
12
16
|
}
|
|
13
|
-
export function refuseUser(user) {
|
|
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
17
|
export function timeoutFor(timeoutMs) {
|
|
18
18
|
if (timeoutMs === undefined)
|
|
19
19
|
return DEFAULT_TIMEOUT_MS;
|
|
@@ -121,14 +121,61 @@ export class Commands {
|
|
|
121
121
|
}
|
|
122
122
|
async run(cmd, opts = {}) {
|
|
123
123
|
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
124
|
-
refuseUser(opts.user);
|
|
125
124
|
const handle = await (async () => {
|
|
125
|
+
const user = await runAs(this.#ctx, opts.user, opening.request);
|
|
126
126
|
await this.#ctx.ensureHome(`${cmd}\n${opts.cwd ?? ""}`, opening.request);
|
|
127
127
|
opening.request.signal?.throwIfAborted();
|
|
128
|
-
|
|
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);
|
|
129
135
|
})().catch(opening.failure);
|
|
130
136
|
return opts.background ? handle : handle.wait();
|
|
131
137
|
}
|
|
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
|
+
}
|
|
132
179
|
async #start(cmd, opts, timeoutMs, request, deadline) {
|
|
133
180
|
request.signal?.throwIfAborted();
|
|
134
181
|
const process = await guard("sandbox", () => this.#ctx.runtime.spawn(cmd, {
|
|
@@ -224,11 +271,12 @@ async function deliver(signal, callback) {
|
|
|
224
271
|
* arrives. */
|
|
225
272
|
export class CommandHandle {
|
|
226
273
|
pid;
|
|
274
|
+
#source;
|
|
227
275
|
#process;
|
|
228
276
|
#stdin;
|
|
229
277
|
#timeoutMs;
|
|
230
278
|
#requestTimeoutMs;
|
|
231
|
-
#abort
|
|
279
|
+
#abort;
|
|
232
280
|
#done;
|
|
233
281
|
#stdout = "";
|
|
234
282
|
#stderr = "";
|
|
@@ -237,9 +285,10 @@ export class CommandHandle {
|
|
|
237
285
|
#failure;
|
|
238
286
|
#disconnected = false;
|
|
239
287
|
#deadline;
|
|
240
|
-
constructor(
|
|
241
|
-
this.#
|
|
242
|
-
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);
|
|
243
292
|
this.#stdin = options.stdin;
|
|
244
293
|
this.#timeoutMs = options.timeoutMs;
|
|
245
294
|
this.#requestTimeoutMs = options.requestTimeoutMs;
|
|
@@ -260,14 +309,17 @@ export class CommandHandle {
|
|
|
260
309
|
const signal = options.signal
|
|
261
310
|
? AbortSignal.any([options.signal, this.#abort.signal])
|
|
262
311
|
: this.#abort.signal;
|
|
312
|
+
const source = this.#source;
|
|
263
313
|
try {
|
|
264
314
|
const outputOptions = {
|
|
265
315
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
266
316
|
signal,
|
|
267
317
|
};
|
|
268
|
-
const output =
|
|
269
|
-
?
|
|
270
|
-
:
|
|
318
|
+
const output = "started" in source
|
|
319
|
+
? { [Symbol.asyncIterator]: () => source.events }
|
|
320
|
+
: options.pty
|
|
321
|
+
? source.outputBytes(outputOptions)
|
|
322
|
+
: source.output(outputOptions);
|
|
271
323
|
for await (const event of output) {
|
|
272
324
|
if (this.#disconnected)
|
|
273
325
|
break;
|
|
@@ -288,7 +340,8 @@ export class CommandHandle {
|
|
|
288
340
|
this.#truncated = true;
|
|
289
341
|
}
|
|
290
342
|
else if (event.type === "exit") {
|
|
291
|
-
|
|
343
|
+
const exit = event;
|
|
344
|
+
this.#exit = { exitCode: exit.exitCode, timedOut: exit.timedOut };
|
|
292
345
|
}
|
|
293
346
|
}
|
|
294
347
|
}
|
|
@@ -348,12 +401,22 @@ export class CommandHandle {
|
|
|
348
401
|
this.#abort.abort();
|
|
349
402
|
await Promise.resolve();
|
|
350
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
|
+
}
|
|
351
413
|
/** Kills the command with SIGKILL. False when it had already ended. */
|
|
352
414
|
async kill() {
|
|
353
415
|
if (this.#exit)
|
|
354
416
|
return false;
|
|
355
417
|
try {
|
|
356
|
-
|
|
418
|
+
const process = await this.#runtimeProcess();
|
|
419
|
+
await guard("sandbox", () => process.kill("SIGKILL", commandRequest({}, this.#requestTimeoutMs)));
|
|
357
420
|
return true;
|
|
358
421
|
}
|
|
359
422
|
catch (error) {
|
|
@@ -366,12 +429,14 @@ export class CommandHandle {
|
|
|
366
429
|
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
367
430
|
if (!this.#stdin)
|
|
368
431
|
throw new InvalidArgumentError("The command was not started with stdin: true, so its input is closed.");
|
|
369
|
-
|
|
432
|
+
const process = await this.#runtimeProcess();
|
|
433
|
+
await guard("sandbox", () => process.write(data, request));
|
|
370
434
|
}
|
|
371
435
|
async closeStdin(opts = {}) {
|
|
372
436
|
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
373
437
|
if (!this.#stdin)
|
|
374
438
|
throw new InvalidArgumentError("The command was not started with stdin: true.");
|
|
375
|
-
|
|
439
|
+
const process = await this.#runtimeProcess();
|
|
440
|
+
await guard("sandbox", () => process.write("", { ...request, eof: true }));
|
|
376
441
|
}
|
|
377
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,8 @@ 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
15
|
export { Pty, type PtyCreateOpts, type PtyConnectOpts } from "./pty.js";
|
|
16
16
|
export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
|
|
17
17
|
export default Sandbox;
|
package/dist/e2b/index.js
CHANGED
|
@@ -3,7 +3,7 @@ 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
7
|
export { Pty } from "./pty.js";
|
|
8
8
|
export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
|
|
9
9
|
export default Sandbox;
|
package/dist/e2b/pty.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CommandHandle, commandRequest, connectionRequest, killProcess,
|
|
1
|
+
import { CommandHandle, commandRequest, connectionRequest, killProcess, resolveProcess, } from "./commands.js";
|
|
2
2
|
import { guard, InvalidArgumentError, NotSupportedError } from "./errors.js";
|
|
3
|
+
import { runAs, shellAs } from "./users.js";
|
|
3
4
|
function dimensions(size) {
|
|
4
5
|
for (const value of [size.cols, size.rows])
|
|
5
6
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
@@ -13,16 +14,18 @@ export class Pty {
|
|
|
13
14
|
this.ctx = ctx;
|
|
14
15
|
}
|
|
15
16
|
async create(opts) {
|
|
16
|
-
refuseUser(opts.user);
|
|
17
17
|
const pty = dimensions(opts);
|
|
18
18
|
const opening = connectionRequest(opts, this.ctx.requestTimeoutMs);
|
|
19
19
|
if (typeof opts.onData !== "function")
|
|
20
20
|
throw new InvalidArgumentError("onData must be a function.");
|
|
21
21
|
const env = { TERM: "xterm-256color", LANG: "C.UTF-8", LC_ALL: "C.UTF-8", ...opts.envs };
|
|
22
22
|
const process = await guard("sandbox", async () => {
|
|
23
|
+
// Another user's terminal is a login shell of theirs, through sudo -u.
|
|
24
|
+
const user = await runAs(this.ctx, opts.user, opening.request);
|
|
23
25
|
await this.ctx.ensureHome(opts.cwd, opening.request);
|
|
24
26
|
opening.request.signal?.throwIfAborted();
|
|
25
|
-
|
|
27
|
+
const shell = user ? shellAs(user, opts.cwd !== undefined) : ["/bin/bash", "-i", "-l"];
|
|
28
|
+
return this.ctx.runtime.spawn(shell, {
|
|
26
29
|
pty,
|
|
27
30
|
stdin: "pipe",
|
|
28
31
|
outputEncoding: "base64",
|
package/dist/e2b/sandbox.d.ts
CHANGED
|
@@ -30,8 +30,9 @@ export interface SandboxOpts extends ConnectionOpts {
|
|
|
30
30
|
/** The sandbox's own environment: every command, terminal and code run in it
|
|
31
31
|
* gets these, whoever connects. Values are never shown again. */
|
|
32
32
|
envs?: Record<string, string>;
|
|
33
|
-
/** Default 300 000. Runtime's leases run 60 s to 1 hour
|
|
34
|
-
* to 60 s (with a warning)
|
|
33
|
+
/** Default 300 000, at most 24 hours. Runtime's leases run 60 s to 1 hour:
|
|
34
|
+
* shorter rounds up to 60 s (with a warning); longer gets an hour, moved on
|
|
35
|
+
* while this process runs, up to the time asked for. */
|
|
35
36
|
timeoutMs?: number;
|
|
36
37
|
/** Accepted and ignored, as E2B ignores it. */
|
|
37
38
|
secure?: boolean;
|
|
@@ -88,6 +89,10 @@ export type SandboxListOpts = SandboxApiOpts & {
|
|
|
88
89
|
limit?: number;
|
|
89
90
|
nextToken?: string;
|
|
90
91
|
};
|
|
92
|
+
/** Test hook: renew every kept lease now, as the five-minute timer would. */
|
|
93
|
+
export declare function renewLeases(): Promise<void>;
|
|
94
|
+
/** Test hook: the end each kept sandbox is kept until. */
|
|
95
|
+
export declare function keptLeases(): Map<string, number>;
|
|
91
96
|
export declare class Sandbox {
|
|
92
97
|
#private;
|
|
93
98
|
protected readonly requestTimeoutMs: number;
|
|
@@ -118,8 +123,9 @@ export declare class Sandbox {
|
|
|
118
123
|
/** Stops the sandbox. False when it was not found or had already ended. */
|
|
119
124
|
static kill(sandboxId: string, opts?: SandboxApiOpts): Promise<boolean>;
|
|
120
125
|
kill(opts?: Pick<ConnectionOpts, "requestTimeoutMs" | "signal">): Promise<boolean>;
|
|
121
|
-
/** Sets the sandbox to end `timeoutMs` from now
|
|
122
|
-
*
|
|
126
|
+
/** Sets the sandbox to end `timeoutMs` from now, up to 24 hours; past an
|
|
127
|
+
* hour the lease is moved on while this process runs. Runtime cannot end a
|
|
128
|
+
* lease early, so an end sooner than the lease's is refused. */
|
|
123
129
|
static setTimeout(sandboxId: string, timeoutMs: number, opts?: SandboxApiOpts): Promise<void>;
|
|
124
130
|
setTimeout(timeoutMs: number, opts?: Pick<ConnectionOpts, "requestTimeoutMs" | "signal">): Promise<void>;
|
|
125
131
|
static getInfo(sandboxId: string, opts?: SandboxApiOpts): Promise<SandboxInfo>;
|
|
@@ -183,11 +189,15 @@ export declare class Sandbox {
|
|
|
183
189
|
getMcpUrl(): never;
|
|
184
190
|
getMcpToken(): Promise<never>;
|
|
185
191
|
}
|
|
186
|
-
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
192
|
+
/** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`.
|
|
193
|
+
* Runtime filters by state and metadata itself. A template, `startedAfter`,
|
|
194
|
+
* newest-first order or a saved `nextToken` are done here: every match is
|
|
195
|
+
* read once, then served a page at a time. */
|
|
187
196
|
export declare class SandboxPaginator {
|
|
188
197
|
#private;
|
|
189
198
|
constructor(client: Runtime, opts: SandboxListOpts);
|
|
190
199
|
get hasNext(): boolean;
|
|
200
|
+
/** Where the next page starts; `Sandbox.list({ nextToken })` resumes there. */
|
|
191
201
|
get nextToken(): string | undefined;
|
|
192
202
|
nextItems(): Promise<SandboxInfo[]>;
|
|
193
203
|
}
|