withruntime 0.8.4 → 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/COMPATIBILITY.md +145 -0
- package/DAYTONA.md +13 -7
- package/E2B.md +34 -20
- package/README.md +22 -11
- package/README.src.md +22 -11
- package/VERCEL.md +13 -3
- package/compatibility-lock.json +357 -0
- package/dist/ai-harness/index.js +2 -2
- package/dist/api-defaults.d.ts +17 -0
- package/dist/api-defaults.js +22 -0
- 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 +3 -2
- package/dist/blaxel/preview.js +16 -14
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +66 -11
- 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-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +351 -85
- package/dist/client.d.ts +33 -4
- package/dist/client.js +47 -12
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +303 -0
- package/dist/cloudflare/index.js +592 -0
- package/dist/cloudflare/worker.d.ts +419 -0
- package/dist/cloudflare/worker.js +447 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +490 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1347 -0
- package/dist/compat/core.d.ts +40 -0
- package/dist/compat/core.js +238 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +133 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/context.js +2 -1
- 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/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- 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/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +35 -12
- package/dist/e2b/commands.js +169 -73
- package/dist/e2b/errors.js +1 -1
- package/dist/e2b/filesystem.d.ts +8 -6
- package/dist/e2b/filesystem.js +127 -28
- 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 +6 -4
- package/dist/e2b/sandbox.js +59 -25
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +309 -0
- package/dist/freestyle/index.js +343 -0
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +486 -0
- package/dist/network-cli.js +112 -44
- package/dist/openai-agents/index.js +18 -71
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/interpreter.d.ts +11 -4
- package/dist/products/interpreter.js +33 -9
- package/dist/products/jobs.js +3 -2
- package/dist/products/limits.d.ts +10 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +18 -4
- package/dist/products/previews.js +14 -5
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +132 -18
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +326 -0
- package/dist/runloop/index.js +535 -0
- package/dist/runloop/pagination.d.ts +46 -0
- package/dist/runloop/pagination.js +82 -0
- package/dist/runloop/snapshots.d.ts +52 -0
- package/dist/runloop/snapshots.js +244 -0
- package/dist/sandbox.d.ts +76 -16
- package/dist/sandbox.js +696 -135
- package/dist/snapshots.d.ts +20 -5
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +588 -0
- package/dist/tar.d.ts +10 -1
- package/dist/tar.js +311 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +23 -8
- package/dist/transport.js +247 -127
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +15 -3
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +19 -2
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.d.ts +8 -8
- package/dist/vercel/sandbox.js +159 -64
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +46 -3
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* import { Sandbox } from "withruntime/e2b/code-interpreter";
|
|
4
4
|
*
|
|
5
5
|
* runCode runs in Runtime's interpreter (sandbox.runtime.interpreter):
|
|
6
|
-
* stateful
|
|
7
|
-
* NotSupportedError. */
|
|
6
|
+
* stateful contexts in every E2B interpreter language. */
|
|
8
7
|
import type { Runtime } from "../client.js";
|
|
9
8
|
import type { ConnectionOpts } from "./client.js";
|
|
10
9
|
import { Sandbox as BaseSandbox } from "./sandbox.js";
|
|
@@ -13,11 +12,11 @@ export type RunCodeLanguage = "python" | "javascript" | "typescript" | "r" | "ja
|
|
|
13
12
|
/** A line of output, as E2B's callbacks receive it. */
|
|
14
13
|
export declare class OutputMessage {
|
|
15
14
|
readonly line: string;
|
|
16
|
-
/** Unix epoch in
|
|
15
|
+
/** Unix epoch in microseconds (from the client's clock), as in E2B. */
|
|
17
16
|
readonly timestamp: number;
|
|
18
17
|
readonly error: boolean;
|
|
19
18
|
constructor(line: string,
|
|
20
|
-
/** Unix epoch in
|
|
19
|
+
/** Unix epoch in microseconds (from the client's clock), as in E2B. */
|
|
21
20
|
timestamp: number, error: boolean);
|
|
22
21
|
toString(): string;
|
|
23
22
|
}
|
|
@@ -110,7 +109,7 @@ export interface RunCodeOpts {
|
|
|
110
109
|
envs?: Record<string, string>;
|
|
111
110
|
/** Default 60 000, as in E2B. */
|
|
112
111
|
timeoutMs?: number;
|
|
113
|
-
/**
|
|
112
|
+
/** Deadline for receiving response headers; 0 disables it. */
|
|
114
113
|
requestTimeoutMs?: number;
|
|
115
114
|
}
|
|
116
115
|
export interface CreateCodeContextOpts {
|
|
@@ -8,7 +8,7 @@ export class OutputMessage {
|
|
|
8
8
|
timestamp;
|
|
9
9
|
error;
|
|
10
10
|
constructor(line,
|
|
11
|
-
/** Unix epoch in
|
|
11
|
+
/** Unix epoch in microseconds (from the client's clock), as in E2B. */
|
|
12
12
|
timestamp, error) {
|
|
13
13
|
this.line = line;
|
|
14
14
|
this.timestamp = timestamp;
|
|
@@ -122,50 +122,18 @@ function lines(text) {
|
|
|
122
122
|
return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
123
123
|
}
|
|
124
124
|
function language(requested) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
throw new NotSupportedError(`Running ${requested} code in the interpreter`, requested === "bash"
|
|
130
|
-
? "Use sandbox.commands.run(code)."
|
|
131
|
-
: "Runtime's interpreter runs Python and JavaScript; install the language and run it with sandbox.commands.run(...).");
|
|
125
|
+
const selected = requested === "js" ? "javascript" : (requested ?? "python");
|
|
126
|
+
if (["python", "javascript", "typescript", "r", "java", "bash", "go"].includes(selected))
|
|
127
|
+
return selected;
|
|
128
|
+
throw new NotSupportedError(`Running ${requested} code in the interpreter`, "Use Python, JavaScript, TypeScript, R, Java, Bash or Go.");
|
|
132
129
|
}
|
|
133
130
|
/** The code interpreter sandbox: everything the base Sandbox has, plus runCode
|
|
134
131
|
* and code contexts. */
|
|
135
132
|
export class Sandbox extends BaseSandbox {
|
|
136
133
|
static defaultTemplate = "code-interpreter-v1";
|
|
137
|
-
/** Contexts made to carry the sandbox's envs, one per language. */
|
|
138
|
-
#envContexts = new Map();
|
|
139
134
|
get #interpreter() {
|
|
140
135
|
return this.runtime.interpreter;
|
|
141
136
|
}
|
|
142
|
-
/** The context a run goes to: the one asked for; else, when the sandbox has
|
|
143
|
-
* envs, a context made once with them; else Runtime's default one. */
|
|
144
|
-
async #contextFor(lang, context) {
|
|
145
|
-
if (context)
|
|
146
|
-
return context.id;
|
|
147
|
-
if (!Object.keys(this.envs).length)
|
|
148
|
-
return undefined;
|
|
149
|
-
let id = this.#envContexts.get(lang);
|
|
150
|
-
if (!id) {
|
|
151
|
-
id = guard("sandbox", async () => {
|
|
152
|
-
const made = await this.#interpreter.contexts.create({
|
|
153
|
-
id: `e2b-${lang}`,
|
|
154
|
-
language: lang,
|
|
155
|
-
env: this.envs,
|
|
156
|
-
});
|
|
157
|
-
return made.id;
|
|
158
|
-
}).catch(async (error) => {
|
|
159
|
-
// Made already, by another object connected to this sandbox.
|
|
160
|
-
const existing = (await this.#interpreter.contexts.list()).find((one) => one.id === `e2b-${lang}`);
|
|
161
|
-
if (existing)
|
|
162
|
-
return existing.id;
|
|
163
|
-
throw error;
|
|
164
|
-
});
|
|
165
|
-
this.#envContexts.set(lang, id);
|
|
166
|
-
}
|
|
167
|
-
return id;
|
|
168
|
-
}
|
|
169
137
|
async #inline(result) {
|
|
170
138
|
const data = { ...result.data };
|
|
171
139
|
for (const [mime, ref] of Object.entries(result.refs ?? {})) {
|
|
@@ -186,28 +154,59 @@ export class Sandbox extends BaseSandbox {
|
|
|
186
154
|
if (opts.envs && Object.keys(opts.envs).length)
|
|
187
155
|
throw new NotSupportedError("Environment variables for one run (runCode envs)", "Make a context with them: sandbox.runtime.interpreter.contexts.create({ env }), then runCode(code, { context: { id } }).");
|
|
188
156
|
const lang = language(opts.context?.language ?? opts.language);
|
|
189
|
-
const context =
|
|
190
|
-
const now = () => Date.now() *
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
157
|
+
const context = opts.context?.id;
|
|
158
|
+
const now = () => Date.now() * 1_000;
|
|
159
|
+
const streamedResults = [];
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
const requestTimeout = opts.requestTimeoutMs ?? this.requestTimeoutMs;
|
|
162
|
+
const timeout = opts.timeoutMs ?? 60_000;
|
|
163
|
+
let stage = "Request";
|
|
164
|
+
let timer;
|
|
165
|
+
const arm = (milliseconds) => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
if (milliseconds)
|
|
168
|
+
timer = setTimeout(() => controller.abort(), milliseconds);
|
|
169
|
+
};
|
|
170
|
+
arm(requestTimeout);
|
|
171
|
+
let execution;
|
|
172
|
+
try {
|
|
173
|
+
execution = await guard("sandbox", () => this.#interpreter.run(code, {
|
|
174
|
+
...(context ? { context } : { language: lang }),
|
|
175
|
+
// E2B times out its reader; it does not send a kernel deadline.
|
|
176
|
+
timeoutMs: 0,
|
|
177
|
+
requestTimeoutMs: 0,
|
|
178
|
+
interruptOnDisconnect: false,
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
onResponse: () => {
|
|
181
|
+
stage = "Execution";
|
|
182
|
+
arm(timeout);
|
|
183
|
+
},
|
|
184
|
+
// Stream even without callbacks: the header/body deadlines are separate.
|
|
185
|
+
onStdout: (text) => opts.onStdout?.(new OutputMessage(text, now(), false)),
|
|
186
|
+
onStderr: (text) => opts.onStderr?.(new OutputMessage(text, now(), true)),
|
|
187
|
+
onError: (error) => opts.onError?.(new ExecutionError(error.name, error.value, error.traceback)),
|
|
188
|
+
onResult: async (result) => {
|
|
189
|
+
const mapped = new Result(await this.#inline(result), result.main);
|
|
190
|
+
streamedResults.push(mapped);
|
|
191
|
+
await opts.onResult?.(mapped);
|
|
192
|
+
},
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (controller.signal.aborted)
|
|
197
|
+
throw new TimeoutError(`${stage} timed out — the '${stage === "Request" ? "requestTimeoutMs" : "timeoutMs"}' option can be used to increase this timeout`);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
}
|
|
203
203
|
if (execution.status === "timeout")
|
|
204
204
|
throw new TimeoutError(`Execution timed out after ${opts.timeoutMs ?? 60_000} ms: pass a larger 'timeoutMs'.`);
|
|
205
205
|
if (execution.status === "lost")
|
|
206
206
|
throw new SandboxError("The interpreter lost this run (its context stopped); run it again.");
|
|
207
|
-
const results =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
await opts.onResult(result);
|
|
207
|
+
const results = streamedResults.length === execution.results.length
|
|
208
|
+
? streamedResults
|
|
209
|
+
: await Promise.all(execution.results.map(async (result) => new Result(await this.#inline(result), result.main)));
|
|
211
210
|
return new Execution(results, { stdout: lines(execution.stdout), stderr: lines(execution.stderr) }, execution.error
|
|
212
211
|
? new ExecutionError(execution.error.name, execution.error.value, execution.error.traceback)
|
|
213
212
|
: undefined, execution.executionCount ?? undefined);
|
|
@@ -217,7 +216,6 @@ export class Sandbox extends BaseSandbox {
|
|
|
217
216
|
const made = await guard("sandbox", () => this.#interpreter.contexts.create({
|
|
218
217
|
language: lang,
|
|
219
218
|
...(opts.cwd ? { cwd: opts.cwd } : {}),
|
|
220
|
-
...(Object.keys(this.envs).length ? { env: this.envs } : {}),
|
|
221
219
|
}));
|
|
222
220
|
return { id: made.id, language: made.language, cwd: made.cwd };
|
|
223
221
|
}
|
package/dist/e2b/commands.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Process } from "../sandbox.js";
|
|
2
|
+
import type { ProcessInfo as RuntimeProcessInfo } from "../types.js";
|
|
3
|
+
import type { RequestOptions } from "../transport.js";
|
|
2
4
|
import type { RuntimeSandbox } from "./client.js";
|
|
3
|
-
import { type CommandResult } from "./errors.js";
|
|
5
|
+
import { TimeoutError, type CommandResult } from "./errors.js";
|
|
4
6
|
export type { CommandResult };
|
|
5
7
|
export type Username = "user" | "root" | (string & {});
|
|
6
8
|
export interface CommandRequestOpts {
|
|
@@ -18,7 +20,7 @@ export interface CommandStartOpts extends CommandRequestOpts {
|
|
|
18
20
|
onStdout?: (data: string) => void | Promise<void>;
|
|
19
21
|
onStderr?: (data: string) => void | Promise<void>;
|
|
20
22
|
stdin?: boolean;
|
|
21
|
-
/**
|
|
23
|
+
/** Stream connection deadline, 60 000 by default. Zero waits without a deadline. */
|
|
22
24
|
timeoutMs?: number;
|
|
23
25
|
}
|
|
24
26
|
export type CommandConnectOpts = Pick<CommandStartOpts, "onStderr" | "onStdout" | "timeoutMs"> & CommandRequestOpts;
|
|
@@ -32,18 +34,34 @@ export interface ProcessInfo {
|
|
|
32
34
|
envs: Record<string, string>;
|
|
33
35
|
cwd?: string;
|
|
34
36
|
}
|
|
35
|
-
/** The shared state a sandbox's modules need: the Runtime sandbox
|
|
36
|
-
*
|
|
37
|
+
/** The shared state a sandbox's modules need: the Runtime sandbox and the
|
|
38
|
+
* one-time /home/user link. The environment given at create is Runtime's to
|
|
39
|
+
* keep: it is the sandbox's own `env`, added to every command there. */
|
|
37
40
|
export interface SandboxContext {
|
|
38
41
|
readonly runtime: RuntimeSandbox;
|
|
39
|
-
readonly
|
|
42
|
+
readonly requestTimeoutMs?: number;
|
|
40
43
|
/** Makes /home/user (E2B's home) lead to /workspace (Runtime's), once, when
|
|
41
44
|
* something names it. */
|
|
42
|
-
ensureHome(text: string | undefined): Promise<void>;
|
|
45
|
+
ensureHome(text: string | undefined, options?: RequestOptions): Promise<void>;
|
|
43
46
|
}
|
|
44
47
|
/** E2B's pids are numbers; Runtime's process ids are strings. The number is a
|
|
45
48
|
* 31-bit FNV-1a hash of the id, so every client derives the same one. */
|
|
46
49
|
export declare function pidOf(processId: string): number;
|
|
50
|
+
export declare function refuseUser(user: Username | undefined): void;
|
|
51
|
+
export declare function timeoutFor(timeoutMs: number | undefined): number;
|
|
52
|
+
/** One budget for lookup and mutation, including retries and queued input. */
|
|
53
|
+
export declare function commandRequest(opts: CommandRequestOpts, defaultTimeoutMs?: number): RequestOptions;
|
|
54
|
+
export declare function findProcess(ctx: SandboxContext, pid: number, options: RequestOptions): Promise<RuntimeProcessInfo | undefined>;
|
|
55
|
+
export declare function resolveProcess(ctx: SandboxContext, pid: number, options: RequestOptions): Promise<Process>;
|
|
56
|
+
export declare function killProcess(ctx: SandboxContext, pid: number, options: RequestOptions): Promise<boolean>;
|
|
57
|
+
export declare function timedOut(timeoutMs: number): TimeoutError;
|
|
58
|
+
/** The subscription deadline includes the opening handshake. */
|
|
59
|
+
export declare function connectionRequest(opts: CommandConnectOpts, defaultTimeoutMs?: number): {
|
|
60
|
+
timeoutMs: number;
|
|
61
|
+
deadline: number | undefined;
|
|
62
|
+
request: RequestOptions;
|
|
63
|
+
failure: (error: unknown) => never;
|
|
64
|
+
};
|
|
47
65
|
/** `sandbox.commands`: E2B's command module over Runtime's exec and processes. */
|
|
48
66
|
export declare class Commands {
|
|
49
67
|
#private;
|
|
@@ -61,12 +79,12 @@ export declare class Commands {
|
|
|
61
79
|
background?: boolean;
|
|
62
80
|
}): Promise<CommandHandle | CommandResult>;
|
|
63
81
|
/** Running commands. */
|
|
64
|
-
list(
|
|
82
|
+
list(opts?: CommandRequestOpts): Promise<ProcessInfo[]>;
|
|
65
83
|
/** Kills a command with SIGKILL, as E2B does. False when there is none. */
|
|
66
|
-
kill(pid: number,
|
|
84
|
+
kill(pid: number, opts?: CommandRequestOpts): Promise<boolean>;
|
|
67
85
|
/** Sends input to a command started with `stdin: true`. */
|
|
68
|
-
sendStdin(pid: number, data: string | Uint8Array,
|
|
69
|
-
closeStdin(pid: number,
|
|
86
|
+
sendStdin(pid: number, data: string | Uint8Array, opts?: CommandRequestOpts): Promise<void>;
|
|
87
|
+
closeStdin(pid: number, opts?: CommandRequestOpts): Promise<void>;
|
|
70
88
|
/** Attaches to a running command; output from now on reaches the handle. */
|
|
71
89
|
connect(pid: number, opts?: CommandConnectOpts): Promise<CommandHandle>;
|
|
72
90
|
}
|
|
@@ -80,8 +98,13 @@ export declare class CommandHandle {
|
|
|
80
98
|
stdin: boolean;
|
|
81
99
|
timeoutMs: number;
|
|
82
100
|
cursor?: number;
|
|
101
|
+
signal?: AbortSignal;
|
|
83
102
|
onStdout?: (data: string) => void | Promise<void>;
|
|
84
103
|
onStderr?: (data: string) => void | Promise<void>;
|
|
104
|
+
onData?: (data: Uint8Array) => void | Promise<void>;
|
|
105
|
+
pty?: boolean;
|
|
106
|
+
requestTimeoutMs?: number;
|
|
107
|
+
deadline?: number;
|
|
85
108
|
});
|
|
86
109
|
/** The exit code, or undefined while the command runs. */
|
|
87
110
|
get exitCode(): number | undefined;
|
|
@@ -95,6 +118,6 @@ export declare class CommandHandle {
|
|
|
95
118
|
disconnect(): Promise<void>;
|
|
96
119
|
/** Kills the command with SIGKILL. False when it had already ended. */
|
|
97
120
|
kill(): Promise<boolean>;
|
|
98
|
-
sendStdin(data: string | Uint8Array,
|
|
99
|
-
closeStdin(
|
|
121
|
+
sendStdin(data: string | Uint8Array, opts?: CommandRequestOpts): Promise<void>;
|
|
122
|
+
closeStdin(opts?: CommandRequestOpts): Promise<void>;
|
|
100
123
|
}
|
package/dist/e2b/commands.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { request } from "./client.js";
|
|
2
1
|
import { CommandExitError, guard, InvalidArgumentError, NotSupportedError, SandboxError, TimeoutError, translate, } from "./errors.js";
|
|
3
2
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
4
|
-
const LONGEST_MS = 86_400_000;
|
|
5
3
|
/** E2B's pids are numbers; Runtime's process ids are strings. The number is a
|
|
6
4
|
* 31-bit FNV-1a hash of the id, so every client derives the same one. */
|
|
7
5
|
export function pidOf(processId) {
|
|
@@ -12,18 +10,57 @@ export function pidOf(processId) {
|
|
|
12
10
|
}
|
|
13
11
|
return hash >>> 1 || 1;
|
|
14
12
|
}
|
|
15
|
-
function refuseUser(user) {
|
|
13
|
+
export function refuseUser(user) {
|
|
16
14
|
if (user !== undefined && user !== "user")
|
|
17
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.');
|
|
18
16
|
}
|
|
19
|
-
function timeoutFor(timeoutMs) {
|
|
17
|
+
export function timeoutFor(timeoutMs) {
|
|
20
18
|
if (timeoutMs === undefined)
|
|
21
19
|
return DEFAULT_TIMEOUT_MS;
|
|
22
|
-
if (timeoutMs
|
|
23
|
-
|
|
24
|
-
return
|
|
20
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)
|
|
21
|
+
throw new InvalidArgumentError("timeoutMs must be a nonnegative whole number.");
|
|
22
|
+
return timeoutMs;
|
|
25
23
|
}
|
|
26
|
-
|
|
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) {
|
|
27
64
|
return new TimeoutError(`Command timed out after ${timeoutMs} ms: this error is likely due to exceeding 'timeoutMs'. ` +
|
|
28
65
|
"Pass a larger 'timeoutMs', or 0 for no limit.");
|
|
29
66
|
}
|
|
@@ -37,13 +74,32 @@ function toResult(result) {
|
|
|
37
74
|
stdout: result.stdout,
|
|
38
75
|
stderr: result.stderr,
|
|
39
76
|
...(exitCode === 0
|
|
40
|
-
? {}
|
|
77
|
+
? { error: "" }
|
|
41
78
|
: {
|
|
42
79
|
error: result.exitCode === null ? "terminated by a signal" : `exit status ${exitCode}`,
|
|
43
80
|
}),
|
|
44
81
|
...(result.truncated ? { truncated: true } : {}),
|
|
45
82
|
};
|
|
46
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
|
+
}
|
|
47
103
|
function settle(result, timeoutMs) {
|
|
48
104
|
if (result.timedOut)
|
|
49
105
|
throw timedOut(timeoutMs);
|
|
@@ -61,88 +117,72 @@ export class Commands {
|
|
|
61
117
|
this.#ctx = ctx;
|
|
62
118
|
}
|
|
63
119
|
#env(envs) {
|
|
64
|
-
|
|
65
|
-
return Object.keys(merged).length ? { env: merged } : {};
|
|
120
|
+
return envs && Object.keys(envs).length ? { env: envs } : {};
|
|
66
121
|
}
|
|
67
122
|
async run(cmd, opts = {}) {
|
|
123
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
68
124
|
refuseUser(opts.user);
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/* Always streamed, so the result holds the whole output as E2B's does: an
|
|
76
|
-
exec with no output callback returns at most 64 KiB of each stream. */
|
|
77
|
-
const onStdout = opts.onStdout;
|
|
78
|
-
const onStderr = opts.onStderr;
|
|
79
|
-
const result = await guard("sandbox", () => this.#ctx.runtime.exec(cmd, {
|
|
80
|
-
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
81
|
-
...this.#env(opts.envs),
|
|
82
|
-
timeoutMs,
|
|
83
|
-
onStdout: (text) => void onStdout?.(text),
|
|
84
|
-
...(onStderr ? { onStderr: (text) => void onStderr(text) } : {}),
|
|
85
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
86
|
-
}));
|
|
87
|
-
return settle({ ...result, truncated: result.stdoutTruncated === true || result.stderrTruncated === true }, timeoutMs);
|
|
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);
|
|
130
|
+
return opts.background ? handle : handle.wait();
|
|
88
131
|
}
|
|
89
|
-
async #start(cmd, opts, timeoutMs) {
|
|
132
|
+
async #start(cmd, opts, timeoutMs, request, deadline) {
|
|
133
|
+
request.signal?.throwIfAborted();
|
|
90
134
|
const process = await guard("sandbox", () => this.#ctx.runtime.spawn(cmd, {
|
|
91
135
|
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
92
136
|
...this.#env(opts.envs),
|
|
93
|
-
timeoutMs,
|
|
94
137
|
...(opts.stdin ? { stdin: "pipe" } : {}),
|
|
95
|
-
|
|
138
|
+
request,
|
|
96
139
|
}));
|
|
97
140
|
return new CommandHandle(process, {
|
|
98
141
|
stdin: opts.stdin === true,
|
|
99
142
|
timeoutMs,
|
|
143
|
+
deadline,
|
|
144
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
145
|
+
signal: opts.signal,
|
|
100
146
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
101
147
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
102
148
|
});
|
|
103
149
|
}
|
|
104
150
|
/** Running commands. */
|
|
105
|
-
async list(
|
|
106
|
-
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));
|
|
107
154
|
return processes.filter((info) => info.state === "running").map(describe);
|
|
108
155
|
}
|
|
109
|
-
async #find(pid) {
|
|
110
|
-
const processes = await guard("sandbox", () => this.#ctx.runtime.processes.list());
|
|
111
|
-
return processes.find((info) => pidOf(info.id) === pid && info.state === "running");
|
|
112
|
-
}
|
|
113
|
-
async #process(pid) {
|
|
114
|
-
const info = await this.#find(pid);
|
|
115
|
-
if (!info)
|
|
116
|
-
throw new SandboxError(`No running command with pid ${pid}.`);
|
|
117
|
-
return guard("sandbox", () => this.#ctx.runtime.processes.get(info.id));
|
|
118
|
-
}
|
|
119
156
|
/** Kills a command with SIGKILL, as E2B does. False when there is none. */
|
|
120
|
-
async kill(pid,
|
|
121
|
-
|
|
122
|
-
if (!info)
|
|
123
|
-
return false;
|
|
124
|
-
const process = await guard("sandbox", () => this.#ctx.runtime.processes.get(info.id));
|
|
125
|
-
await guard("sandbox", () => process.kill("SIGKILL"));
|
|
126
|
-
return true;
|
|
157
|
+
async kill(pid, opts = {}) {
|
|
158
|
+
return killProcess(this.#ctx, pid, commandRequest(opts, this.#ctx.requestTimeoutMs));
|
|
127
159
|
}
|
|
128
160
|
/** Sends input to a command started with `stdin: true`. */
|
|
129
|
-
async sendStdin(pid, data,
|
|
130
|
-
const
|
|
161
|
+
async sendStdin(pid, data, opts = {}) {
|
|
162
|
+
const request = commandRequest(opts, this.#ctx.requestTimeoutMs);
|
|
163
|
+
const process = await resolveProcess(this.#ctx, pid, request);
|
|
131
164
|
if (!process.info.stdinOpen)
|
|
132
165
|
throw new InvalidArgumentError(`The command with pid ${pid} was not started with stdin: true, so its input is closed.`);
|
|
133
|
-
|
|
166
|
+
request.signal?.throwIfAborted();
|
|
167
|
+
await guard("sandbox", () => process.write(data, request));
|
|
134
168
|
}
|
|
135
|
-
async closeStdin(pid,
|
|
136
|
-
const
|
|
137
|
-
|
|
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 }));
|
|
138
174
|
}
|
|
139
175
|
/** Attaches to a running command; output from now on reaches the handle. */
|
|
140
176
|
async connect(pid, opts = {}) {
|
|
141
|
-
const
|
|
177
|
+
const opening = connectionRequest(opts, this.#ctx.requestTimeoutMs);
|
|
178
|
+
const process = await resolveProcess(this.#ctx, pid, opening.request).catch(opening.failure);
|
|
142
179
|
return new CommandHandle(process, {
|
|
143
180
|
stdin: process.info.stdinOpen,
|
|
144
|
-
timeoutMs:
|
|
181
|
+
timeoutMs: opening.timeoutMs,
|
|
182
|
+
deadline: opening.deadline,
|
|
183
|
+
signal: opts.signal,
|
|
145
184
|
cursor: process.info.outputBytes,
|
|
185
|
+
requestTimeoutMs: this.#ctx.requestTimeoutMs,
|
|
146
186
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
147
187
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
148
188
|
});
|
|
@@ -157,6 +197,28 @@ function describe(info) {
|
|
|
157
197
|
cwd: info.cwd,
|
|
158
198
|
};
|
|
159
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
|
+
}
|
|
160
222
|
/** A command started in the background: E2B's CommandHandle over a Runtime
|
|
161
223
|
* process. Output streams into `stdout`/`stderr` and the callbacks as it
|
|
162
224
|
* arrives. */
|
|
@@ -165,6 +227,7 @@ export class CommandHandle {
|
|
|
165
227
|
#process;
|
|
166
228
|
#stdin;
|
|
167
229
|
#timeoutMs;
|
|
230
|
+
#requestTimeoutMs;
|
|
168
231
|
#abort = new AbortController();
|
|
169
232
|
#done;
|
|
170
233
|
#stdout = "";
|
|
@@ -173,26 +236,53 @@ export class CommandHandle {
|
|
|
173
236
|
#truncated = false;
|
|
174
237
|
#failure;
|
|
175
238
|
#disconnected = false;
|
|
239
|
+
#deadline;
|
|
176
240
|
constructor(process, options) {
|
|
177
241
|
this.#process = process;
|
|
178
242
|
this.pid = pidOf(process.id);
|
|
179
243
|
this.#stdin = options.stdin;
|
|
180
244
|
this.#timeoutMs = options.timeoutMs;
|
|
245
|
+
this.#requestTimeoutMs = options.requestTimeoutMs;
|
|
246
|
+
// E2B's timeout limits the subscription. It never asks us to kill the
|
|
247
|
+
// process: the sandbox lease still bounds the workload after disconnect.
|
|
248
|
+
if (options.timeoutMs > 0) {
|
|
249
|
+
this.#deadline = setTimeout(() => {
|
|
250
|
+
this.#failure = timedOut(options.timeoutMs);
|
|
251
|
+
this.#abort.abort();
|
|
252
|
+
}, options.deadline === undefined
|
|
253
|
+
? options.timeoutMs
|
|
254
|
+
: Math.max(0, options.deadline - performance.now()));
|
|
255
|
+
this.#deadline.unref?.();
|
|
256
|
+
}
|
|
181
257
|
this.#done = this.#follow(options);
|
|
182
258
|
}
|
|
183
259
|
async #follow(options) {
|
|
260
|
+
const signal = options.signal
|
|
261
|
+
? AbortSignal.any([options.signal, this.#abort.signal])
|
|
262
|
+
: this.#abort.signal;
|
|
184
263
|
try {
|
|
185
|
-
|
|
264
|
+
const outputOptions = {
|
|
186
265
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
187
|
-
signal
|
|
188
|
-
}
|
|
189
|
-
|
|
266
|
+
signal,
|
|
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)
|
|
273
|
+
break;
|
|
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") {
|
|
190
280
|
this.#stdout += event.data;
|
|
191
|
-
await options.onStdout?.(event.data);
|
|
281
|
+
await deliver(signal, () => options.onStdout?.(event.data));
|
|
192
282
|
}
|
|
193
|
-
else if (event.type === "stderr") {
|
|
283
|
+
else if (event.type === "stderr" && typeof event.data === "string") {
|
|
194
284
|
this.#stderr += event.data;
|
|
195
|
-
await options.onStderr?.(event.data);
|
|
285
|
+
await deliver(signal, () => options.onStderr?.(event.data));
|
|
196
286
|
}
|
|
197
287
|
else if (event.type === "truncated") {
|
|
198
288
|
this.#truncated = true;
|
|
@@ -203,12 +293,16 @@ export class CommandHandle {
|
|
|
203
293
|
}
|
|
204
294
|
}
|
|
205
295
|
catch (error) {
|
|
206
|
-
if (!this.#disconnected) {
|
|
296
|
+
if (!this.#disconnected && !this.#failure) {
|
|
207
297
|
const translated = translate(error, "sandbox");
|
|
208
298
|
this.#failure =
|
|
209
299
|
translated instanceof Error ? translated : new SandboxError(String(translated));
|
|
210
300
|
}
|
|
211
301
|
}
|
|
302
|
+
finally {
|
|
303
|
+
if (this.#deadline !== undefined)
|
|
304
|
+
clearTimeout(this.#deadline);
|
|
305
|
+
}
|
|
212
306
|
}
|
|
213
307
|
/** The exit code, or undefined while the command runs. */
|
|
214
308
|
get exitCode() {
|
|
@@ -259,7 +353,7 @@ export class CommandHandle {
|
|
|
259
353
|
if (this.#exit)
|
|
260
354
|
return false;
|
|
261
355
|
try {
|
|
262
|
-
await guard("sandbox", () => this.#process.kill("SIGKILL"));
|
|
356
|
+
await guard("sandbox", () => this.#process.kill("SIGKILL", commandRequest({}, this.#requestTimeoutMs)));
|
|
263
357
|
return true;
|
|
264
358
|
}
|
|
265
359
|
catch (error) {
|
|
@@ -268,14 +362,16 @@ export class CommandHandle {
|
|
|
268
362
|
throw error;
|
|
269
363
|
}
|
|
270
364
|
}
|
|
271
|
-
async sendStdin(data,
|
|
365
|
+
async sendStdin(data, opts = {}) {
|
|
366
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
272
367
|
if (!this.#stdin)
|
|
273
368
|
throw new InvalidArgumentError("The command was not started with stdin: true, so its input is closed.");
|
|
274
|
-
await guard("sandbox", () => this.#process.write(data));
|
|
369
|
+
await guard("sandbox", () => this.#process.write(data, request));
|
|
275
370
|
}
|
|
276
|
-
async closeStdin(
|
|
371
|
+
async closeStdin(opts = {}) {
|
|
372
|
+
const request = commandRequest(opts, this.#requestTimeoutMs);
|
|
277
373
|
if (!this.#stdin)
|
|
278
374
|
throw new InvalidArgumentError("The command was not started with stdin: true.");
|
|
279
|
-
await guard("sandbox", () => this.#process.write("", { eof: true }));
|
|
375
|
+
await guard("sandbox", () => this.#process.write("", { ...request, eof: true }));
|
|
280
376
|
}
|
|
281
377
|
}
|
package/dist/e2b/errors.js
CHANGED
|
@@ -77,7 +77,7 @@ export class NotSupportedError extends SandboxError {
|
|
|
77
77
|
export class CommandExitError extends SandboxError {
|
|
78
78
|
#result;
|
|
79
79
|
constructor(result) {
|
|
80
|
-
super(
|
|
80
|
+
super(result.error);
|
|
81
81
|
this.#result = result;
|
|
82
82
|
}
|
|
83
83
|
get exitCode() {
|