withruntime 0.11.1 → 0.11.2

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 (53) hide show
  1. package/BLAXEL.md +1 -1
  2. package/DAYTONA.md +1 -1
  3. package/E2B.md +57 -29
  4. package/README.md +4 -3
  5. package/README.src.md +4 -3
  6. package/compatibility-lock.json +14 -14
  7. package/dist/blaxel/errors.js +4 -4
  8. package/dist/blaxel/sandbox.d.ts +1 -2
  9. package/dist/blaxel/sandbox.js +1 -2
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +63 -15
  12. package/dist/client.d.ts +11 -59
  13. package/dist/client.js +8 -113
  14. package/dist/daytona/daytona.d.ts +1 -2
  15. package/dist/daytona/daytona.js +1 -2
  16. package/dist/daytona/sandbox.d.ts +1 -1
  17. package/dist/daytona/sandbox.js +1 -1
  18. package/dist/e2b/client.d.ts +15 -2
  19. package/dist/e2b/client.js +61 -7
  20. package/dist/e2b/commands.d.ts +3 -0
  21. package/dist/e2b/commands.js +16 -5
  22. package/dist/e2b/errors.d.ts +26 -2
  23. package/dist/e2b/errors.js +38 -5
  24. package/dist/e2b/filesystem.d.ts +1 -1
  25. package/dist/e2b/filesystem.js +82 -16
  26. package/dist/e2b/index.d.ts +2 -2
  27. package/dist/e2b/index.js +2 -2
  28. package/dist/e2b/sandbox.d.ts +23 -14
  29. package/dist/e2b/sandbox.js +195 -178
  30. package/dist/e2b/unsupported.d.ts +19 -0
  31. package/dist/e2b/unsupported.js +21 -0
  32. package/dist/e2b/users.d.ts +9 -0
  33. package/dist/e2b/users.js +22 -0
  34. package/dist/errors.d.ts +4 -4
  35. package/dist/errors.js +12 -10
  36. package/dist/machine.d.ts +180 -0
  37. package/dist/machine.js +504 -0
  38. package/dist/openai-agents/index.d.ts +1 -1
  39. package/dist/openai-agents/index.js +1 -1
  40. package/dist/products/jobs.d.ts +2 -3
  41. package/dist/products/jobs.js +1 -2
  42. package/dist/products/limits.d.ts +4 -10
  43. package/dist/products/network.d.ts +1 -1
  44. package/dist/products/secrets.d.ts +1 -1
  45. package/dist/products/switching.d.ts +2 -3
  46. package/dist/sandbox.d.ts +7 -92
  47. package/dist/sandbox.js +8 -362
  48. package/dist/transport.d.ts +4 -2
  49. package/dist/transport.js +6 -4
  50. package/dist/types.d.ts +53 -8
  51. package/dist/vercel/sandbox.d.ts +1 -2
  52. package/dist/vercel/sandbox.js +1 -2
  53. package/package.json +1 -1
package/dist/client.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { RuntimeError } from "./errors.js";
2
- import type { Page } from "./page.js";
3
1
  import { type ClientExtensions } from "./products/index.js";
4
2
  import { usageExport as makeUsageExport } from "./products/observability.js";
3
+ import { Machines } from "./machine.js";
5
4
  import { Sandbox } from "./sandbox.js";
6
5
  import { Snapshots } from "./snapshots.js";
7
6
  import { Transport, type RequestOptions } from "./transport.js";
8
7
  import type { CreateSandbox, DeletedSandbox, FeedbackKind, SandboxInfo, Usage } from "./types.js";
8
+ export type { CreateOptions } from "./machine.js";
9
9
  export type RuntimeOptions = {
10
10
  /** Default: the RUNTIME_API_KEY environment variable. */
11
11
  apiKey?: string;
@@ -22,9 +22,12 @@ export type RuntimeOptions = {
22
22
  * before it has used a valid key. 8 are kept for calls whatever the
23
23
  * streams hold. */
24
24
  maxConnections?: number;
25
+ /** Called each time a call has to wait for one of those connections: an
26
+ * adapter warns with it, once, that its caller is being held back. */
27
+ onQueued?: (maxConnections: number) => void;
25
28
  /** How long `sandboxes.create` keeps retrying, with the same key and input,
26
- * when the trial's slots, the account's quota or the region is full
27
- * (trial_busy, quota_exceeded, no_capacity and the like). Default 120 000
29
+ * when the slots without credit, the account's quota or the region is full
30
+ * (no_credit_running_limit, quota_exceeded, no_capacity and the like). Default 120 000
28
31
  * (two minutes); 0 fails at once. The refusal is thrown as it came when the
29
32
  * wait runs out. */
30
33
  waitForCapacityMs?: number;
@@ -57,8 +60,8 @@ export declare class Runtime {
57
60
  /** The role of the member who made this key: owner, admin, developer or billing. */
58
61
  role?: string | null;
59
62
  }>;
60
- /** Balance, holds, trial time and per-resource charges, in integer microdollars. */
61
- /** Credit, holds, trial time and per-resource charges. */
63
+ /** Balance, holds, free time and per-resource charges, in integer microdollars. */
64
+ /** Credit, holds, free time and per-resource charges. */
62
65
  usage(options?: RequestOptions): Promise<Usage>;
63
66
  /** This account's authenticated API answers, in hourly windows. Counts are
64
67
  * exact decimal strings, and an empty window has a null error percentage.
@@ -80,60 +83,9 @@ export declare class Runtime {
80
83
  }>;
81
84
  }>;
82
85
  }
83
- /** How `sandboxes.create` waits when every trial slot, the account's quota or
84
- * the region is full. */
85
- export type CreateOptions = RequestOptions & {
86
- /** How long to keep retrying a refusal that clears by itself. Default: the
87
- * client's, two minutes; 0 fails at once. */
88
- waitForCapacityMs?: number;
89
- /** Called before each wait, with the refusal and the milliseconds until the
90
- * next try: to tell a person why nothing has happened yet. */
91
- onCapacityWait?: (refusal: RuntimeError, waitMs: number) => void;
92
- };
93
- export declare class Sandboxes {
94
- private readonly t;
86
+ /** `runtime.sandboxes`: the sandbox product of the machine engine. */
87
+ export declare class Sandboxes extends Machines<Sandbox, SandboxInfo, CreateSandbox, DeletedSandbox> {
95
88
  constructor(t: Transport);
96
- /** Creates a sandbox and waits until it is running. Every field is optional:
97
- * with none you get the free trial while it lasts, the default region and a
98
- * 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
99
- * When every trial slot or the account's quota is taken, it waits for one to
100
- * free, up to `waitForCapacityMs` (the client's, two minutes by default). */
101
- create(input?: CreateSandbox & {
102
- wait?: boolean;
103
- }, options?: CreateOptions): Promise<Sandbox>;
104
- /** The sandbox named `name` in this account, ready to use: running as it
105
- * is, woken if it is paused, restarted if it is stopped and persistent, or
106
- * created with `input` when no sandbox has the name. `sandbox.info.reused`
107
- * says which. The other fields apply only when it is created. */
108
- getOrCreate(name: string, input?: Omit<CreateSandbox, "name" | "getOrCreate"> & {
109
- wait?: boolean;
110
- }, options?: CreateOptions): Promise<Sandbox>;
111
- get(id: string, options?: RequestOptions): Promise<Sandbox>;
112
- /** Deletes a sandbox for good, by id, without reading it first: see
113
- * `sandbox.delete()`. Deleting it again answers the same. */
114
- delete(id: string, options?: RequestOptions): Promise<DeletedSandbox>;
115
- /** Live sandboxes, oldest first. Await for a page, or `for await` over all.
116
- * Request options apply to the initial request and every subsequent page. */
117
- list(filter?: {
118
- state?: SandboxInfo["state"][];
119
- includeStopped?: boolean;
120
- /** Every label must match. */
121
- labels?: Record<string, string>;
122
- name?: string;
123
- limit?: number;
124
- }, options?: RequestOptions): Promise<Page<Sandbox>>;
125
- /** Stops every live sandbox whose labels all match, eight at a time, and
126
- * says which stopped and which failed; one failure does not stop the rest.
127
- * Needs at least one label, so stopping everything is never one call. */
128
- stopAll(filter: {
129
- labels: Record<string, string>;
130
- }, options?: RequestOptions): Promise<{
131
- stopped: string[];
132
- failed: {
133
- id: string;
134
- error: unknown;
135
- }[];
136
- }>;
137
89
  }
138
90
  export declare class FeedbackApi {
139
91
  private readonly t;
package/dist/client.js CHANGED
@@ -1,8 +1,7 @@
1
- import { WAIT_FOR_TIMEOUT_SECONDS } from "./api-defaults.js";
2
- import { RuntimeError } from "./errors.js";
3
1
  import { clientFactories, clientProductAliases } from "./products/index.js";
4
2
  import { usageExport as makeUsageExport } from "./products/observability.js";
5
- import { Sandbox, deleteSandbox, sandboxPage } from "./sandbox.js";
3
+ import { Machines } from "./machine.js";
4
+ import { SANDBOXES, Sandbox } from "./sandbox.js";
6
5
  import { Snapshots } from "./snapshots.js";
7
6
  import { DEFAULT_BASE_URL, Transport, missingKey } from "./transport.js";
8
7
  /** The key `runtime login` saved for this machine and this API origin, found
@@ -43,6 +42,7 @@ export class Runtime {
43
42
  ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
44
43
  ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
45
44
  ...(options.maxConnections !== undefined ? { maxConnections: options.maxConnections } : {}),
45
+ ...(options.onQueued ? { onQueued: options.onQueued } : {}),
46
46
  ...(options.waitForCapacityMs !== undefined
47
47
  ? { waitForCapacityMs: options.waitForCapacityMs }
48
48
  : {}),
@@ -68,8 +68,8 @@ export class Runtime {
68
68
  ...options,
69
69
  });
70
70
  }
71
- /** Balance, holds, trial time and per-resource charges, in integer microdollars. */
72
- /** Credit, holds, trial time and per-resource charges. */
71
+ /** Balance, holds, free time and per-resource charges, in integer microdollars. */
72
+ /** Credit, holds, free time and per-resource charges. */
73
73
  usage(options = {}) {
74
74
  return this.transport.json({
75
75
  method: "GET",
@@ -89,115 +89,10 @@ export class Runtime {
89
89
  });
90
90
  }
91
91
  }
92
- export class Sandboxes {
93
- t;
92
+ /** `runtime.sandboxes`: the sandbox product of the machine engine. */
93
+ export class Sandboxes extends Machines {
94
94
  constructor(t) {
95
- this.t = t;
96
- }
97
- /** Creates a sandbox and waits until it is running. Every field is optional:
98
- * with none you get the free trial while it lasts, the default region and a
99
- * 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
100
- * When every trial slot or the account's quota is taken, it waits for one to
101
- * free, up to `waitForCapacityMs` (the client's, two minutes by default). */
102
- async create(input = {}, options = {}) {
103
- const { wait, ...body } = input;
104
- const began = performance.now();
105
- // Time spent waiting for capacity is the room's, not the deadline's.
106
- let waited = 0;
107
- const info = await this.t.json({
108
- method: "POST",
109
- path: "/v1/sandboxes",
110
- body,
111
- wait: wait === false ? 0 : 60,
112
- ...options,
113
- waitForCapacityMs: options.waitForCapacityMs ?? this.t.waitForCapacityMs,
114
- onCapacityWait: (refusal, waitMs) => {
115
- waited += waitMs;
116
- options.onCapacityWait?.(refusal, waitMs);
117
- },
118
- });
119
- const sandbox = new Sandbox(this.t, info);
120
- if (wait !== false && info.state !== "running") {
121
- // One deadline for the whole create: the wait for running gets what the
122
- // create left of it (1 ms when nothing is left, so it ends as a timeout).
123
- const { timeoutMs } = options;
124
- await sandbox.waitFor("running", {
125
- timeoutSeconds: WAIT_FOR_TIMEOUT_SECONDS,
126
- signal: options.signal,
127
- timeoutMs: timeoutMs
128
- ? Math.max(1, Math.ceil(timeoutMs - (performance.now() - began - waited)))
129
- : timeoutMs,
130
- idempotencyKey: options.idempotencyKey,
131
- });
132
- if (sandbox.state !== "running")
133
- throw new RuntimeError({
134
- message: `Sandbox ${info.id} is ${sandbox.state}, not running.`,
135
- code: "start_failed",
136
- status: 0,
137
- hint: "Read it with runtime.sandboxes.get(id); stopReason says why.",
138
- });
139
- }
140
- return sandbox;
141
- }
142
- /** The sandbox named `name` in this account, ready to use: running as it
143
- * is, woken if it is paused, restarted if it is stopped and persistent, or
144
- * created with `input` when no sandbox has the name. `sandbox.info.reused`
145
- * says which. The other fields apply only when it is created. */
146
- async getOrCreate(name, input = {}, options = {}) {
147
- return this.create({ ...input, name, getOrCreate: true }, options);
148
- }
149
- async get(id, options = {}) {
150
- return new Sandbox(this.t, await this.t.json({
151
- method: "GET",
152
- path: `/v1/sandboxes/${encodeURIComponent(id)}`,
153
- ...options,
154
- }));
155
- }
156
- /** Deletes a sandbox for good, by id, without reading it first: see
157
- * `sandbox.delete()`. Deleting it again answers the same. */
158
- delete(id, options = {}) {
159
- return deleteSandbox(this.t, id, options);
160
- }
161
- /** Live sandboxes, oldest first. Await for a page, or `for await` over all.
162
- * Request options apply to the initial request and every subsequent page. */
163
- async list(filter = {}, options = {}) {
164
- const query = {
165
- ...(filter.state ? { state: filter.state } : {}),
166
- ...(filter.includeStopped ? { includeStopped: true } : {}),
167
- ...(filter.labels
168
- ? { label: Object.entries(filter.labels).map(([k, v]) => `${k}:${v}`) }
169
- : {}),
170
- ...(filter.name ? { name: filter.name } : {}),
171
- ...(filter.limit ? { limit: filter.limit } : {}),
172
- };
173
- return sandboxPage(this.t, await this.t.json({ method: "GET", path: "/v1/sandboxes", query, ...options }), query, options);
174
- }
175
- /** Stops every live sandbox whose labels all match, eight at a time, and
176
- * says which stopped and which failed; one failure does not stop the rest.
177
- * Needs at least one label, so stopping everything is never one call. */
178
- async stopAll(filter, options = {}) {
179
- if (!filter?.labels || Object.keys(filter.labels).length === 0)
180
- throw new RuntimeError({
181
- message: "stopAll needs at least one label to match.",
182
- code: "invalid_request",
183
- status: 0,
184
- hint: "Stop one sandbox with sandbox.stop(), or label the ones to stop together.",
185
- });
186
- const live = await (await this.list({ labels: filter.labels, limit: 100 }, options)).toArray(Infinity);
187
- // One key cannot name several stops: each stop makes its own.
188
- const { idempotencyKey: _one, ...each } = options;
189
- // In list order, oldest first, whichever finishes first.
190
- const errors = new Array(live.length);
191
- let next = 0;
192
- const worker = async () => {
193
- for (let at = next++; at < live.length; at = next++)
194
- await live[at].stop(each).catch((error) => (errors[at] = error ?? "failed"));
195
- };
196
- await Promise.all(Array.from({ length: Math.min(8, live.length) }, worker));
197
- return {
198
- stopped: live.filter((_, at) => errors[at] === undefined).map((sbx) => sbx.id),
199
- failed: live.flatMap((sbx, at) => errors[at] === undefined ? [] : [{ id: sbx.id, error: errors[at] }]),
200
- };
95
+ super(SANDBOXES, t, (transport, info) => new Sandbox(transport, info));
201
96
  }
202
97
  }
203
98
  export class FeedbackApi {
@@ -177,8 +177,7 @@ export declare class Daytona implements AsyncDisposable {
177
177
  [Symbol.asyncDispose](): Promise<void>;
178
178
  /** Creates a sandbox and waits until it runs, with Daytona's defaults: 1
179
179
  * vCPU, 1 GiB, 3 GiB disk, pausing after 15 minutes without calls. Funding
180
- * is left to Runtime: the free trial while the account has trial time, then
181
- * prepaid credit, exactly as withruntime's own create. */
180
+ * is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
182
181
  create(params?: CreateSandboxFromSnapshotParams | CreateSandboxFromImageParams, options?: {
183
182
  timeout?: number;
184
183
  onSnapshotCreateLogs?: (chunk: string) => void;
@@ -183,8 +183,7 @@ export class Daytona {
183
183
  async [Symbol.asyncDispose]() { }
184
184
  /** Creates a sandbox and waits until it runs, with Daytona's defaults: 1
185
185
  * vCPU, 1 GiB, 3 GiB disk, pausing after 15 minutes without calls. Funding
186
- * is left to Runtime: the free trial while the account has trial time, then
187
- * prepaid credit, exactly as withruntime's own create. */
186
+ * is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
188
187
  async create(params = {}, options = {}) {
189
188
  refuseCreate(params);
190
189
  const client = params.withruntime?.client ?? this.#client;
@@ -125,7 +125,7 @@ export declare class Sandbox {
125
125
  /** Pauses the sandbox with its memory. */
126
126
  pause(_timeout?: number): Promise<void>;
127
127
  /** Runtime has no archive tier: a stopped sandbox stays paused, kept for
128
- * its retention (30 days paid, 7 on the trial, or autoDeleteInterval). */
128
+ * its retention (30 days paid, 7 without credit, or autoDeleteInterval). */
129
129
  archive(): Promise<void>;
130
130
  /** Ends the sandbox for good. */
131
131
  delete(_timeout?: number): Promise<void>;
@@ -269,7 +269,7 @@ export class Sandbox {
269
269
  this.#pausedByPause = true;
270
270
  }
271
271
  /** Runtime has no archive tier: a stopped sandbox stays paused, kept for
272
- * its retention (30 days paid, 7 on the trial, or autoDeleteInterval). */
272
+ * its retention (30 days paid, 7 without credit, or autoDeleteInterval). */
273
273
  async archive() {
274
274
  await this.stop();
275
275
  }
@@ -43,15 +43,28 @@ export interface RuntimeOpts {
43
43
  /** Refuses the E2B-only connection options: they point at E2B, and quietly
44
44
  * dropping one would send the call somewhere its author did not mean. */
45
45
  export declare function refuseE2BConnection(opts?: ConnectionOpts): void;
46
+ /** Connections the adapter's own client may hold at once: E2B's SDK has no
47
+ * cap of its own, so an orchestrator that drives a thousand sandboxes from one
48
+ * process runs every command at once. 4,096 is the widest room Runtime's edge
49
+ * gives an address that has used a valid key (api.md, "Limits"); the edge and
50
+ * the API answer anything past an account's room with a retry, which the
51
+ * client waits out. Runtime's own SDK keeps 48. */
52
+ export declare const E2B_MAX_CONNECTIONS = 4096;
53
+ /** Said once per process, the first time a call has to wait for a
54
+ * connection: E2B never holds a call back, so a caller should hear it. */
55
+ export declare function warnQueued(maxConnections: number): void;
46
56
  /** The Runtime client for these options: an explicit client, else one per key,
47
57
  * made once and reused so its connections stay open. */
48
58
  export declare function clientFor(opts?: ConnectionOpts & {
49
59
  runtime?: RuntimeOpts;
50
60
  }): Runtime;
51
61
  /** Explicit key, then RUNTIME_API_KEY, then E2B_API_KEY when it holds a
52
- * Runtime key; undefined lets the Runtime SDK find the saved login. */
62
+ * Runtime key; undefined, when none is set, lets the Runtime SDK find the
63
+ * saved login. An E2B key is refused, never sent. */
53
64
  export declare function pickKey(explicit?: string): string | undefined;
54
- /** Test hook: forget the cached clients. */
65
+ /** Where the key in use came from, as an error names it. */
66
+ export declare function keySource(explicit?: string): string;
67
+ /** Test hook: forget the cached clients and the warnings. */
55
68
  export declare function resetClients(): void;
56
69
  /** Request options for a Runtime call from E2B's. */
57
70
  export declare function request(opts?: {
@@ -26,6 +26,23 @@ export function refuseE2BConnection(opts = {}) {
26
26
  throw new NotSupportedError("E2B's debug mode (a local envd)", "Remove debug: true; there is no local Runtime guest to connect to.");
27
27
  }
28
28
  const clients = new Map();
29
+ /** Connections the adapter's own client may hold at once: E2B's SDK has no
30
+ * cap of its own, so an orchestrator that drives a thousand sandboxes from one
31
+ * process runs every command at once. 4,096 is the widest room Runtime's edge
32
+ * gives an address that has used a valid key (api.md, "Limits"); the edge and
33
+ * the API answer anything past an account's room with a retry, which the
34
+ * client waits out. Runtime's own SDK keeps 48. */
35
+ export const E2B_MAX_CONNECTIONS = 4096;
36
+ let warnedQueued = false;
37
+ /** Said once per process, the first time a call has to wait for a
38
+ * connection: E2B never holds a call back, so a caller should hear it. */
39
+ export function warnQueued(maxConnections) {
40
+ if (warnedQueued || typeof process === "undefined" || !process.emitWarning)
41
+ return;
42
+ warnedQueued = true;
43
+ process.emitWarning(`More than ${maxConnections} calls and command streams are open at once from this process, so the rest wait their turn. ` +
44
+ "Pass runtime: { client: new Runtime({ maxConnections }) } for more.", { code: "RUNTIME_E2B_QUEUED" });
45
+ }
29
46
  /** The Runtime client for these options: an explicit client, else one per key,
30
47
  * made once and reused so its connections stay open. */
31
48
  export function clientFor(opts = {}) {
@@ -36,30 +53,67 @@ export function clientFor(opts = {}) {
36
53
  const cacheKey = key ?? "";
37
54
  let client = clients.get(cacheKey);
38
55
  if (!client) {
39
- client = new Runtime(key ? { apiKey: key } : {});
56
+ client = new Runtime({
57
+ ...(key ? { apiKey: key } : {}),
58
+ maxConnections: E2B_MAX_CONNECTIONS,
59
+ onQueued: warnQueued,
60
+ });
40
61
  clients.set(cacheKey, client);
41
62
  }
42
63
  return client;
43
64
  }
44
65
  /** Explicit key, then RUNTIME_API_KEY, then E2B_API_KEY when it holds a
45
- * Runtime key; undefined lets the Runtime SDK find the saved login. */
66
+ * Runtime key; undefined, when none is set, lets the Runtime SDK find the
67
+ * saved login. An E2B key is refused, never sent. */
46
68
  export function pickKey(explicit) {
47
69
  const runtimeKey = env("RUNTIME_API_KEY");
48
70
  if (explicit !== undefined && !isE2BKey(explicit))
49
71
  return explicit;
50
- if (runtimeKey)
72
+ if (runtimeKey) {
73
+ warnTwoKeys(runtimeKey, env("E2B_API_KEY"));
51
74
  return runtimeKey;
75
+ }
52
76
  if (explicit !== undefined)
53
77
  throw new AuthenticationError("The apiKey passed is an E2B API key (e2b_...), and it was not sent. Set RUNTIME_API_KEY to a Runtime key " +
54
78
  "(https://withruntime.com/account/keys, or run `npx withruntime login`), or pass the Runtime key as apiKey.");
55
- // An E2B key left in E2B_API_KEY is skipped, never sent: the Runtime SDK
56
- // then uses the key `npx withruntime login` saved, or says none was found.
79
+ // An E2B key left in E2B_API_KEY is never sent, and never quietly passed
80
+ // over for the key `npx withruntime login` saved, which may be another
81
+ // account's.
57
82
  const e2bVariable = env("E2B_API_KEY");
58
- return e2bVariable && !isE2BKey(e2bVariable) ? e2bVariable : undefined;
83
+ if (e2bVariable && isE2BKey(e2bVariable))
84
+ throw new AuthenticationError("E2B_API_KEY holds an E2B API key (e2b_...), and it was not sent. Set RUNTIME_API_KEY to a Runtime key " +
85
+ "(https://withruntime.com/account/keys, or run `npx withruntime login`), or put the Runtime key in E2B_API_KEY.");
86
+ return e2bVariable || undefined;
87
+ }
88
+ /** Where the key in use came from, as an error names it. */
89
+ export function keySource(explicit) {
90
+ if (explicit !== undefined && !isE2BKey(explicit))
91
+ return "apiKey";
92
+ if (env("RUNTIME_API_KEY"))
93
+ return "RUNTIME_API_KEY";
94
+ if (env("E2B_API_KEY"))
95
+ return "E2B_API_KEY";
96
+ return "the saved login";
97
+ }
98
+ let warnedTwoKeys = false;
99
+ /** Said once per process: RUNTIME_API_KEY wins, as in Runtime's own SDK, but
100
+ * code written for E2B means E2B_API_KEY, so two different Runtime keys are
101
+ * likely two accounts, and the wrong one fails later as a missing image
102
+ * (5 October 2026). */
103
+ function warnTwoKeys(runtimeKey, e2bVariable) {
104
+ if (!e2bVariable || isE2BKey(e2bVariable) || e2bVariable === runtimeKey)
105
+ return;
106
+ if (warnedTwoKeys || typeof process === "undefined" || !process.emitWarning)
107
+ return;
108
+ warnedTwoKeys = true;
109
+ process.emitWarning("RUNTIME_API_KEY and E2B_API_KEY hold different Runtime keys, which may be different accounts; this package uses " +
110
+ "RUNTIME_API_KEY. Unset one, or set both to the same key.", { code: "RUNTIME_E2B_TWO_KEYS" });
59
111
  }
60
- /** Test hook: forget the cached clients. */
112
+ /** Test hook: forget the cached clients and the warnings. */
61
113
  export function resetClients() {
62
114
  clients.clear();
115
+ warnedQueued = false;
116
+ warnedTwoKeys = false;
63
117
  }
64
118
  /** Request options for a Runtime call from E2B's. */
65
119
  export function request(opts = {}) {
@@ -43,6 +43,9 @@ export interface SandboxContext {
43
43
  /** Makes /home/user (E2B's home) lead to /workspace (Runtime's), once, when
44
44
  * something names it. */
45
45
  ensureHome(text: string | undefined, options?: RequestOptions): Promise<void>;
46
+ /** Whether /home/user leads to /workspace, linking it first if nothing is
47
+ * there; false when that cannot be told. Never fails. */
48
+ homeIsWorkspace(): Promise<boolean>;
46
49
  }
47
50
  /** E2B's pids are numbers; Runtime's process ids are strings. The number is a
48
51
  * 31-bit FNV-1a hash of the id, so every client derives the same one. */
@@ -1,5 +1,5 @@
1
- import { CommandExitError, guard, InvalidArgumentError, SandboxError, TimeoutError, translate, } from "./errors.js";
2
- import { commandAs, runAs } from "./users.js";
1
+ import { CommandExitError, guard, InvalidArgumentError, SandboxError, SandboxNotFoundError, TimeoutError, translate, } from "./errors.js";
2
+ import { commandAs, listedAs, runAs } from "./users.js";
3
3
  const DEFAULT_TIMEOUT_MS = 60_000;
4
4
  /** How long a command may run on Runtime: a day, E2B's longest sandbox. E2B's
5
5
  * own `timeoutMs` bounds only the connection, so the process gets this. */
@@ -238,12 +238,19 @@ export class Commands {
238
238
  function describe(info) {
239
239
  return {
240
240
  pid: pidOf(info.id),
241
- cmd: "/bin/bash",
242
- args: ["-c", info.command],
241
+ ...listedAs(info.command),
243
242
  envs: {},
244
243
  cwd: info.cwd,
245
244
  };
246
245
  }
246
+ /** A command whose sandbox went away under it, as E2B says it: a
247
+ * TimeoutError, which code written for E2B catches (5 October 2026: it was a
248
+ * SandboxNotFoundError). */
249
+ function sandboxEnded(cause) {
250
+ const error = new TimeoutError("The command ended before the stream completed: the sandbox was killed or reached its end of life.");
251
+ error.cause = cause;
252
+ return error;
253
+ }
247
254
  /** Reader cancellation also releases a callback whose promise never settles. */
248
255
  async function deliver(signal, callback) {
249
256
  signal.throwIfAborted();
@@ -349,7 +356,11 @@ export class CommandHandle {
349
356
  if (!this.#disconnected && !this.#failure) {
350
357
  const translated = translate(error, "sandbox");
351
358
  this.#failure =
352
- translated instanceof Error ? translated : new SandboxError(String(translated));
359
+ translated instanceof SandboxNotFoundError
360
+ ? sandboxEnded(translated)
361
+ : translated instanceof Error
362
+ ? translated
363
+ : new SandboxError(String(translated));
353
364
  }
354
365
  }
355
366
  finally {
@@ -37,6 +37,31 @@ export declare class ServiceBusyError extends Error {
37
37
  requestId?: string;
38
38
  constructor(message: string);
39
39
  }
40
+ /** E2B's other error classes, by the same names and parents, so a module
41
+ * that imports or catches them loads and works: nothing in this package
42
+ * throws them, since the features behind them (template builds, git,
43
+ * volumes, secrets) answer NotSupportedError here. */
44
+ export declare class GitAuthError extends AuthenticationError {
45
+ }
46
+ export declare class GitUpstreamError extends SandboxError {
47
+ }
48
+ export declare class BuildError extends Error {
49
+ constructor(message?: string);
50
+ }
51
+ export declare class FileUploadError extends BuildError {
52
+ }
53
+ export declare class VolumeError extends Error {
54
+ constructor(message?: string);
55
+ }
56
+ export declare class VolumeNotFoundError extends VolumeError {
57
+ }
58
+ export declare class VolumePathNotFoundError extends VolumeError {
59
+ }
60
+ export declare class SecretError extends Error {
61
+ constructor(message?: string);
62
+ }
63
+ export declare class SecretNotFoundError extends SecretError {
64
+ }
40
65
  /** A call that E2B supports and Runtime does not, or not in the same way.
41
66
  * Thrown before anything is done, never after doing something different.
42
67
  * `feature` names what was asked for; `alternative` says what to use on
@@ -46,8 +71,7 @@ export declare class NotSupportedError extends SandboxError {
46
71
  readonly alternative: string;
47
72
  constructor(feature: string, alternative: string, message?: string);
48
73
  }
49
- /** Runtime's addition, for `getHost` and `getPublicHost`: a free-trial
50
- * sandbox shares a port only privately, so a request needs the port's token,
74
+ /** Runtime's addition, for `getHost` and `getPublicHost`: a sandbox without credit shares a port only privately, so a request needs the port's token,
51
75
  * and a host name alone cannot carry one. The message says what works. A
52
76
  * NotSupportedError, so code that catches those catches this too. */
53
77
  export declare class PublicPreviewNotAllowedError extends NotSupportedError {
@@ -58,6 +58,40 @@ export class ServiceBusyError extends Error {
58
58
  this.name = new.target.name;
59
59
  }
60
60
  }
61
+ /** E2B's other error classes, by the same names and parents, so a module
62
+ * that imports or catches them loads and works: nothing in this package
63
+ * throws them, since the features behind them (template builds, git,
64
+ * volumes, secrets) answer NotSupportedError here. */
65
+ export class GitAuthError extends AuthenticationError {
66
+ }
67
+ export class GitUpstreamError extends SandboxError {
68
+ }
69
+ export class BuildError extends Error {
70
+ constructor(message) {
71
+ super(message);
72
+ this.name = new.target.name;
73
+ }
74
+ }
75
+ export class FileUploadError extends BuildError {
76
+ }
77
+ export class VolumeError extends Error {
78
+ constructor(message) {
79
+ super(message);
80
+ this.name = new.target.name;
81
+ }
82
+ }
83
+ export class VolumeNotFoundError extends VolumeError {
84
+ }
85
+ export class VolumePathNotFoundError extends VolumeError {
86
+ }
87
+ export class SecretError extends Error {
88
+ constructor(message) {
89
+ super(message);
90
+ this.name = new.target.name;
91
+ }
92
+ }
93
+ export class SecretNotFoundError extends SecretError {
94
+ }
61
95
  /** A call that E2B supports and Runtime does not, or not in the same way.
62
96
  * Thrown before anything is done, never after doing something different.
63
97
  * `feature` names what was asked for; `alternative` says what to use on
@@ -72,19 +106,18 @@ export class NotSupportedError extends SandboxError {
72
106
  this.code = "not_supported";
73
107
  }
74
108
  }
75
- /** Runtime's addition, for `getHost` and `getPublicHost`: a free-trial
76
- * sandbox shares a port only privately, so a request needs the port's token,
109
+ /** Runtime's addition, for `getHost` and `getPublicHost`: a sandbox without credit shares a port only privately, so a request needs the port's token,
77
110
  * and a host name alone cannot carry one. The message says what works. A
78
111
  * NotSupportedError, so code that catches those catches this too. */
79
112
  export class PublicPreviewNotAllowedError extends NotSupportedError {
80
113
  constructor(sandboxId, port, message) {
81
114
  const where = port === undefined ? "<port>" : String(port);
82
- const alternative = `For an address that works on the trial, use (await sandbox.runtime.previews.create(${where})).urlWithToken: ` +
115
+ const alternative = `For an address that works without credit, use (await sandbox.runtime.previews.create(${where})).urlWithToken: ` +
83
116
  "it carries the token, in a browser, fetch or curl. For other paths on it, send the token as the " +
84
117
  "x-runtime-preview-token header or the runtime_preview_token query parameter. A public host needs a paid " +
85
118
  "sandbox, which is the account owner's decision.";
86
- super("A public address on a free-trial sandbox", alternative, message ??
87
- `Sandbox ${sandboxId} runs on the free trial, where a shared port is private: every request needs the ` +
119
+ super("A public address on a sandbox without credit", alternative, message ??
120
+ `Sandbox ${sandboxId} runs without credit, where a shared port is private: every request needs the ` +
88
121
  `port's token, and a host name alone cannot carry one. ${alternative}`);
89
122
  this.code = "public_preview_not_allowed";
90
123
  }
@@ -72,7 +72,7 @@ export interface FilesystemListOpts extends FilesystemRequestOpts {
72
72
  }
73
73
  /** `sandbox.files`: E2B's filesystem module over Runtime's files API. Relative
74
74
  * paths resolve against the home directory, as in E2B (Runtime's is
75
- * /workspace). */
75
+ * /workspace), and come back under /home/user, as E2B gives them. */
76
76
  export declare class Filesystem {
77
77
  #private;
78
78
  constructor(ctx: SandboxContext);