withruntime 0.11.2 → 0.14.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 (171) hide show
  1. package/BLAXEL.md +2 -2
  2. package/COMPATIBILITY.md +74 -9
  3. package/DAYTONA.md +33 -5
  4. package/E2B.md +85 -24
  5. package/README.md +11 -10
  6. package/README.src.md +10 -9
  7. package/VERCEL.md +43 -5
  8. package/compatibility-lock.json +58 -49
  9. package/dist/billing-cli.d.ts +1 -1
  10. package/dist/billing-cli.js +85 -5
  11. package/dist/blaxel/errors.d.ts +14 -0
  12. package/dist/blaxel/errors.js +29 -0
  13. package/dist/blaxel/index.d.ts +2 -2
  14. package/dist/blaxel/index.js +4 -4
  15. package/dist/blaxel/interpreter.d.ts +2 -5
  16. package/dist/blaxel/sandbox.d.ts +15 -6
  17. package/dist/blaxel/sandbox.js +64 -8
  18. package/dist/blaxel/snapshot.d.ts +4 -0
  19. package/dist/blaxel/snapshot.js +4 -0
  20. package/dist/cli/logs.d.ts +21 -0
  21. package/dist/cli/logs.js +188 -0
  22. package/dist/cli-extras.d.ts +3 -3
  23. package/dist/cli-extras.js +17 -12
  24. package/dist/cli-words.d.ts +10 -1
  25. package/dist/cli-words.js +18 -0
  26. package/dist/cli.d.ts +22 -3
  27. package/dist/cli.js +647 -273
  28. package/dist/client.d.ts +2 -4
  29. package/dist/client.js +27 -11
  30. package/dist/cloudflare/errors.d.ts +143 -0
  31. package/dist/cloudflare/errors.js +167 -0
  32. package/dist/cloudflare/index.d.ts +175 -6
  33. package/dist/cloudflare/index.js +416 -26
  34. package/dist/cloudflare/interpreter.d.ts +119 -0
  35. package/dist/cloudflare/interpreter.js +223 -0
  36. package/dist/cloudflare/streams.d.ts +38 -0
  37. package/dist/cloudflare/streams.js +145 -0
  38. package/dist/cloudflare/watch.d.ts +9 -0
  39. package/dist/cloudflare/watch.js +24 -0
  40. package/dist/codesandbox/index.d.ts +216 -28
  41. package/dist/codesandbox/index.js +478 -78
  42. package/dist/compat/core.d.ts +18 -0
  43. package/dist/compat/core.js +29 -0
  44. package/dist/compat/files.d.ts +1 -0
  45. package/dist/compat/files.js +25 -5
  46. package/dist/credentials.d.ts +3 -0
  47. package/dist/credentials.js +15 -0
  48. package/dist/daytona/computer-use.d.ts +13 -4
  49. package/dist/daytona/computer-use.js +66 -8
  50. package/dist/daytona/daytona.d.ts +7 -1
  51. package/dist/daytona/daytona.js +31 -4
  52. package/dist/daytona/errors.d.ts +11 -0
  53. package/dist/daytona/errors.js +18 -0
  54. package/dist/daytona/filesystem.js +2 -1
  55. package/dist/daytona/image.d.ts +13 -9
  56. package/dist/daytona/image.js +18 -12
  57. package/dist/daytona/index.d.ts +1 -1
  58. package/dist/daytona/sandbox.d.ts +8 -2
  59. package/dist/daytona/sandbox.js +16 -2
  60. package/dist/domains/index.d.ts +9 -0
  61. package/dist/domains/index.js +10 -0
  62. package/dist/e2b/commands.d.ts +1 -1
  63. package/dist/e2b/commands.js +6 -7
  64. package/dist/e2b/errors.d.ts +17 -0
  65. package/dist/e2b/errors.js +40 -1
  66. package/dist/e2b/index.d.ts +2 -2
  67. package/dist/e2b/index.js +1 -1
  68. package/dist/e2b/sandbox.d.ts +28 -22
  69. package/dist/e2b/sandbox.js +34 -8
  70. package/dist/e2b/unsupported.js +1 -1
  71. package/dist/errors.d.ts +11 -2
  72. package/dist/errors.js +17 -5
  73. package/dist/freestyle/index.d.ts +285 -37
  74. package/dist/freestyle/index.js +656 -144
  75. package/dist/identity.d.ts +3 -0
  76. package/dist/identity.js +27 -0
  77. package/dist/images/index.d.ts +9 -0
  78. package/dist/images/index.js +10 -0
  79. package/dist/index.d.ts +3 -1
  80. package/dist/ips/index.d.ts +9 -0
  81. package/dist/ips/index.js +10 -0
  82. package/dist/jobs/index.d.ts +9 -0
  83. package/dist/jobs/index.js +10 -0
  84. package/dist/keys.js +2 -0
  85. package/dist/login.js +2 -0
  86. package/dist/machine.d.ts +27 -4
  87. package/dist/machine.js +46 -14
  88. package/dist/mcp.js +144 -20
  89. package/dist/modal/index.d.ts +339 -61
  90. package/dist/modal/index.js +781 -121
  91. package/dist/network-cli.d.ts +3 -1
  92. package/dist/network-cli.js +78 -36
  93. package/dist/networks/index.d.ts +11 -0
  94. package/dist/networks/index.js +10 -0
  95. package/dist/ports/index.d.ts +9 -0
  96. package/dist/ports/index.js +10 -0
  97. package/dist/product-names.d.ts +52 -0
  98. package/dist/product-names.js +164 -0
  99. package/dist/product-renames.d.ts +103 -0
  100. package/dist/product-renames.js +189 -0
  101. package/dist/products/api-keys.d.ts +73 -0
  102. package/dist/products/api-keys.js +23 -0
  103. package/dist/products/apps.d.ts +157 -0
  104. package/dist/products/apps.js +60 -0
  105. package/dist/products/balancers.d.ts +87 -0
  106. package/dist/products/balancers.js +8 -0
  107. package/dist/products/billing.d.ts +41 -0
  108. package/dist/products/billing.js +13 -0
  109. package/dist/products/browser.d.ts +10 -0
  110. package/dist/products/browsers.d.ts +71 -0
  111. package/dist/products/browsers.js +102 -0
  112. package/dist/products/builds.d.ts +156 -0
  113. package/dist/products/builds.js +227 -0
  114. package/dist/products/index.d.ts +5 -16
  115. package/dist/products/index.js +5 -14
  116. package/dist/products/jobs.d.ts +19 -2
  117. package/dist/products/jobs.js +28 -11
  118. package/dist/products/logs.d.ts +245 -0
  119. package/dist/products/logs.js +274 -0
  120. package/dist/products/network-products.d.ts +38 -7
  121. package/dist/products/network-products.js +31 -18
  122. package/dist/products/observability.d.ts +8 -2
  123. package/dist/products/private-network.js +1 -1
  124. package/dist/products/queues.d.ts +149 -0
  125. package/dist/products/queues.js +213 -0
  126. package/dist/products/referrals.d.ts +21 -7
  127. package/dist/products/referrals.js +3 -3
  128. package/dist/products/resources.d.ts +54 -0
  129. package/dist/products/resources.js +122 -0
  130. package/dist/products/secrets.d.ts +29 -3
  131. package/dist/products/secrets.js +47 -18
  132. package/dist/products/signed-requests.d.ts +21 -0
  133. package/dist/products/signed-requests.js +11 -0
  134. package/dist/products/streams.d.ts +73 -0
  135. package/dist/products/streams.js +17 -0
  136. package/dist/products/switching.d.ts +35 -7
  137. package/dist/products/switching.js +4 -3
  138. package/dist/products/volumes.d.ts +6 -0
  139. package/dist/record-cli.d.ts +23 -0
  140. package/dist/record-cli.js +308 -0
  141. package/dist/renamed-products.d.ts +38 -0
  142. package/dist/renamed-products.js +34 -0
  143. package/dist/runloop/index.d.ts +9 -2
  144. package/dist/runloop/index.js +29 -8
  145. package/dist/runloop/sdk.d.ts +131 -0
  146. package/dist/runloop/sdk.js +167 -0
  147. package/dist/sandbox.d.ts +7 -2
  148. package/dist/sandbox.js +41 -11
  149. package/dist/sandboxes/index.d.ts +11 -0
  150. package/dist/sandboxes/index.js +12 -0
  151. package/dist/snapshots/index.d.ts +9 -0
  152. package/dist/snapshots/index.js +11 -0
  153. package/dist/snapshots.d.ts +7 -4
  154. package/dist/sprites/index.d.ts +343 -31
  155. package/dist/sprites/index.js +866 -84
  156. package/dist/ssh.d.ts +1 -1
  157. package/dist/ssh.js +11 -11
  158. package/dist/terminal.d.ts +24 -0
  159. package/dist/terminal.js +32 -0
  160. package/dist/transport.d.ts +18 -1
  161. package/dist/transport.js +80 -6
  162. package/dist/tunnel.js +1 -1
  163. package/dist/types.d.ts +71 -4
  164. package/dist/vercel/sandbox.d.ts +1 -1
  165. package/dist/vercel/sandbox.js +6 -4
  166. package/dist/vercel/snapshot.js +10 -2
  167. package/dist/volumes/index.d.ts +9 -0
  168. package/dist/volumes/index.js +10 -0
  169. package/dist/wallet.d.ts +4 -1
  170. package/dist/wallet.js +2 -1
  171. package/package.json +48 -14
package/dist/ssh.d.ts CHANGED
@@ -16,6 +16,6 @@ export declare function sshKey(env: NodeJS.ProcessEnv): Promise<{
16
16
  export declare function resolveSandbox(runtime: Runtime, target: string): Promise<Sandbox>;
17
17
  export declare function sshConfig(env: NodeJS.ProcessEnv): Promise<string>;
18
18
  export declare function sshCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out, connect: () => Promise<Runtime>, me: string): Promise<number>;
19
- /** `runtime sandbox port-forward <id> 5432 8080:3000 ...` */
19
+ /** `runtime sandboxes port-forward <id> 5432 8080:3000 ...` */
20
20
  export declare function portForwardCommand(argv: string[], out: Out, connect: () => Promise<Runtime>, me: string): Promise<number>;
21
21
  export {};
package/dist/ssh.js CHANGED
@@ -6,12 +6,12 @@ import { isAbsolute, join } from "node:path";
6
6
  import { RuntimeError } from "./errors.js";
7
7
  import { VERSION } from "./transport.js";
8
8
  const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
9
- export const SSH_HELP = (me) => `${me} sandbox ssh <id|name> [-- <command...>] Log in over SSH (an interactive shell, or run the command)
10
- ${me} sandbox ssh config [--install] The ssh config for \`ssh <id|name>.runtime\`, also from
9
+ export const SSH_HELP = (me) => `${me} sandboxes ssh <id|name> [-- <command...>] Log in over SSH (an interactive shell, or run the command)
10
+ ${me} sandboxes ssh config [--install] The ssh config for \`ssh <id|name>.runtime\`, also from
11
11
  VS Code Remote-SSH and JetBrains Gateway; --install adds
12
12
  it to ~/.ssh/config
13
- ${me} sandbox ssh proxy <id|name>[.runtime] ProxyCommand mode: SSH on stdin and stdout
14
- ${me} sandbox port-forward <id|name> <port|local:remote>... [--address 127.0.0.1]
13
+ ${me} sandboxes ssh proxy <id|name>[.runtime] ProxyCommand mode: SSH on stdin and stdout
14
+ ${me} sandboxes port-forward <id|name> <port|local:remote>... [--address 127.0.0.1]
15
15
  Forward local ports to ports in the sandbox, until Ctrl-C
16
16
 
17
17
  SSH and port forwarding go through Runtime's API with your key: the sandbox opens
@@ -63,7 +63,7 @@ export async function resolveSandbox(runtime, target) {
63
63
  : `No live sandbox is named ${name}.`,
64
64
  code: page.data.length ? "ambiguous_name" : "sandbox_not_found",
65
65
  status: 0,
66
- hint: "Use its id: runtime sandbox ls",
66
+ hint: "Use its id: runtime sandboxes ls",
67
67
  });
68
68
  }
69
69
  const quote = (text) => /^[A-Za-z0-9_./:@=-]+$/.test(text) ? text : `'${text.replace(/'/g, "'\\''")}'`;
@@ -193,7 +193,7 @@ export async function sshCommand(argv, env, out, connect, me) {
193
193
  }
194
194
  if (first === "proxy") {
195
195
  if (!second)
196
- throw usageError(`Give the host: ${me} sandbox ssh proxy <id>.runtime`, me);
196
+ throw usageError(`Give the host: ${me} sandboxes ssh proxy <id>.runtime`, me);
197
197
  return proxy(await connect(), second, await publicKey());
198
198
  }
199
199
  const runtime = await connect();
@@ -223,7 +223,7 @@ export async function sshCommand(argv, env, out, connect, me) {
223
223
  message: `Could not run ssh: ${error.message}`,
224
224
  code: "ssh_missing",
225
225
  status: 0,
226
- hint: "Install OpenSSH, or use `runtime sandbox shell <id>`, which needs nothing.",
226
+ hint: "Install OpenSSH, or use `runtime sandboxes shell <id>`, which needs nothing.",
227
227
  })));
228
228
  child.on("exit", (code) => resolve(code ?? 255));
229
229
  });
@@ -233,10 +233,10 @@ function usageError(message, me) {
233
233
  message,
234
234
  code: "usage",
235
235
  status: 0,
236
- hint: `Run \`${me} sandbox ssh help\`.`,
236
+ hint: `Run \`${me} sandboxes ssh help\`.`,
237
237
  });
238
238
  }
239
- /** `runtime sandbox port-forward <id> 5432 8080:3000 ...` */
239
+ /** `runtime sandboxes port-forward <id> 5432 8080:3000 ...` */
240
240
  export async function portForwardCommand(argv, out, connect, me) {
241
241
  const at = argv.indexOf("--address");
242
242
  const address = at === -1 ? "127.0.0.1" : argv[at + 1];
@@ -244,10 +244,10 @@ export async function portForwardCommand(argv, out, connect, me) {
244
244
  const [target, ...specs] = positional;
245
245
  if (!target || !specs.length || !address)
246
246
  throw new RuntimeError({
247
- message: `Give a sandbox and at least one port: ${me} sandbox port-forward <id> 5432 8080:3000`,
247
+ message: `Give a sandbox and at least one port: ${me} sandboxes port-forward <id> 5432 8080:3000`,
248
248
  code: "usage",
249
249
  status: 0,
250
- hint: `Run \`${me} sandbox ssh help\`.`,
250
+ hint: `Run \`${me} sandboxes ssh help\`.`,
251
251
  });
252
252
  const ports = specs.map((spec) => {
253
253
  const match = /^(?:(\d{1,5}):)?(\d{1,5})$/.exec(spec);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Text that came from someone else's program, made safe to print on a
3
+ * terminal: log lines, labels, anything a customer's code wrote. Printed raw,
4
+ * an escape sequence in it could clear or rewrite the screen, set the
5
+ * clipboard (OSC 52), forge a link (OSC 8) or reorder what is read (Unicode
6
+ * direction overrides). Each such character is shown as its \uXXXX escape
7
+ * instead, so nothing is hidden and nothing acts. Tab stays; a newline inside
8
+ * one value is escaped too, so a line on screen is always one value.
9
+ *
10
+ * Only for a terminal: a pipe or a file gets the bytes as they are.
11
+ */
12
+ export declare function terminalSafe(text: string): string;
13
+ /** As {@link terminalSafe}, for a stream of lines: its newlines stay. */
14
+ export declare function terminalSafeLines(text: string): string;
15
+ /**
16
+ * Writes a log stream (a sandbox process's, an image build's, a job run's) to
17
+ * `to`: escaped on a terminal, as written to a pipe or a file, or with `raw`.
18
+ * Not for `exec` or an attached process, whose output is the customer's own
19
+ * terminal session.
20
+ */
21
+ export declare function logStream(to: {
22
+ isTTY?: boolean;
23
+ write(text: string): unknown;
24
+ }, raw: boolean): (text: string) => void;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Text that came from someone else's program, made safe to print on a
3
+ * terminal: log lines, labels, anything a customer's code wrote. Printed raw,
4
+ * an escape sequence in it could clear or rewrite the screen, set the
5
+ * clipboard (OSC 52), forge a link (OSC 8) or reorder what is read (Unicode
6
+ * direction overrides). Each such character is shown as its \uXXXX escape
7
+ * instead, so nothing is hidden and nothing acts. Tab stays; a newline inside
8
+ * one value is escaped too, so a line on screen is always one value.
9
+ *
10
+ * Only for a terminal: a pipe or a file gets the bytes as they are.
11
+ */
12
+ export function terminalSafe(text) {
13
+ return text.replace(UNSAFE, escaped);
14
+ }
15
+ /** As {@link terminalSafe}, for a stream of lines: its newlines stay. */
16
+ export function terminalSafeLines(text) {
17
+ return text.replace(UNSAFE_BUT_NEWLINE, escaped);
18
+ }
19
+ /**
20
+ * Writes a log stream (a sandbox process's, an image build's, a job run's) to
21
+ * `to`: escaped on a terminal, as written to a pipe or a file, or with `raw`.
22
+ * Not for `exec` or an attached process, whose output is the customer's own
23
+ * terminal session.
24
+ */
25
+ export function logStream(to, raw) {
26
+ return (text) => void to.write(to.isTTY && !raw ? terminalSafeLines(text) : text);
27
+ }
28
+ const escaped = (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
29
+ // C0 but tab, DEL, C1, and the Unicode marks and overrides that reorder text.
30
+ const CONTROLS = String.raw `\x00-\x08\x0b-\x1f\x7f-\x9f؜‎‏‪-‮⁦-⁩`;
31
+ const UNSAFE = new RegExp(String.raw `[\n${CONTROLS}]`, "g");
32
+ const UNSAFE_BUT_NEWLINE = new RegExp(`[${CONTROLS}]`, "g");
@@ -1,5 +1,5 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.11.2";
2
+ export declare const VERSION = "0.14.0";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
4
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
5
5
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
@@ -7,6 +7,9 @@ export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
7
7
  * because the hop never leaves the machine: it goes from the program to the
8
8
  * guest's own proxy and over the sandbox's private channel to its host. */
9
9
  export declare const SANDBOX_BASE_URL = "http://runtime.internal";
10
+ /** How a Runtime secret's placeholder starts, the value a sandbox sees in its
11
+ * place (packages/cloud-guide/docs/security.md). */
12
+ export declare const PLACEHOLDER = "rtsec_";
10
13
  /** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
11
14
  * move it, so the machine running them does not decide what they see. */
12
15
  export declare const sandboxMarker: {
@@ -84,6 +87,13 @@ export declare const DEFAULT_MAX_CONNECTIONS = 48;
84
87
  * served, and wait rather than fail. Streams (a command's output, a download,
85
88
  * a watch) take at most `maxConnections` less KEPT_FOR_CALLS of them. A
86
89
  * terminal or tunnel's WebSocket is not counted. */
90
+ /** AbortSignal.timeout in whole milliseconds, rounded up, never under 1.
91
+ * Node throws ERR_OUT_OF_RANGE on a fraction (Bun accepts one), so a
92
+ * customer's timeoutMs of 1500.5 would be a RangeError instead of a request;
93
+ * the live promise probe broke this way on 9 October 2026 (d3cd4bfbd). Every
94
+ * deadline the SDK makes from a customer's or a computed value goes through
95
+ * this. */
96
+ export declare function timeoutSignal(ms: number): AbortSignal;
87
97
  export declare class Transport {
88
98
  #private;
89
99
  readonly baseUrl: string;
@@ -128,7 +138,14 @@ export declare class Transport {
128
138
  /** A WebSocket to an API path, authenticated with the same key. */
129
139
  websocket(path: string, query?: Query): Promise<WebSocket>;
130
140
  }
141
+ /** CLOUD.md section 4: a call under a renamed product word is answered with a
142
+ * Warning naming the new word. It is printed once per process, to stderr, so
143
+ * a loop does not drown its own output and stdout stays the caller's. */
144
+ export declare function sayRenamed(header: string | null, say?: (line: string) => void): void;
131
145
  /** A download that ended short of (or past) the length the API promised. */
132
146
  export declare function incompleteDownload(got: number, expected: number): RuntimeError;
133
147
  /** No key given, none in RUNTIME_API_KEY, and none saved by `runtime login`. */
134
148
  export declare function missingKey(): RuntimeError;
149
+ /** Whether a fetch failure is a connection that never opened: its code, or
150
+ * one of its causes', says so. A timeout or a reset never counts. */
151
+ export declare function neverSent(cause: unknown): boolean;
package/dist/transport.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ConnectionError, DELIBERATE, errorFor, NOTHING_RAN, RuntimeError, WAITS_FOR_ROOM, } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
3
- export const VERSION = "0.11.2";
3
+ import { warningText } from "./product-renames.js";
4
+ export const VERSION = "0.14.0";
4
5
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
5
6
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
6
7
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
@@ -8,6 +9,9 @@ export const DEFAULT_BASE_URL = "https://api.withruntime.com";
8
9
  * because the hop never leaves the machine: it goes from the program to the
9
10
  * guest's own proxy and over the sandbox's private channel to its host. */
10
11
  export const SANDBOX_BASE_URL = "http://runtime.internal";
12
+ /** How a Runtime secret's placeholder starts, the value a sandbox sees in its
13
+ * place (packages/cloud-guide/docs/security.md). */
14
+ export const PLACEHOLDER = "rtsec_";
11
15
  /** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
12
16
  * move it, so the machine running them does not decide what they see. */
13
17
  export const sandboxMarker = { path: "/run/runtime/environment.json" };
@@ -172,6 +176,15 @@ const KEPT_FOR_CALLS = 8;
172
176
  * served, and wait rather than fail. Streams (a command's output, a download,
173
177
  * a watch) take at most `maxConnections` less KEPT_FOR_CALLS of them. A
174
178
  * terminal or tunnel's WebSocket is not counted. */
179
+ /** AbortSignal.timeout in whole milliseconds, rounded up, never under 1.
180
+ * Node throws ERR_OUT_OF_RANGE on a fraction (Bun accepts one), so a
181
+ * customer's timeoutMs of 1500.5 would be a RangeError instead of a request;
182
+ * the live promise probe broke this way on 9 October 2026 (d3cd4bfbd). Every
183
+ * deadline the SDK makes from a customer's or a computed value goes through
184
+ * this. */
185
+ export function timeoutSignal(ms) {
186
+ return AbortSignal.timeout(Math.max(1, Math.ceil(ms)));
187
+ }
175
188
  export class Transport {
176
189
  baseUrl;
177
190
  #apiKey;
@@ -194,7 +207,13 @@ export class Transport {
194
207
  throw missingKey();
195
208
  else
196
209
  this.#apiKey = options.apiKey;
197
- this.baseUrl = apiOrigin(options.baseUrl ? reachable(options.baseUrl) : defaultBaseUrl());
210
+ // A secret's placeholder becomes the key only on its way out through the
211
+ // sandbox's proxy; runtime.internal goes to the host without it, so the
212
+ // API sees the placeholder and answers 401 (land-lander, 11 October 2026:
213
+ // every worker's `check --remote`). Such a key's calls take the public API.
214
+ const placeholder = this.#apiKey?.startsWith(PLACEHOLDER) === true;
215
+ const origin = options.baseUrl || processEnv().RUNTIME_API_URL || DEFAULT_BASE_URL;
216
+ this.baseUrl = apiOrigin(placeholder ? origin : reachable(origin));
198
217
  // The environment's proxy (HTTPS_PROXY, NO_PROXY) on Node and Bun alike;
199
218
  // a fetch passed in is used as it is.
200
219
  this.#fetch = options.fetch ?? envFetch;
@@ -245,7 +264,7 @@ export class Transport {
245
264
  #prepare(call) {
246
265
  const timeoutMs = call.timeoutMs ?? this.#timeoutMs;
247
266
  const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
248
- const deadline = timeoutMs === 0 ? undefined : AbortSignal.timeout(timeoutMs + roomMs);
267
+ const deadline = timeoutMs === 0 ? undefined : timeoutSignal(timeoutMs + roomMs);
249
268
  const signal = deadline
250
269
  ? call.signal
251
270
  ? AbortSignal.any([call.signal, deadline])
@@ -291,6 +310,18 @@ export class Transport {
291
310
  await sleep(ms, signal);
292
311
  await gate?.take();
293
312
  }
313
+ /** Wait before sending a refused call again. When the call's deadline
314
+ * ends the wait, the refusal stands: on a loaded machine bun can fire
315
+ * AbortSignal.timeout before a shorter setTimeout, and the call then
316
+ * failed as a timeout although the transport had judged the wait to fit
317
+ * (11 October 2026). A caller's own cancel still ends it as a cancel. */
318
+ async #retryAfter(refusal, ms, signal, gate) {
319
+ await this.#rest(ms, signal, gate).catch((cause) => {
320
+ throw signal?.reason?.name === "TimeoutError"
321
+ ? refusal
322
+ : cause;
323
+ });
324
+ }
294
325
  async #send(call, consume, gate) {
295
326
  let apiKey;
296
327
  try {
@@ -314,6 +345,8 @@ export class Transport {
314
345
  // An answer that ran nothing is sent again until the call's deadline.
315
346
  const idleUntil = call.deadlineAt ?? performance.now() + NOTHING_RAN_MS;
316
347
  let idleAttempt = 0;
348
+ // The last try never left, and was sent again at once.
349
+ let resentAtOnce = false;
317
350
  for (;;) {
318
351
  let response;
319
352
  let inCallback = false;
@@ -344,6 +377,7 @@ export class Transport {
344
377
  await response.body?.cancel().catch(() => { });
345
378
  throw new TypeError(`fetch failed: Runtime answered a redirect (${response.status}).`);
346
379
  }
380
+ sayRenamed(response.headers.get("warning"));
347
381
  /* An answer that took longer than the API holds its headers (90 s)
348
382
  comes as a 200 sent early, and then its JSON: the answer, or
349
383
  {"error": ...} when the work failed after the status went
@@ -400,7 +434,7 @@ export class Transport {
400
434
  inCallback = true;
401
435
  call.onCapacityWait?.(error, pause);
402
436
  inCallback = false;
403
- await this.#rest(pause, signal, gate);
437
+ await this.#retryAfter(error, pause, signal, gate);
404
438
  continue;
405
439
  }
406
440
  // Nothing ran: safe to send again for any call, past maxRetries,
@@ -416,7 +450,7 @@ export class Transport {
416
450
  const pause = Math.min(30_000, base * 2 ** Math.min(idleAttempt++, 3)) * (0.9 + Math.random() * 0.2);
417
451
  if (pause >= idleUntil - performance.now())
418
452
  throw error;
419
- await this.#rest(pause, signal, gate);
453
+ await this.#retryAfter(error, pause, signal, gate);
420
454
  continue;
421
455
  }
422
456
  const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
@@ -439,7 +473,14 @@ export class Transport {
439
473
  throw cause;
440
474
  if (signal?.aborted || !retry || attempt >= this.#maxRetries)
441
475
  throw this.#connectionError(call, cause);
442
- await this.#rest(backoff(attempt), signal, gate).catch((cause) => {
476
+ // A connection that never opened sent no byte of the request, so
477
+ // the next try, which may reach another server, goes at once; once
478
+ // in a row, so an API that is down is not hammered. Anything that
479
+ // may have reached a server (a reset, a timeout, a cut answer)
480
+ // keeps the backoff and its key's rules.
481
+ const atOnce = response === undefined && neverSent(cause) && !resentAtOnce;
482
+ resentAtOnce = atOnce;
483
+ await this.#rest(atOnce ? Math.random() * 50 : backoff(attempt), signal, gate).catch((cause) => {
443
484
  throw cause instanceof RuntimeError ? cause : this.#connectionError(call, cause);
444
485
  });
445
486
  }
@@ -628,6 +669,17 @@ export class Transport {
628
669
  }
629
670
  /** The status a late answer's body failed with, or undefined when it is an
630
671
  * answer. The body is the error the API would have sent as its status. */
672
+ const said = new Set();
673
+ /** CLOUD.md section 4: a call under a renamed product word is answered with a
674
+ * Warning naming the new word. It is printed once per process, to stderr, so
675
+ * a loop does not drown its own output and stdout stays the caller's. */
676
+ export function sayRenamed(header, say = console.warn) {
677
+ const text = warningText(header);
678
+ if (text === undefined || said.has(text))
679
+ return;
680
+ said.add(text);
681
+ say(`Runtime: ${text}`);
682
+ }
631
683
  function lateFailure(text) {
632
684
  let body;
633
685
  try {
@@ -688,6 +740,28 @@ export function missingKey() {
688
740
  hint: "Run `npx -y withruntime login` (a browser approval; nothing to copy), set RUNTIME_API_KEY to a key from https://withruntime.com/account/keys, or pass { apiKey }.",
689
741
  });
690
742
  }
743
+ /** Error codes of a connection that never opened (Node's and Bun's), so no
744
+ * byte of the request left this machine. */
745
+ const NEVER_OPENED = new Set([
746
+ "ECONNREFUSED",
747
+ "ENOENT",
748
+ "EHOSTUNREACH",
749
+ "ENETUNREACH",
750
+ "EADDRNOTAVAIL",
751
+ "ConnectionRefused",
752
+ "FailedToOpenSocket",
753
+ ]);
754
+ /** Whether a fetch failure is a connection that never opened: its code, or
755
+ * one of its causes', says so. A timeout or a reset never counts. */
756
+ export function neverSent(cause) {
757
+ for (let at = cause, depth = 0; at && depth < 5; depth++) {
758
+ const code = at.code;
759
+ if (typeof code === "string" && NEVER_OPENED.has(code))
760
+ return true;
761
+ at = at.cause;
762
+ }
763
+ return false;
764
+ }
691
765
  function backoff(attempt) {
692
766
  return Math.min(8000, 250 * 2 ** attempt) * (0.5 + Math.random());
693
767
  }
package/dist/tunnel.js CHANGED
@@ -222,7 +222,7 @@ export class Tunnel {
222
222
  message: `Nothing is listening on port ${port} in the sandbox.`,
223
223
  code: "port_closed",
224
224
  status: 0,
225
- hint: "Start the server with spawn (`runtime sandbox spawn <id> -- <command>`, sbx.spawn()), which keeps it running: a process started by exec ends with its command. Let it listen on 127.0.0.1, ::1 or every address.",
225
+ hint: "Start the server with spawn (`runtime sandboxes spawn <id> -- <command>`, sbx.spawn()), which keeps it running: a process started by exec ends with its command. Let it listen on 127.0.0.1, ::1 or every address.",
226
226
  })
227
227
  : error.message === "reserved"
228
228
  ? new RuntimeError({
package/dist/types.d.ts CHANGED
@@ -82,13 +82,17 @@ export type SandboxInfo = {
82
82
  };
83
83
  export type CreateSandbox = {
84
84
  name?: string;
85
+ /** What the end of an `await using` block does to it. By default the
86
+ * block deletes it; `keep: true` stops it instead, which keeps its disk
87
+ * (billed while it is kept). Not sent to the API. */
88
+ keep?: boolean;
85
89
  labels?: Record<string, string>;
86
90
  /** Environment variables for every command, process, terminal, SSH session
87
91
  * and the image's start command in this sandbox, under each command's own
88
92
  * `env`. At most 32 variables and 32 KiB. Values are never shown again;
89
93
  * answers list only `envNames`. Copies made by fork() keep them. */
90
94
  env?: Record<string, string>;
91
- /** Omit to use the included usage while it lasts, then prepaid credit. */
95
+ /** Omit to use the free usage while it lasts, then prepaid credit. */
92
96
  funding?: "trial" | "paid";
93
97
  region?: string;
94
98
  vcpu?: number;
@@ -153,6 +157,18 @@ export type CreateSandbox = {
153
157
  tags?: string[];
154
158
  };
155
159
  };
160
+ /** Present on a command the sandbox's out-of-memory killer ended (the
161
+ * program, or anything it started): a bare exit -9, or 137 from a shell,
162
+ * does not say why. */
163
+ export type OutOfMemory = {
164
+ code: "out_of_memory";
165
+ processesKilled: number;
166
+ /** The sandbox's memory size; null if it could not be read. */
167
+ memoryMiB: number | null;
168
+ message: string;
169
+ /** What to do: a larger memoryMiB, or a program that uses less. */
170
+ hint: string;
171
+ };
156
172
  export type CommandResult = {
157
173
  exitCode: number | null;
158
174
  stdout: string;
@@ -163,6 +179,7 @@ export type CommandResult = {
163
179
  durationMs?: number | null;
164
180
  processId?: string;
165
181
  replayed?: boolean;
182
+ outOfMemory?: OutOfMemory;
166
183
  };
167
184
  export type ExecOptions = {
168
185
  cwd?: string;
@@ -196,6 +213,7 @@ export type ProcessInfo = {
196
213
  timeoutMs: number | null;
197
214
  outputBytes: number;
198
215
  firstOffset: number;
216
+ outOfMemory?: OutOfMemory;
199
217
  };
200
218
  export type OutputEvent = {
201
219
  type: "start";
@@ -212,6 +230,7 @@ export type OutputEvent = {
212
230
  state: string;
213
231
  timedOut: boolean;
214
232
  durationMs?: number;
233
+ outOfMemory?: OutOfMemory;
215
234
  } | {
216
235
  type: "truncated";
217
236
  droppedBytes: number;
@@ -276,11 +295,11 @@ export type Usage = {
276
295
  /** @deprecated Read `availableMs`. Still sent for older clients. */
277
296
  reservedMs: number;
278
297
  availableMs: number;
279
- /** `monthly`: the included usage every month only. `hours`: free hours
280
- * given before 5 October 2026, kept, beside the included usage. Absent
298
+ /** `monthly`: the free usage every month only. `hours`: free hours
299
+ * given before 5 October 2026, kept, beside the free usage. Absent
281
300
  * from an API older than 5 October 2026. */
282
301
  offer?: "monthly" | "hours";
283
- /** When the included usage renews: the 1st of next month, 00:00 UTC. */
302
+ /** When the free usage renews: the 1st of next month, 00:00 UTC. */
284
303
  renewsAt?: string;
285
304
  } | null;
286
305
  /** What the account uses free this calendar month (UTC), one row per
@@ -299,7 +318,55 @@ export type Usage = {
299
318
  /** The month's first day, YYYY-MM-DD. */
300
319
  month: string;
301
320
  renewsAt: string;
321
+ /** When the free machine time ends for an account that has never paid
322
+ * (30 days after it opened); null once it has paid, when it renews
323
+ * every month. Absent from an API older than 14 October 2026. */
324
+ freeUntil?: string | null;
325
+ }>;
326
+ /** What Runtime for Startups includes (https://withruntime.com/startups).
327
+ * Startup hours are given once, used after this month's free usage
328
+ * and before credit, never renewed, and gone after 12 months. Storage is
329
+ * GB kept free each month for those 12 months. Empty unless the account
330
+ * was accepted to the program or given it; absent from an older API. */
331
+ startupIncluded?: Array<{
332
+ /** machine-cpu, machine-memory, saved-copies (snapshots, images and
333
+ * backups) or disks (volumes). */
334
+ pool: string;
335
+ /** vCPU-hour or GiB-hour for startup hours given `once`, GB for `month` storage. */
336
+ unit: string;
337
+ /** `once`: given once and used up by what runs. `month`: `quantity` GB
338
+ * added to each month's included storage until `expiresAt`, never used
339
+ * up; storage past it bills at the published rate. Absent from an API
340
+ * before startup storage (7 October 2026): `once`. */
341
+ period?: "once" | "month";
342
+ quantity: number;
343
+ /** Null for monthly storage. */
344
+ used: number | null;
345
+ /** Held for what is running now; given back unless used. Null for
346
+ * monthly storage. */
347
+ reserved: number | null;
348
+ /** Null for monthly storage. */
349
+ left: number | null;
350
+ /** Storage GB kept now; null for startup hours given `once`. */
351
+ keptGB?: number | null;
352
+ /** `startup`. */
353
+ source: string;
354
+ grantedAt: string;
355
+ /** 12 months after `grantedAt`: what is left then is gone. */
356
+ expiresAt?: string;
302
357
  }>;
358
+ /** What Runtime for Startups includes at once for its 12 months:
359
+ * image builds, and sandboxes (past it, a create answers quota_exceeded).
360
+ * Null without one or after them, when the account's own limits apply;
361
+ * absent from an API older than 7 October 2026. */
362
+ startupLimits?: {
363
+ buildsAtOnce: number;
364
+ sandboxesAtOnce: number;
365
+ } | null;
366
+ /** @deprecated Renamed `startupIncluded`; works for 30 days after the rename, with a warning. */
367
+ readonly pot?: Usage["startupIncluded"];
368
+ /** @deprecated Renamed `startupLimits`; works for 30 days after the rename, with a warning. */
369
+ readonly potLimits?: Usage["startupLimits"];
303
370
  /** Outbound traffic this calendar month, UTC: the first `allowanceBytes` an
304
371
  * account sends are free, and the rest is charged at the rate in the pricing
305
372
  * guide (https://withruntime.com/docs/pricing#network-products). */
@@ -147,7 +147,7 @@ export declare class Sandbox {
147
147
  /** Creates a sandbox and waits until it runs. Vercel's defaults: 2 vCPUs,
148
148
  * 2048 MiB per vCPU, persistent. With no `timeout` it has no time limit: it
149
149
  * runs while it works and pauses when idle. A `timeout` is kept as given. Funding is left to
150
- * Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
150
+ * Runtime: the free usage first, then prepaid credit, exactly as withruntime's own create. */
151
151
  static create(params?: CreateSandboxParams): Promise<Sandbox & AsyncDisposable>;
152
152
  /** A sandbox by name (or Runtime id), woken if it is paused unless
153
153
  * `resume: false`. */
@@ -2,7 +2,7 @@ import { extensionSeconds, LeaseKeeper, LEASE_MAX_SECONDS } from "../compat/leas
2
2
  import { RuntimeError } from "../errors.js";
3
3
  import { clientFor, signalOf, } from "./client.js";
4
4
  import { Command, CommandFinished, exitCodeOf } from "./command.js";
5
- import { APIError, guard, NotSupportedError, translate } from "./errors.js";
5
+ import { APIError, guard, NotSupportedError, StreamError, translate } from "./errors.js";
6
6
  import { FileSystem } from "./filesystem.js";
7
7
  import { rootOrThrow, SandboxUser, SandboxUserAlreadyExistsError, validateName, } from "./sandbox-user.js";
8
8
  import { Snapshot } from "./snapshot.js";
@@ -105,7 +105,7 @@ async function resolveImage(client, params) {
105
105
  const found = page.data[0];
106
106
  if (found)
107
107
  return { image: found.id };
108
- throw new NotSupportedError(`The image ${image}, which is not a Runtime image`, `Build it as a Runtime image with that name and this call starts from it: \`npx withruntime image build --dockerfile Dockerfile --name ${image}\`, or runtime.images.build({ name: "${image}", image: "<registry reference>" }).`);
108
+ throw new NotSupportedError(`The image ${image}, which is not a Runtime image`, `Build it as a Runtime image with that name and this call starts from it: \`npx withruntime images build --dockerfile Dockerfile --name ${image}\`, or runtime.images.build({ name: "${image}", image: "<registry reference>" }).`);
109
109
  }
110
110
  /** Runtime's state as Vercel's status: a paused sandbox is a stopped
111
111
  * persistent one. */
@@ -260,7 +260,7 @@ export class Sandbox {
260
260
  /** Creates a sandbox and waits until it runs. Vercel's defaults: 2 vCPUs,
261
261
  * 2048 MiB per vCPU, persistent. With no `timeout` it has no time limit: it
262
262
  * runs while it works and pauses when idle. A `timeout` is kept as given. Funding is left to
263
- * Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
263
+ * Runtime: the free usage first, then prepaid credit, exactly as withruntime's own create. */
264
264
  static async create(params = {}) {
265
265
  refuseCreate(params);
266
266
  const client = clientFor(params);
@@ -520,6 +520,8 @@ export class Sandbox {
520
520
  onStdout: record("stdout", params.stdout),
521
521
  onStderr: record("stderr", params.stderr),
522
522
  }), params);
523
+ if (result.stdoutTruncated || result.stderrTruncated)
524
+ throw new StreamError("output_truncated", "Some command output is no longer available.", result.processId ?? this.#rt.id);
523
525
  return new CommandFinished({
524
526
  id: result.processId ?? `cmd_${startedAt}`,
525
527
  cwd: vercelCwd,
@@ -775,7 +777,7 @@ export class Sandbox {
775
777
  * Runtime's terminal opens with the account key, which must not leave the
776
778
  * server. */
777
779
  openInteractive() {
778
- return Promise.reject(new NotSupportedError("Vercel's interactive shell (openInteractive)", "Use `await sandbox.withruntime.terminal({ cols, rows, onData })`, or `npx withruntime sandbox shell <id>`."));
780
+ return Promise.reject(new NotSupportedError("Vercel's interactive shell (openInteractive)", "Use `await sandbox.withruntime.terminal({ cols, rows, onData })`, or `npx withruntime sandboxes shell <id>`."));
779
781
  }
780
782
  /** The user commands run as, `runtime`, and its group. */
781
783
  getDefaultUser(_opts = {}) {
@@ -7,6 +7,14 @@ function statusOf(state) {
7
7
  return "deleted";
8
8
  return "created";
9
9
  }
10
+ /** Vercel's snapshots always expire. Runtime's has no end date while it is kept
11
+ * for as long as the account has credit; it is then taken at the longest a
12
+ * snapshot is kept, a year from when it was made. */
13
+ function expiresAtOf(snapshot) {
14
+ return snapshot.expiresAt === null
15
+ ? Date.parse(snapshot.createdAt) + 365 * 86_400_000
16
+ : Date.parse(snapshot.expiresAt);
17
+ }
10
18
  function metadataOf(snapshot) {
11
19
  return {
12
20
  id: snapshot.id,
@@ -17,7 +25,7 @@ function metadataOf(snapshot) {
17
25
  sizeBytes: snapshot.storedBytes ?? 0,
18
26
  createdAt: Date.parse(snapshot.createdAt),
19
27
  updatedAt: Date.parse(snapshot.readyAt ?? snapshot.createdAt),
20
- expiresAt: Date.parse(snapshot.expiresAt),
28
+ expiresAt: expiresAtOf(snapshot),
21
29
  };
22
30
  }
23
31
  /** A saved filesystem, as Vercel's Snapshot, over a Runtime disk snapshot.
@@ -52,7 +60,7 @@ export class Snapshot {
52
60
  return new Date(this.#snapshot.readyAt ?? this.#snapshot.createdAt);
53
61
  }
54
62
  get expiresAt() {
55
- return new Date(this.#snapshot.expiresAt);
63
+ return new Date(expiresAtOf(this.#snapshot));
56
64
  }
57
65
  static async get(params) {
58
66
  const client = clientFor(params);
@@ -0,0 +1,9 @@
1
+ /** `withruntime/volumes`: Volumes alone. Persistent disks to attach to sandboxes.
2
+ *
3
+ * import { volumes } from "withruntime/volumes";
4
+ * const client = volumes(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime, type RuntimeOptions } from "../client.js";
7
+ export type * from "../products/volumes.js";
8
+ /** `runtime.volumes` of a client made with `options`. */
9
+ export declare function volumes(options?: RuntimeOptions): Runtime["volumes"];
@@ -0,0 +1,10 @@
1
+ /** `withruntime/volumes`: Volumes alone. Persistent disks to attach to sandboxes.
2
+ *
3
+ * import { volumes } from "withruntime/volumes";
4
+ * const client = volumes(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime } from "../client.js";
7
+ /** `runtime.volumes` of a client made with `options`. */
8
+ export function volumes(options) {
9
+ return new Runtime(options).volumes;
10
+ }
package/dist/wallet.d.ts CHANGED
@@ -17,8 +17,11 @@ export type WalletClaim = {
17
17
  status: Exclude<Topup["status"], "paid">;
18
18
  orgId: string;
19
19
  topup: Topup | null;
20
+ /** What to do now, such as sending the top-up again when it has no address. */
21
+ next?: string;
20
22
  };
21
- /** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $10,000).
23
+ /** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $500,
24
+ * a new account's first week).
22
25
  * `acceptTerms` must be true: opening one means agreeing to
23
26
  * https://withruntime.com/legal/terms, so ask the person you work for first. */
24
27
  export declare function openWalletAccount(input: {
package/dist/wallet.js CHANGED
@@ -33,7 +33,8 @@ async function post(options, path, body) {
33
33
  });
34
34
  return [response.status, answer];
35
35
  }
36
- /** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $10,000).
36
+ /** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $500,
37
+ * a new account's first week).
37
38
  * `acceptTerms` must be true: opening one means agreeing to
38
39
  * https://withruntime.com/legal/terms, so ask the person you work for first. */
39
40
  export async function openWalletAccount(input, options = {}) {