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.
Files changed (132) hide show
  1. package/BLAXEL.md +4 -4
  2. package/COMPATIBILITY.md +145 -0
  3. package/DAYTONA.md +13 -7
  4. package/E2B.md +34 -20
  5. package/README.md +22 -11
  6. package/README.src.md +22 -11
  7. package/VERCEL.md +13 -3
  8. package/compatibility-lock.json +357 -0
  9. package/dist/ai-harness/index.js +2 -2
  10. package/dist/api-defaults.d.ts +17 -0
  11. package/dist/api-defaults.js +22 -0
  12. package/dist/blaxel/errors.js +1 -1
  13. package/dist/blaxel/lookup.d.ts +3 -2
  14. package/dist/blaxel/lookup.js +3 -2
  15. package/dist/blaxel/pagination.d.ts +2 -2
  16. package/dist/blaxel/pagination.js +45 -17
  17. package/dist/blaxel/preview.d.ts +3 -2
  18. package/dist/blaxel/preview.js +16 -14
  19. package/dist/blaxel/process.d.ts +1 -1
  20. package/dist/blaxel/process.js +66 -11
  21. package/dist/blaxel/sandbox.d.ts +2 -0
  22. package/dist/blaxel/sandbox.js +2 -0
  23. package/dist/blaxel/snapshot.js +3 -5
  24. package/dist/cli-words.d.ts +27 -0
  25. package/dist/cli-words.js +135 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +351 -85
  28. package/dist/client.d.ts +33 -4
  29. package/dist/client.js +47 -12
  30. package/dist/cloudflare/identity.d.ts +7 -0
  31. package/dist/cloudflare/identity.js +13 -0
  32. package/dist/cloudflare/index.d.ts +303 -0
  33. package/dist/cloudflare/index.js +592 -0
  34. package/dist/cloudflare/worker.d.ts +419 -0
  35. package/dist/cloudflare/worker.js +447 -0
  36. package/dist/codesandbox/guest-service.d.ts +31 -0
  37. package/dist/codesandbox/guest-service.js +490 -0
  38. package/dist/codesandbox/index.d.ts +386 -0
  39. package/dist/codesandbox/index.js +1347 -0
  40. package/dist/compat/core.d.ts +40 -0
  41. package/dist/compat/core.js +238 -0
  42. package/dist/compat/files.d.ts +26 -0
  43. package/dist/compat/files.js +133 -0
  44. package/dist/compat/shell.d.ts +4 -0
  45. package/dist/compat/shell.js +165 -0
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +107 -38
  48. package/dist/daytona/context.js +2 -1
  49. package/dist/daytona/daytona.js +11 -4
  50. package/dist/daytona/filesystem.d.ts +17 -4
  51. package/dist/daytona/filesystem.js +121 -38
  52. package/dist/daytona/index.d.ts +1 -1
  53. package/dist/daytona/process.d.ts +1 -1
  54. package/dist/daytona/process.js +27 -4
  55. package/dist/daytona/sandbox.d.ts +1 -1
  56. package/dist/daytona/sandbox.js +16 -7
  57. package/dist/e2b/client.js +1 -1
  58. package/dist/e2b/code-interpreter.d.ts +4 -5
  59. package/dist/e2b/code-interpreter.js +54 -56
  60. package/dist/e2b/commands.d.ts +35 -12
  61. package/dist/e2b/commands.js +169 -73
  62. package/dist/e2b/errors.js +1 -1
  63. package/dist/e2b/filesystem.d.ts +8 -6
  64. package/dist/e2b/filesystem.js +127 -28
  65. package/dist/e2b/index.d.ts +1 -0
  66. package/dist/e2b/index.js +1 -0
  67. package/dist/e2b/pty.d.ts +24 -0
  68. package/dist/e2b/pty.js +80 -0
  69. package/dist/e2b/sandbox.d.ts +6 -4
  70. package/dist/e2b/sandbox.js +59 -25
  71. package/dist/errors.d.ts +8 -0
  72. package/dist/errors.js +15 -2
  73. package/dist/file-source.d.ts +16 -0
  74. package/dist/file-source.js +162 -0
  75. package/dist/freestyle/index.d.ts +309 -0
  76. package/dist/freestyle/index.js +343 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/login.js +16 -10
  79. package/dist/mcp.js +2 -2
  80. package/dist/modal/index.d.ts +205 -0
  81. package/dist/modal/index.js +486 -0
  82. package/dist/network-cli.js +112 -44
  83. package/dist/openai-agents/index.js +18 -71
  84. package/dist/page.d.ts +1 -1
  85. package/dist/page.js +2 -2
  86. package/dist/products/audit.d.ts +3 -0
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/interpreter.d.ts +11 -4
  90. package/dist/products/interpreter.js +33 -9
  91. package/dist/products/jobs.js +3 -2
  92. package/dist/products/limits.d.ts +10 -0
  93. package/dist/products/observability.d.ts +44 -0
  94. package/dist/products/observability.js +6 -0
  95. package/dist/products/previews.d.ts +18 -4
  96. package/dist/products/previews.js +14 -5
  97. package/dist/products/tailscale.d.ts +57 -0
  98. package/dist/products/tailscale.js +17 -0
  99. package/dist/products/volumes.d.ts +42 -0
  100. package/dist/products/volumes.js +36 -0
  101. package/dist/products/watch.d.ts +25 -4
  102. package/dist/products/watch.js +132 -18
  103. package/dist/proxy.js +3 -1
  104. package/dist/runloop/index.d.ts +326 -0
  105. package/dist/runloop/index.js +535 -0
  106. package/dist/runloop/pagination.d.ts +46 -0
  107. package/dist/runloop/pagination.js +82 -0
  108. package/dist/runloop/snapshots.d.ts +52 -0
  109. package/dist/runloop/snapshots.js +244 -0
  110. package/dist/sandbox.d.ts +76 -16
  111. package/dist/sandbox.js +696 -135
  112. package/dist/snapshots.d.ts +20 -5
  113. package/dist/snapshots.js +11 -1
  114. package/dist/sprites/index.d.ts +251 -0
  115. package/dist/sprites/index.js +588 -0
  116. package/dist/tar.d.ts +10 -1
  117. package/dist/tar.js +311 -35
  118. package/dist/tools/index.js +2 -1
  119. package/dist/transport.d.ts +23 -8
  120. package/dist/transport.js +247 -127
  121. package/dist/types.d.ts +52 -3
  122. package/dist/vercel/command.d.ts +1 -1
  123. package/dist/vercel/command.js +15 -3
  124. package/dist/vercel/errors.d.ts +2 -2
  125. package/dist/vercel/errors.js +19 -2
  126. package/dist/vercel/filesystem.d.ts +21 -18
  127. package/dist/vercel/filesystem.js +95 -48
  128. package/dist/vercel/sandbox.d.ts +8 -8
  129. package/dist/vercel/sandbox.js +159 -64
  130. package/dist/vercel/snapshot.d.ts +3 -2
  131. package/dist/vercel/snapshot.js +9 -5
  132. package/package.json +46 -3
@@ -14,9 +14,9 @@ export interface EntryInfo extends WriteInfo {
14
14
  size: number;
15
15
  mode: number;
16
16
  permissions: string;
17
- /** Runtime's listing does not say who owns a file: always "". */
17
+ /** File owner when returned by the native guest. */
18
18
  owner: string;
19
- /** Runtime's listing does not say a file's group: always "". */
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 watch runs; 0 is Runtime's maximum, 24 hours. Default 60000. */
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: the same events (CREATE, WRITE,
102
- * REMOVE, RENAME, CHMOD) with names relative to the directory, stopped
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>;
@@ -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: the same events (CREATE, WRITE,
150
- * REMOVE, RENAME, CHMOD) with names relative to the directory, stopped
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
- const lifetime = opts.timeoutMs === 0 ? 86_400_000 : (opts.timeoutMs ?? 60_000);
156
- const watch = await guard("file", () => this.#files.watch(target, (event) => onEvent({
157
- name: event.path.startsWith(prefix) ? event.path.slice(prefix.length) : event.path,
158
- type: event.type,
159
- }), {
160
- recursive: opts.recursive === true,
161
- timeoutMs: Math.max(1_000, Math.min(86_400_000, lifetime)),
162
- onExit: (reason) => {
163
- if (reason === "stopped")
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
- void opts.onExit?.(reason === "timeout" || reason === "paused"
166
- ? undefined
167
- : new Error(`The watch ended: ${reason}`));
168
- },
169
- }));
170
- return { stop: () => watch.stop() };
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
  }
@@ -12,6 +12,7 @@ export { Commands, CommandHandle, pidOf, type CommandStartOpts, type CommandConn
12
12
  export { Filesystem, FileType, type EntryInfo, type WriteInfo, type WriteEntry, type FilesystemRequestOpts, type FilesystemReadOpts, type FilesystemWriteOpts, type FilesystemListOpts, FilesystemEventType, type FilesystemEvent, type WatchOpts, type E2BWatchHandle as WatchHandle, } from "./filesystem.js";
13
13
  export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, type CommandResult, } from "./errors.js";
14
14
  export type { ConnectionOpts, RuntimeOpts } from "./client.js";
15
+ export { Pty, type PtyCreateOpts, type PtyConnectOpts } from "./pty.js";
15
16
  export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
16
17
  export default Sandbox;
17
18
  /** E2B's client with bound options: `new E2B({ apiKey }).Sandbox.create()`. */
package/dist/e2b/index.js CHANGED
@@ -4,6 +4,7 @@ export { Sandbox, SandboxPaginator, DEFAULT_SHAPE, DEFAULT_TIMEOUT_MS, } from ".
4
4
  export { Commands, CommandHandle, pidOf, } from "./commands.js";
5
5
  export { Filesystem, FileType, FilesystemEventType, } from "./filesystem.js";
6
6
  export { SandboxError, TimeoutError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, TemplateError, RateLimitError, AuthenticationError, ServiceBusyError, CommandExitError, NotSupportedError, } from "./errors.js";
7
+ export { Pty } from "./pty.js";
7
8
  export { Template, TemplateBase, Volume, Secret, waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from "./unsupported.js";
8
9
  export default Sandbox;
9
10
  /** E2B's client with bound options: `new E2B({ apiKey }).Sandbox.create()`. */
@@ -0,0 +1,24 @@
1
+ import { CommandHandle, type CommandRequestOpts, type SandboxContext, type Username } from "./commands.js";
2
+ export interface PtyCreateOpts extends CommandRequestOpts {
3
+ cols: number;
4
+ rows: number;
5
+ onData: (data: Uint8Array) => void | Promise<void>;
6
+ timeoutMs?: number;
7
+ user?: Username;
8
+ envs?: Record<string, string>;
9
+ cwd?: string;
10
+ }
11
+ export type PtyConnectOpts = CommandRequestOpts & Pick<PtyCreateOpts, "onData" | "timeoutMs">;
12
+ /** E2B's PTY methods over native terminal processes with lossless output. */
13
+ export declare class Pty {
14
+ private readonly ctx;
15
+ constructor(ctx: SandboxContext);
16
+ create(opts: PtyCreateOpts): Promise<CommandHandle>;
17
+ connect(pid: number, opts?: PtyConnectOpts): Promise<CommandHandle>;
18
+ sendInput(pid: number, data: Uint8Array, opts?: CommandRequestOpts): Promise<void>;
19
+ resize(pid: number, size: {
20
+ cols: number;
21
+ rows: number;
22
+ }, opts?: CommandRequestOpts): Promise<void>;
23
+ kill(pid: number, opts?: CommandRequestOpts): Promise<boolean>;
24
+ }
@@ -0,0 +1,80 @@
1
+ import { CommandHandle, commandRequest, connectionRequest, killProcess, refuseUser, resolveProcess, } from "./commands.js";
2
+ import { guard, InvalidArgumentError, NotSupportedError } from "./errors.js";
3
+ function dimensions(size) {
4
+ for (const value of [size.cols, size.rows])
5
+ if (!Number.isSafeInteger(value) || value <= 0)
6
+ throw new InvalidArgumentError("PTY rows and columns must be positive whole numbers.");
7
+ return { cols: size.cols, rows: size.rows };
8
+ }
9
+ /** E2B's PTY methods over native terminal processes with lossless output. */
10
+ export class Pty {
11
+ ctx;
12
+ constructor(ctx) {
13
+ this.ctx = ctx;
14
+ }
15
+ async create(opts) {
16
+ refuseUser(opts.user);
17
+ const pty = dimensions(opts);
18
+ const opening = connectionRequest(opts, this.ctx.requestTimeoutMs);
19
+ if (typeof opts.onData !== "function")
20
+ throw new InvalidArgumentError("onData must be a function.");
21
+ const env = { TERM: "xterm-256color", LANG: "C.UTF-8", LC_ALL: "C.UTF-8", ...opts.envs };
22
+ const process = await guard("sandbox", async () => {
23
+ await this.ctx.ensureHome(opts.cwd, opening.request);
24
+ opening.request.signal?.throwIfAborted();
25
+ return this.ctx.runtime.spawn(["/bin/bash", "-i", "-l"], {
26
+ pty,
27
+ stdin: "pipe",
28
+ outputEncoding: "base64",
29
+ env,
30
+ ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
31
+ request: opening.request,
32
+ });
33
+ }).catch(opening.failure);
34
+ return new CommandHandle(process, {
35
+ stdin: false,
36
+ pty: true,
37
+ onData: opts.onData,
38
+ timeoutMs: opening.timeoutMs,
39
+ deadline: opening.deadline,
40
+ signal: opts.signal,
41
+ requestTimeoutMs: this.ctx.requestTimeoutMs,
42
+ });
43
+ }
44
+ async connect(pid, opts) {
45
+ const opening = connectionRequest(opts ?? {}, this.ctx.requestTimeoutMs);
46
+ const process = await resolveProcess(this.ctx, pid, opening.request).catch(opening.failure);
47
+ if (!process.info.pty || process.info.outputEncoding !== "base64")
48
+ throw new NotSupportedError("Connecting to a legacy text-output PTY", "Create the PTY through sandbox.pty.create first.");
49
+ return new CommandHandle(process, {
50
+ stdin: false,
51
+ pty: true,
52
+ onData: opts?.onData,
53
+ timeoutMs: opening.timeoutMs,
54
+ deadline: opening.deadline,
55
+ cursor: process.info.outputBytes,
56
+ signal: opts?.signal,
57
+ requestTimeoutMs: this.ctx.requestTimeoutMs,
58
+ });
59
+ }
60
+ async sendInput(pid, data, opts = {}) {
61
+ const request = commandRequest(opts, this.ctx.requestTimeoutMs);
62
+ const process = await resolveProcess(this.ctx, pid, request);
63
+ if (!process.info.pty)
64
+ throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
65
+ request.signal?.throwIfAborted();
66
+ await guard("sandbox", () => process.write(data, request));
67
+ }
68
+ async resize(pid, size, opts = {}) {
69
+ const pty = dimensions(size);
70
+ const request = commandRequest(opts, this.ctx.requestTimeoutMs);
71
+ const process = await resolveProcess(this.ctx, pid, request);
72
+ if (!process.info.pty)
73
+ throw new InvalidArgumentError(`The process with pid ${pid} is not a PTY.`);
74
+ request.signal?.throwIfAborted();
75
+ await guard("sandbox", () => process.resize(pty.cols, pty.rows, request));
76
+ }
77
+ kill(pid, opts = {}) {
78
+ return killProcess(this.ctx, pid, commandRequest(opts, this.ctx.requestTimeoutMs));
79
+ }
80
+ }
@@ -2,6 +2,7 @@ import type { Runtime } from "../client.js";
2
2
  import { type ConnectionOpts, type RuntimeOpts, type RuntimeSandbox } from "./client.js";
3
3
  import { Commands } from "./commands.js";
4
4
  import { Filesystem } from "./filesystem.js";
5
+ import { Pty } from "./pty.js";
5
6
  /** E2B's default machine: 2 vCPU and 512 MiB (docs.e2b.dev/billing, checked
6
7
  * 23 September 2026). Pass `runtime: { create: { memoryMiB } }` for another. */
7
8
  export declare const DEFAULT_SHAPE: {
@@ -26,7 +27,8 @@ export interface SandboxOpts extends ConnectionOpts {
26
27
  template?: string;
27
28
  /** Stored as the sandbox's labels (at most 32, values up to 256 characters). */
28
29
  metadata?: Record<string, string>;
29
- /** Given to every command and code run through this object. */
30
+ /** The sandbox's own environment: every command, terminal and code run in it
31
+ * gets these, whoever connects. Values are never shown again. */
30
32
  envs?: Record<string, string>;
31
33
  /** Default 300 000. Runtime's leases run 60 s to 1 hour; shorter rounds up
32
34
  * to 60 s (with a warning), longer is refused. */
@@ -88,17 +90,18 @@ export type SandboxListOpts = SandboxApiOpts & {
88
90
  };
89
91
  export declare class Sandbox {
90
92
  #private;
93
+ protected readonly requestTimeoutMs: number;
91
94
  protected static readonly defaultTemplate: string;
92
95
  readonly files: Filesystem;
93
96
  readonly commands: Commands;
97
+ readonly pty: Pty;
94
98
  /** Use Sandbox.create or Sandbox.connect. */
95
- constructor(runtime: RuntimeSandbox, client: Runtime, envs?: Record<string, string>);
99
+ constructor(runtime: RuntimeSandbox, client: Runtime, requestTimeoutMs?: number);
96
100
  get sandboxId(): string;
97
101
  /** The Runtime sandbox underneath: previews, network, desktop, interpreter,
98
102
  * processes, terminal and the rest of Runtime's SDK. */
99
103
  get runtime(): RuntimeSandbox;
100
104
  protected get client(): Runtime;
101
- protected get envs(): Record<string, string>;
102
105
  /** Creates a sandbox from `template` (default "base", Runtime's stock image)
103
106
  * with E2B's default machine, 2 vCPU and 512 MiB, and waits until it runs.
104
107
  * Funding is left to Runtime: the free trial while the account has trial
@@ -166,7 +169,6 @@ export declare class Sandbox {
166
169
  * its host, `<port>-<id>.<domain>`, once the share has landed. Anyone with
167
170
  * the address can reach it; a browser sees a one-time page naming Runtime. */
168
171
  getPublicHost(port: number): Promise<string>;
169
- get pty(): never;
170
172
  get git(): never;
171
173
  /** E2B's metrics, from Runtime's measured readings (`sbx.metrics()`): CPU
172
174
  * as a percent of the sandbox's vCPUs and resident memory, one entry per
@@ -2,6 +2,7 @@ import { clientFor, request, } from "./client.js";
2
2
  import { Commands } from "./commands.js";
3
3
  import { guard, InvalidArgumentError, NotSupportedError, SandboxError, SandboxNotFoundError, TemplateError, } from "./errors.js";
4
4
  import { Filesystem } from "./filesystem.js";
5
+ import { Pty } from "./pty.js";
5
6
  /* E2B's Sandbox over Runtime's. Every call is one or a few calls of the
6
7
  withruntime SDK; nothing here speaks HTTP. What Runtime cannot do the same
7
8
  way throws NotSupportedError before anything happens. */
@@ -65,12 +66,13 @@ function refuseCreate(opts) {
65
66
  }
66
67
  /** Where a template sends the create: the stock image, a Runtime image by
67
68
  * name or id, or a Runtime snapshot by id. */
68
- async function resolveTemplate(client, template) {
69
+ async function resolveTemplate(client, template, options) {
70
+ options.signal?.throwIfAborted();
69
71
  if (STOCK_TEMPLATES.has(template))
70
72
  return {};
71
73
  if (UUID.test(template)) {
72
74
  try {
73
- await client.images.get(template);
75
+ await client.images.get(template, options);
74
76
  return { image: template };
75
77
  }
76
78
  catch (error) {
@@ -79,7 +81,7 @@ async function resolveTemplate(client, template) {
79
81
  }
80
82
  return { snapshot: template };
81
83
  }
82
- const page = await client.images.list({ name: template, state: "ready", limit: 1 });
84
+ const page = await client.images.list({ name: template, state: "ready", limit: 1 }, options);
83
85
  const image = page.data[0];
84
86
  if (image)
85
87
  return { image: image.id };
@@ -131,28 +133,30 @@ function unsupported(feature, alternative) {
131
133
  /** Where Runtime serves previews; getHost names a host under it. */
132
134
  const PREVIEW_DOMAIN = "runtimehost.com";
133
135
  export class Sandbox {
136
+ requestTimeoutMs;
134
137
  static defaultTemplate = "base";
135
138
  files;
136
139
  commands;
140
+ pty;
137
141
  #client;
138
142
  #runtime;
139
- #envs;
140
143
  #home;
141
144
  /** Ports shared as public previews, by port: the one share each asked for. */
142
145
  #shares = new Map();
143
146
  /** Use Sandbox.create or Sandbox.connect. */
144
- constructor(runtime, client, envs = {}) {
147
+ constructor(runtime, client, requestTimeoutMs = 60_000) {
148
+ this.requestTimeoutMs = requestTimeoutMs;
145
149
  this.#runtime = runtime;
146
150
  this.#client = client;
147
- this.#envs = envs;
148
151
  const ctx = {
149
- envs,
150
- ensureHome: (text) => this.#ensureHome(text),
152
+ ensureHome: (text, options) => this.#ensureHome(text, options),
153
+ requestTimeoutMs: this.requestTimeoutMs,
151
154
  };
152
155
  // A getter, so the modules see the sandbox as it is after each refresh.
153
156
  Object.defineProperty(ctx, "runtime", { get: () => this.#runtime });
154
157
  this.files = new Filesystem(ctx);
155
158
  this.commands = new Commands(ctx);
159
+ this.pty = new Pty(ctx);
156
160
  }
157
161
  get sandboxId() {
158
162
  return this.#runtime.id;
@@ -165,27 +169,50 @@ export class Sandbox {
165
169
  get client() {
166
170
  return this.#client;
167
171
  }
168
- get envs() {
169
- return this.#envs;
170
- }
171
172
  /** E2B's home is /home/user and Runtime's is /workspace. The first time a
172
173
  * path or command names /home/user, it is made a link to /workspace (unless
173
174
  * something is already there), so paths written for E2B work. */
174
- #ensureHome(text) {
175
+ #ensureHome(text, options = {}) {
176
+ const signal = options.signal;
177
+ signal?.throwIfAborted();
178
+ const wait = (pending) => {
179
+ if (!signal)
180
+ return pending;
181
+ return new Promise((resolve, reject) => {
182
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- rejects with the caller's abort reason as is, as the upstream SDK does
183
+ const abort = () => reject(signal.reason);
184
+ signal.addEventListener("abort", abort, { once: true });
185
+ pending.then(() => {
186
+ signal.removeEventListener("abort", abort);
187
+ resolve();
188
+ }, (error) => {
189
+ signal.removeEventListener("abort", abort);
190
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- passes the pending call's own failure through unchanged
191
+ reject(error);
192
+ });
193
+ });
194
+ };
175
195
  if (this.#home || !text?.includes("/home/user"))
176
- return this.#home ?? Promise.resolve();
177
- this.#home = (async () => {
178
- const result = await guard("sandbox", () => this.#runtime.exec("[ -e /home/user ] || sudo ln -s /workspace /home/user"));
196
+ return wait(this.#home ?? Promise.resolve());
197
+ const pending = (async () => {
198
+ const result = await guard("sandbox", () => this.#runtime.exec("[ -e /home/user ] || sudo ln -s /workspace /home/user", { signal }));
179
199
  if (result.exitCode !== 0 && typeof process !== "undefined")
180
200
  process.emitWarning(`Could not link /home/user to /workspace: ${result.stderr.trim()}`, {
181
201
  code: "RUNTIME_E2B_HOME",
182
202
  });
183
203
  })();
184
- return this.#home;
204
+ this.#home = pending;
205
+ pending.catch(() => {
206
+ if (this.#home === pending)
207
+ this.#home = undefined;
208
+ });
209
+ return wait(pending);
185
210
  }
186
211
  static async create(templateOrOpts, maybeOpts) {
187
212
  const opts = (typeof templateOrOpts === "string" ? maybeOpts : templateOrOpts) ?? {};
188
213
  const template = (typeof templateOrOpts === "string" ? templateOrOpts : opts.template) ?? this.defaultTemplate;
214
+ const options = request(opts);
215
+ options.signal?.throwIfAborted();
189
216
  refuseCreate(opts);
190
217
  const client = clientFor(opts);
191
218
  const input = {
@@ -195,9 +222,13 @@ export class Sandbox {
195
222
  // asked (autoResume); Runtime's automatic wake is the same thing (0093).
196
223
  ...(opts.lifecycle ? { autoWake: opts.lifecycle.autoResume === true } : {}),
197
224
  ...(opts.metadata && Object.keys(opts.metadata).length ? { labels: opts.metadata } : {}),
225
+ // Runtime keeps them with the sandbox: every command, terminal and
226
+ // interpreter in it gets them, from this client or any other.
227
+ ...(opts.envs && Object.keys(opts.envs).length ? { env: opts.envs } : {}),
198
228
  ...(opts.allowInternetAccess === false ? { network: { internet: false } } : {}),
199
229
  };
200
- const source = await guard("other", () => resolveTemplate(client, template));
230
+ const source = await guard("other", () => resolveTemplate(client, template, options));
231
+ options.signal?.throwIfAborted();
201
232
  const create = {
202
233
  // A snapshot carries its own machine; everything else gets E2B's shape.
203
234
  ...(source.snapshot ? {} : DEFAULT_SHAPE),
@@ -205,8 +236,8 @@ export class Sandbox {
205
236
  ...source,
206
237
  ...opts.runtime?.create,
207
238
  };
208
- const runtime = await guard("sandbox", () => client.sandboxes.create(create, request(opts)));
209
- return new this(runtime, client, { ...opts.envs });
239
+ const runtime = await guard("sandbox", () => client.sandboxes.create(create, options));
240
+ return new this(runtime, client, opts.requestTimeoutMs);
210
241
  }
211
242
  /** Connects to a sandbox by id, waking it if it is paused, as E2B does. A
212
243
  * `timeoutMs` moves a running sandbox's end later, never earlier. */
@@ -214,7 +245,7 @@ export class Sandbox {
214
245
  const client = clientFor(opts);
215
246
  const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
216
247
  await resume(runtime, opts);
217
- return new this(runtime, client);
248
+ return new this(runtime, client, opts.requestTimeoutMs);
218
249
  }
219
250
  /** Wakes this sandbox if it is paused. */
220
251
  async connect(opts = {}) {
@@ -309,8 +340,9 @@ export class Sandbox {
309
340
  // honoured after the forks exist, so this is refused before any are made.
310
341
  throw new NotSupportedError("A timeout for forks (fork timeoutMs)", "Fork without it, then call setTimeout(ms) on each fork.");
311
342
  const copies = await guard("sandbox", () => this.#runtime.fork({ count: opts.count ?? 1, ...request(opts) }));
343
+ // Copies keep the source's environment on Runtime's side.
312
344
  const Kind = this.constructor;
313
- return copies.map((copy) => new Kind(copy, this.#client, { ...this.#envs }));
345
+ return copies.map((copy) => new Kind(copy, this.#client));
314
346
  }
315
347
  /** Keeps the sandbox's whole machine as a Runtime snapshot; start from it
316
348
  * with Sandbox.create(snapshotId). */
@@ -369,9 +401,6 @@ export class Sandbox {
369
401
  return share;
370
402
  }
371
403
  // ---- what Runtime does differently -----------------------------------
372
- get pty() {
373
- throw new NotSupportedError("E2B's pty module", "Use `await sandbox.runtime.terminal({ cols, rows, onData })` for an interactive terminal, or runtime.spawn(cmd, { pty: {} }).");
374
- }
375
404
  get git() {
376
405
  throw new NotSupportedError("E2B's git module (deprecated by E2B too)", "Run git with sandbox.commands.run('git ...'); git is installed.");
377
406
  }
@@ -454,11 +483,13 @@ const PAUSED = ["pausing", "paused"];
454
483
  /** E2B's paginator: `while (p.hasNext) items.push(...(await p.nextItems()))`. */
455
484
  export class SandboxPaginator {
456
485
  #client;
486
+ #request;
457
487
  #filter;
458
488
  #page;
459
489
  #hasNext = true;
460
490
  constructor(client, opts) {
461
491
  this.#client = client;
492
+ this.#request = request(opts);
462
493
  const query = opts.query ?? {};
463
494
  if (query.template !== undefined && !STOCK_TEMPLATES.has(query.template))
464
495
  throw new NotSupportedError("Listing by template", "Filter by metadata instead: give sandboxes a metadata key when you create them.");
@@ -482,10 +513,13 @@ export class SandboxPaginator {
482
513
  return this.#page?.nextCursor ?? undefined;
483
514
  }
484
515
  async nextItems() {
516
+ this.#request.signal?.throwIfAborted();
485
517
  if (!this.#hasNext)
486
518
  throw new SandboxError("No more items to fetch.");
487
519
  const previous = this.#page;
488
- const page = await guard("other", async () => previous ? await previous.next() : await this.#client.sandboxes.list(this.#filter));
520
+ const page = await guard("other", async () => previous
521
+ ? await previous.next()
522
+ : await this.#client.sandboxes.list(this.#filter, this.#request));
489
523
  this.#page = page ?? undefined;
490
524
  this.#hasNext = Boolean(page?.hasMore);
491
525
  return (page?.data ?? []).map(infoOf);
package/dist/errors.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ /** Every failure the SDK raises. `code` is stable and machine-readable,
2
+ * `hint` says what to do next, and `requestId` is what to quote in a report
3
+ * (runtime.feedback.submit, `runtime feedback`, or support). */
4
+ /** A product switched off on this deployment, or paused on purpose: the API
5
+ * answers these 503s in fixed words (`serverFault` "off" in
6
+ * packages/cloud/src/api/respond.ts) and retrying cannot change them.
7
+ * host_unavailable and busy pass, and are retried. */
8
+ export declare const DELIBERATE: ReadonlySet<string>;
1
9
  /** Refusals of a create that clear when a sandbox stops or pauses, or a host
2
10
  * frees room: the trial's slots, an email domain's trial slots, the account's
3
11
  * quota and the region's capacity. `sandboxes.create` waits them out, retrying