withruntime 0.8.4 → 0.9.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/COMPATIBILITY.md +145 -0
- package/E2B.md +23 -13
- package/README.md +8 -5
- package/README.src.md +8 -5
- package/compatibility-lock.json +281 -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/preview.d.ts +1 -1
- package/dist/blaxel/preview.js +1 -1
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +11 -2
- package/dist/cli-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.js +162 -31
- package/dist/client.d.ts +4 -1
- package/dist/client.js +8 -2
- package/dist/cloudflare/index.d.ts +294 -0
- package/dist/cloudflare/index.js +554 -0
- package/dist/cloudflare/worker.d.ts +415 -0
- package/dist/cloudflare/worker.js +297 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +453 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1313 -0
- package/dist/compat/core.d.ts +39 -0
- package/dist/compat/core.js +199 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +115 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/daytona/context.js +2 -1
- package/dist/daytona/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- package/dist/e2b/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +5 -4
- package/dist/e2b/commands.js +37 -29
- 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/sandbox.d.ts +4 -3
- package/dist/e2b/sandbox.js +10 -10
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/freestyle/index.d.ts +301 -0
- package/dist/freestyle/index.js +310 -0
- package/dist/index.d.ts +1 -0
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +485 -0
- package/dist/network-cli.js +1 -1
- package/dist/openai-agents/index.js +9 -26
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -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/previews.d.ts +10 -2
- package/dist/products/previews.js +3 -2
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +75 -14
- package/dist/runloop/index.d.ts +340 -0
- package/dist/runloop/index.js +438 -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 +40 -9
- package/dist/sandbox.js +273 -76
- package/dist/snapshots.d.ts +11 -1
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +552 -0
- package/dist/tar.d.ts +8 -1
- package/dist/tar.js +185 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +20 -2
- package/dist/transport.js +48 -11
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.js +12 -1
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +12 -1
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.js +5 -5
- package/package.json +45 -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
|
@@ -18,7 +18,7 @@ export interface CommandStartOpts extends CommandRequestOpts {
|
|
|
18
18
|
onStdout?: (data: string) => void | Promise<void>;
|
|
19
19
|
onStderr?: (data: string) => void | Promise<void>;
|
|
20
20
|
stdin?: boolean;
|
|
21
|
-
/**
|
|
21
|
+
/** Stream connection deadline, 60 000 by default. Zero waits without a deadline. */
|
|
22
22
|
timeoutMs?: number;
|
|
23
23
|
}
|
|
24
24
|
export type CommandConnectOpts = Pick<CommandStartOpts, "onStderr" | "onStdout" | "timeoutMs"> & CommandRequestOpts;
|
|
@@ -32,11 +32,11 @@ export interface ProcessInfo {
|
|
|
32
32
|
envs: Record<string, string>;
|
|
33
33
|
cwd?: string;
|
|
34
34
|
}
|
|
35
|
-
/** The shared state a sandbox's modules need: the Runtime sandbox
|
|
36
|
-
*
|
|
35
|
+
/** The shared state a sandbox's modules need: the Runtime sandbox and the
|
|
36
|
+
* one-time /home/user link. The environment given at create is Runtime's to
|
|
37
|
+
* keep: it is the sandbox's own `env`, added to every command there. */
|
|
37
38
|
export interface SandboxContext {
|
|
38
39
|
readonly runtime: RuntimeSandbox;
|
|
39
|
-
readonly envs: Record<string, string>;
|
|
40
40
|
/** Makes /home/user (E2B's home) lead to /workspace (Runtime's), once, when
|
|
41
41
|
* something names it. */
|
|
42
42
|
ensureHome(text: string | undefined): Promise<void>;
|
|
@@ -80,6 +80,7 @@ export declare class CommandHandle {
|
|
|
80
80
|
stdin: boolean;
|
|
81
81
|
timeoutMs: number;
|
|
82
82
|
cursor?: number;
|
|
83
|
+
signal?: AbortSignal;
|
|
83
84
|
onStdout?: (data: string) => void | Promise<void>;
|
|
84
85
|
onStderr?: (data: string) => void | Promise<void>;
|
|
85
86
|
});
|
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) {
|
|
@@ -19,9 +17,9 @@ function refuseUser(user) {
|
|
|
19
17
|
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
|
function timedOut(timeoutMs) {
|
|
27
25
|
return new TimeoutError(`Command timed out after ${timeoutMs} ms: this error is likely due to exceeding 'timeoutMs'. ` +
|
|
@@ -37,7 +35,7 @@ function toResult(result) {
|
|
|
37
35
|
stdout: result.stdout,
|
|
38
36
|
stderr: result.stderr,
|
|
39
37
|
...(exitCode === 0
|
|
40
|
-
? {}
|
|
38
|
+
? { error: "" }
|
|
41
39
|
: {
|
|
42
40
|
error: result.exitCode === null ? "terminated by a signal" : `exit status ${exitCode}`,
|
|
43
41
|
}),
|
|
@@ -61,42 +59,32 @@ export class Commands {
|
|
|
61
59
|
this.#ctx = ctx;
|
|
62
60
|
}
|
|
63
61
|
#env(envs) {
|
|
64
|
-
|
|
65
|
-
return Object.keys(merged).length ? { env: merged } : {};
|
|
62
|
+
return envs && Object.keys(envs).length ? { env: envs } : {};
|
|
66
63
|
}
|
|
67
64
|
async run(cmd, opts = {}) {
|
|
65
|
+
opts.signal?.throwIfAborted();
|
|
68
66
|
refuseUser(opts.user);
|
|
69
67
|
await this.#ctx.ensureHome(`${cmd}\n${opts.cwd ?? ""}`);
|
|
70
68
|
const timeoutMs = timeoutFor(opts.timeoutMs);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return opts.background ? handle : handle.wait();
|
|
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);
|
|
69
|
+
const handle = await this.#start(cmd, opts, timeoutMs);
|
|
70
|
+
return opts.background ? handle : handle.wait();
|
|
88
71
|
}
|
|
89
72
|
async #start(cmd, opts, timeoutMs) {
|
|
73
|
+
opts.signal?.throwIfAborted();
|
|
74
|
+
const requestDeadline = opts.requestTimeoutMs
|
|
75
|
+
? AbortSignal.timeout(opts.requestTimeoutMs)
|
|
76
|
+
: undefined;
|
|
77
|
+
const signals = [opts.signal, requestDeadline].filter((signal) => signal !== undefined);
|
|
90
78
|
const process = await guard("sandbox", () => this.#ctx.runtime.spawn(cmd, {
|
|
91
79
|
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
92
80
|
...this.#env(opts.envs),
|
|
93
|
-
timeoutMs,
|
|
94
81
|
...(opts.stdin ? { stdin: "pipe" } : {}),
|
|
95
|
-
...
|
|
82
|
+
...(signals.length ? { signal: AbortSignal.any(signals) } : {}),
|
|
96
83
|
}));
|
|
97
84
|
return new CommandHandle(process, {
|
|
98
85
|
stdin: opts.stdin === true,
|
|
99
86
|
timeoutMs,
|
|
87
|
+
signal: opts.signal,
|
|
100
88
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
101
89
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
102
90
|
});
|
|
@@ -142,6 +130,7 @@ export class Commands {
|
|
|
142
130
|
return new CommandHandle(process, {
|
|
143
131
|
stdin: process.info.stdinOpen,
|
|
144
132
|
timeoutMs: timeoutFor(opts.timeoutMs),
|
|
133
|
+
signal: opts.signal,
|
|
145
134
|
cursor: process.info.outputBytes,
|
|
146
135
|
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
147
136
|
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
@@ -173,19 +162,34 @@ export class CommandHandle {
|
|
|
173
162
|
#truncated = false;
|
|
174
163
|
#failure;
|
|
175
164
|
#disconnected = false;
|
|
165
|
+
#deadline;
|
|
176
166
|
constructor(process, options) {
|
|
177
167
|
this.#process = process;
|
|
178
168
|
this.pid = pidOf(process.id);
|
|
179
169
|
this.#stdin = options.stdin;
|
|
180
170
|
this.#timeoutMs = options.timeoutMs;
|
|
171
|
+
// E2B's timeout limits the subscription. It never asks us to kill the
|
|
172
|
+
// process: the sandbox lease still bounds the workload after disconnect.
|
|
173
|
+
if (options.timeoutMs > 0) {
|
|
174
|
+
this.#deadline = setTimeout(() => {
|
|
175
|
+
this.#failure = timedOut(options.timeoutMs);
|
|
176
|
+
this.#abort.abort();
|
|
177
|
+
}, options.timeoutMs);
|
|
178
|
+
this.#deadline.unref?.();
|
|
179
|
+
}
|
|
181
180
|
this.#done = this.#follow(options);
|
|
182
181
|
}
|
|
183
182
|
async #follow(options) {
|
|
183
|
+
const signal = options.signal
|
|
184
|
+
? AbortSignal.any([options.signal, this.#abort.signal])
|
|
185
|
+
: this.#abort.signal;
|
|
184
186
|
try {
|
|
185
187
|
for await (const event of this.#process.output({
|
|
186
188
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
187
|
-
signal
|
|
189
|
+
signal,
|
|
188
190
|
})) {
|
|
191
|
+
if (this.#disconnected || signal.aborted)
|
|
192
|
+
break;
|
|
189
193
|
if (event.type === "stdout") {
|
|
190
194
|
this.#stdout += event.data;
|
|
191
195
|
await options.onStdout?.(event.data);
|
|
@@ -203,12 +207,16 @@ export class CommandHandle {
|
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
catch (error) {
|
|
206
|
-
if (!this.#disconnected) {
|
|
210
|
+
if (!this.#disconnected && !this.#failure) {
|
|
207
211
|
const translated = translate(error, "sandbox");
|
|
208
212
|
this.#failure =
|
|
209
213
|
translated instanceof Error ? translated : new SandboxError(String(translated));
|
|
210
214
|
}
|
|
211
215
|
}
|
|
216
|
+
finally {
|
|
217
|
+
if (this.#deadline !== undefined)
|
|
218
|
+
clearTimeout(this.#deadline);
|
|
219
|
+
}
|
|
212
220
|
}
|
|
213
221
|
/** The exit code, or undefined while the command runs. */
|
|
214
222
|
get exitCode() {
|
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() {
|
package/dist/e2b/filesystem.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface EntryInfo extends WriteInfo {
|
|
|
14
14
|
size: number;
|
|
15
15
|
mode: number;
|
|
16
16
|
permissions: string;
|
|
17
|
-
/**
|
|
17
|
+
/** File owner when returned by the native guest. */
|
|
18
18
|
owner: string;
|
|
19
|
-
/**
|
|
19
|
+
/** File group when returned by the native guest. */
|
|
20
20
|
group: string;
|
|
21
21
|
modifiedTime?: Date;
|
|
22
22
|
symlinkTarget?: string;
|
|
@@ -32,11 +32,14 @@ export interface FilesystemEvent {
|
|
|
32
32
|
/** Relative to the watched directory. */
|
|
33
33
|
name: string;
|
|
34
34
|
type: FilesystemEventType;
|
|
35
|
+
entry?: EntryInfo;
|
|
35
36
|
}
|
|
36
37
|
export interface WatchOpts extends FilesystemRequestOpts {
|
|
37
|
-
/** How long the
|
|
38
|
+
/** How long the subscription runs; 0 has no subscription deadline. Default 60000. */
|
|
38
39
|
timeoutMs?: number;
|
|
39
40
|
recursive?: boolean;
|
|
41
|
+
includeEntry?: boolean;
|
|
42
|
+
allowNetworkMounts?: boolean;
|
|
40
43
|
}
|
|
41
44
|
export interface E2BWatchHandle {
|
|
42
45
|
stop(): Promise<void>;
|
|
@@ -98,9 +101,8 @@ export declare class Filesystem {
|
|
|
98
101
|
remove(path: string, opts?: FilesystemRequestOpts): Promise<void>;
|
|
99
102
|
exists(path: string, opts?: FilesystemRequestOpts): Promise<boolean>;
|
|
100
103
|
getInfo(path: string, opts?: FilesystemRequestOpts): Promise<EntryInfo>;
|
|
101
|
-
/** E2B's watchDir on Runtime's file watch
|
|
102
|
-
*
|
|
103
|
-
* with `handle.stop()`. `timeoutMs` 0 is Runtime's 24-hour maximum. */
|
|
104
|
+
/** E2B's watchDir on Runtime's file watch. A local deadline preserves
|
|
105
|
+
* subsecond subscriptions; 0 runs until stopped or the sandbox ends. */
|
|
104
106
|
watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, opts?: WatchOpts & {
|
|
105
107
|
onExit?: (err?: Error) => void | Promise<void>;
|
|
106
108
|
}): Promise<E2BWatchHandle>;
|
package/dist/e2b/filesystem.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { request } from "./client.js";
|
|
2
|
-
import { FileNotFoundError, guard, NotSupportedError } from "./errors.js";
|
|
2
|
+
import { FileNotFoundError, guard, InvalidArgumentError, NotSupportedError, TimeoutError, } from "./errors.js";
|
|
3
3
|
export var FileType;
|
|
4
4
|
(function (FileType) {
|
|
5
5
|
FileType["FILE"] = "file";
|
|
@@ -42,8 +42,9 @@ function entryInfo(entry) {
|
|
|
42
42
|
size: entry.size,
|
|
43
43
|
mode,
|
|
44
44
|
permissions: PERMISSIONS[(bits >> 6) & 7] + PERMISSIONS[(bits >> 3) & 7] + PERMISSIONS[bits & 7],
|
|
45
|
-
owner: "",
|
|
46
|
-
group: "",
|
|
45
|
+
owner: entry.owner ?? "",
|
|
46
|
+
group: entry.group ?? "",
|
|
47
|
+
...(entry.symlinkTarget ? { symlinkTarget: entry.symlinkTarget } : {}),
|
|
47
48
|
modifiedTime: new Date(entry.modifiedAt),
|
|
48
49
|
};
|
|
49
50
|
}
|
|
@@ -73,8 +74,10 @@ export class Filesystem {
|
|
|
73
74
|
return this.#ctx.runtime.files;
|
|
74
75
|
}
|
|
75
76
|
async #path(path, opts) {
|
|
77
|
+
opts.signal?.throwIfAborted();
|
|
76
78
|
refuse(opts);
|
|
77
79
|
await this.#ctx.ensureHome(path);
|
|
80
|
+
opts.signal?.throwIfAborted();
|
|
78
81
|
return absolute(path);
|
|
79
82
|
}
|
|
80
83
|
async read(path, opts = {}) {
|
|
@@ -98,6 +101,7 @@ export class Filesystem {
|
|
|
98
101
|
return this.#writeOne(pathOrFiles, dataOrOpts, maybeOpts ?? {});
|
|
99
102
|
}
|
|
100
103
|
async writeFiles(files, opts = {}) {
|
|
104
|
+
opts.signal?.throwIfAborted();
|
|
101
105
|
const written = [];
|
|
102
106
|
for (const file of files)
|
|
103
107
|
written.push(await this.#writeOne(file.path, file.data, opts));
|
|
@@ -112,61 +116,156 @@ export class Filesystem {
|
|
|
112
116
|
/** A directory's entries, hidden ones included; `depth` goes deeper. */
|
|
113
117
|
async list(path, opts = {}) {
|
|
114
118
|
const dir = await this.#path(path, opts);
|
|
115
|
-
const entries = await guard("file", () => this.#files.list(dir, { depth: opts.depth ?? 1, hidden: true }));
|
|
119
|
+
const entries = await guard("file", () => this.#files.list(dir, { depth: opts.depth ?? 1, hidden: true, ...request(opts) }));
|
|
116
120
|
return entries.map(entryInfo);
|
|
117
121
|
}
|
|
118
122
|
/** Makes a directory and its parents. False when it already existed. */
|
|
119
123
|
async makeDir(path, opts = {}) {
|
|
120
124
|
const dir = await this.#path(path, opts);
|
|
121
|
-
if (await guard("file", () => this.#files.exists(dir)))
|
|
125
|
+
if (await guard("file", () => this.#files.exists(dir, request(opts))))
|
|
122
126
|
return false;
|
|
123
|
-
await guard("file", () => this.#files.mkdir(dir, { parents: true }));
|
|
127
|
+
await guard("file", () => this.#files.mkdir(dir, { parents: true, ...request(opts) }));
|
|
124
128
|
return true;
|
|
125
129
|
}
|
|
126
130
|
/** Moves a file or directory, replacing a file at the new path, as E2B does. */
|
|
127
131
|
async rename(oldPath, newPath, opts = {}) {
|
|
128
132
|
const from = await this.#path(oldPath, opts);
|
|
129
133
|
const to = await this.#path(newPath, opts);
|
|
130
|
-
await guard("file", () => this.#files.rename(from, to, { overwrite: true }));
|
|
134
|
+
await guard("file", () => this.#files.rename(from, to, { overwrite: true, ...request(opts) }));
|
|
131
135
|
return this.getInfo(to, opts);
|
|
132
136
|
}
|
|
133
137
|
/** Removes a file, or a directory with everything in it. */
|
|
134
138
|
async remove(path, opts = {}) {
|
|
135
139
|
const target = await this.#path(path, opts);
|
|
136
|
-
await guard("file", () => this.#files.remove(target, { recursive: true }));
|
|
140
|
+
await guard("file", () => this.#files.remove(target, { recursive: true, ...request(opts) }));
|
|
137
141
|
}
|
|
138
142
|
async exists(path, opts = {}) {
|
|
139
143
|
const target = await this.#path(path, opts);
|
|
140
|
-
return guard("file", () => this.#files.exists(target));
|
|
144
|
+
return guard("file", () => this.#files.exists(target, request(opts)));
|
|
141
145
|
}
|
|
142
146
|
async getInfo(path, opts = {}) {
|
|
143
147
|
const target = await this.#path(path, opts);
|
|
144
|
-
const stat = await guard("file", () => this.#files.stat(target));
|
|
148
|
+
const stat = await guard("file", () => this.#files.stat(target, request(opts)));
|
|
145
149
|
if (!stat.exists)
|
|
146
150
|
throw new FileNotFoundError(`${target} does not exist.`);
|
|
147
151
|
return entryInfo(stat);
|
|
148
152
|
}
|
|
149
|
-
/** E2B's watchDir on Runtime's file watch
|
|
150
|
-
*
|
|
151
|
-
* with `handle.stop()`. `timeoutMs` 0 is Runtime's 24-hour maximum. */
|
|
153
|
+
/** E2B's watchDir on Runtime's file watch. A local deadline preserves
|
|
154
|
+
* subsecond subscriptions; 0 runs until stopped or the sandbox ends. */
|
|
152
155
|
async watchDir(path, onEvent, opts = {}) {
|
|
156
|
+
opts.signal?.throwIfAborted();
|
|
157
|
+
if (opts.allowNetworkMounts)
|
|
158
|
+
throw new NotSupportedError("Watching network mounts", "Watch a local sandbox directory instead.");
|
|
159
|
+
const lifetime = opts.timeoutMs ?? 60000;
|
|
160
|
+
if (!Number.isFinite(lifetime) || lifetime < 0)
|
|
161
|
+
throw new InvalidArgumentError("timeoutMs must be a nonnegative finite number.");
|
|
153
162
|
const target = await this.#path(path, opts);
|
|
154
163
|
const prefix = target.endsWith("/") ? target : `${target}/`;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
let exited = false;
|
|
165
|
+
let native;
|
|
166
|
+
let stopping;
|
|
167
|
+
let timer;
|
|
168
|
+
const lifetimeAbort = new AbortController();
|
|
169
|
+
const signal = opts.signal
|
|
170
|
+
? AbortSignal.any([opts.signal, lifetimeAbort.signal])
|
|
171
|
+
: lifetimeAbort.signal;
|
|
172
|
+
const exit = async (error) => {
|
|
173
|
+
if (exited)
|
|
174
|
+
return;
|
|
175
|
+
exited = true;
|
|
176
|
+
if (timer !== undefined)
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
179
|
+
try {
|
|
180
|
+
await opts.onExit?.(error);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* Upstream treats a throwing terminal callback as handled. */
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const stopRemote = () => {
|
|
187
|
+
if (!native)
|
|
188
|
+
return Promise.resolve();
|
|
189
|
+
return (stopping ??= native.stop());
|
|
190
|
+
};
|
|
191
|
+
const close = async (error) => {
|
|
192
|
+
const callback = exit(error);
|
|
193
|
+
try {
|
|
194
|
+
await stopRemote();
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
await callback;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const onAbort = () => {
|
|
201
|
+
const error = opts.signal?.reason instanceof Error
|
|
202
|
+
? opts.signal.reason
|
|
203
|
+
: new Error("The watch was aborted.");
|
|
204
|
+
void close(error).catch(() => undefined);
|
|
205
|
+
};
|
|
206
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
207
|
+
if (opts.signal?.aborted)
|
|
208
|
+
onAbort();
|
|
209
|
+
if (lifetime > 0) {
|
|
210
|
+
const deadline = Date.now() + lifetime;
|
|
211
|
+
const schedule = () => {
|
|
212
|
+
timer = setTimeout(() => {
|
|
213
|
+
if (exited)
|
|
214
|
+
return;
|
|
215
|
+
if (Date.now() < deadline) {
|
|
216
|
+
schedule();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const error = new TimeoutError("Filesystem watch timed out.");
|
|
220
|
+
// Mark the terminal result before native cancellation can report a
|
|
221
|
+
// clean end. The same signal also cancels creation still in flight.
|
|
222
|
+
void close(error).catch(() => undefined);
|
|
223
|
+
lifetimeAbort.abort(error);
|
|
224
|
+
}, Math.min(2147483647, Math.max(0, deadline - Date.now())));
|
|
225
|
+
timer.unref?.();
|
|
226
|
+
};
|
|
227
|
+
schedule();
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const watch = await guard("file", () => this.#files.watch(target, async (event) => {
|
|
231
|
+
if (exited)
|
|
164
232
|
return;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
233
|
+
let entry;
|
|
234
|
+
if (opts.includeEntry) {
|
|
235
|
+
try {
|
|
236
|
+
entry = await this.getInfo(event.path, { ...opts, signal });
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (!(error instanceof FileNotFoundError))
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!exited)
|
|
244
|
+
await onEvent({
|
|
245
|
+
name: event.path.startsWith(prefix) ? event.path.slice(prefix.length) : event.path,
|
|
246
|
+
type: event.type,
|
|
247
|
+
...(entry ? { entry } : {}),
|
|
248
|
+
});
|
|
249
|
+
}, {
|
|
250
|
+
recursive: opts.recursive === true,
|
|
251
|
+
timeoutMs: 0,
|
|
252
|
+
signal,
|
|
253
|
+
onExit: (reason) => {
|
|
254
|
+
void exit(reason === "timeout" ? new TimeoutError("Filesystem watch timed out.") : undefined);
|
|
255
|
+
},
|
|
256
|
+
onNotice: (notice) => {
|
|
257
|
+
void close(new Error(`Filesystem events were lost (${notice.k}); rescan the directory and start a new watch.`)).catch(() => undefined);
|
|
258
|
+
},
|
|
259
|
+
}));
|
|
260
|
+
native = watch;
|
|
261
|
+
if (exited)
|
|
262
|
+
await stopRemote();
|
|
263
|
+
void watch.done?.then(() => exit(), (error) => close(error instanceof Error ? error : new Error(String(error))).catch(() => undefined));
|
|
264
|
+
return { stop: () => close() };
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
await close(error instanceof Error ? error : new Error(String(error))).catch(() => undefined);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
171
270
|
}
|
|
172
271
|
}
|
package/dist/e2b/sandbox.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export interface SandboxOpts extends ConnectionOpts {
|
|
|
26
26
|
template?: string;
|
|
27
27
|
/** Stored as the sandbox's labels (at most 32, values up to 256 characters). */
|
|
28
28
|
metadata?: Record<string, string>;
|
|
29
|
-
/**
|
|
29
|
+
/** The sandbox's own environment: every command, terminal and code run in it
|
|
30
|
+
* gets these, whoever connects. Values are never shown again. */
|
|
30
31
|
envs?: Record<string, string>;
|
|
31
32
|
/** Default 300 000. Runtime's leases run 60 s to 1 hour; shorter rounds up
|
|
32
33
|
* to 60 s (with a warning), longer is refused. */
|
|
@@ -88,17 +89,17 @@ export type SandboxListOpts = SandboxApiOpts & {
|
|
|
88
89
|
};
|
|
89
90
|
export declare class Sandbox {
|
|
90
91
|
#private;
|
|
92
|
+
protected readonly requestTimeoutMs: number;
|
|
91
93
|
protected static readonly defaultTemplate: string;
|
|
92
94
|
readonly files: Filesystem;
|
|
93
95
|
readonly commands: Commands;
|
|
94
96
|
/** Use Sandbox.create or Sandbox.connect. */
|
|
95
|
-
constructor(runtime: RuntimeSandbox, client: Runtime,
|
|
97
|
+
constructor(runtime: RuntimeSandbox, client: Runtime, requestTimeoutMs?: number);
|
|
96
98
|
get sandboxId(): string;
|
|
97
99
|
/** The Runtime sandbox underneath: previews, network, desktop, interpreter,
|
|
98
100
|
* processes, terminal and the rest of Runtime's SDK. */
|
|
99
101
|
get runtime(): RuntimeSandbox;
|
|
100
102
|
protected get client(): Runtime;
|
|
101
|
-
protected get envs(): Record<string, string>;
|
|
102
103
|
/** Creates a sandbox from `template` (default "base", Runtime's stock image)
|
|
103
104
|
* with E2B's default machine, 2 vCPU and 512 MiB, and waits until it runs.
|
|
104
105
|
* Funding is left to Runtime: the free trial while the account has trial
|