withruntime 0.9.0 → 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 (90) hide show
  1. package/BLAXEL.md +4 -4
  2. package/DAYTONA.md +13 -7
  3. package/E2B.md +12 -8
  4. package/README.md +15 -7
  5. package/README.src.md +15 -7
  6. package/VERCEL.md +13 -3
  7. package/compatibility-lock.json +84 -8
  8. package/dist/blaxel/errors.js +1 -1
  9. package/dist/blaxel/lookup.d.ts +3 -2
  10. package/dist/blaxel/lookup.js +3 -2
  11. package/dist/blaxel/pagination.d.ts +2 -2
  12. package/dist/blaxel/pagination.js +45 -17
  13. package/dist/blaxel/preview.d.ts +2 -1
  14. package/dist/blaxel/preview.js +15 -13
  15. package/dist/blaxel/process.js +56 -10
  16. package/dist/blaxel/sandbox.d.ts +2 -0
  17. package/dist/blaxel/sandbox.js +2 -0
  18. package/dist/blaxel/snapshot.js +3 -5
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +189 -54
  21. package/dist/client.d.ts +29 -3
  22. package/dist/client.js +40 -11
  23. package/dist/cloudflare/identity.d.ts +7 -0
  24. package/dist/cloudflare/identity.js +13 -0
  25. package/dist/cloudflare/index.d.ts +9 -0
  26. package/dist/cloudflare/index.js +47 -9
  27. package/dist/cloudflare/worker.d.ts +4 -0
  28. package/dist/cloudflare/worker.js +166 -16
  29. package/dist/codesandbox/guest-service.js +57 -20
  30. package/dist/codesandbox/index.js +42 -8
  31. package/dist/compat/core.d.ts +9 -8
  32. package/dist/compat/core.js +60 -21
  33. package/dist/compat/files.js +22 -4
  34. package/dist/credentials.d.ts +6 -1
  35. package/dist/credentials.js +107 -38
  36. package/dist/daytona/daytona.js +11 -4
  37. package/dist/daytona/filesystem.d.ts +17 -4
  38. package/dist/daytona/filesystem.js +121 -38
  39. package/dist/daytona/index.d.ts +1 -1
  40. package/dist/daytona/sandbox.d.ts +1 -1
  41. package/dist/daytona/sandbox.js +16 -7
  42. package/dist/e2b/client.js +1 -1
  43. package/dist/e2b/commands.d.ts +30 -8
  44. package/dist/e2b/commands.js +142 -54
  45. package/dist/e2b/index.d.ts +1 -0
  46. package/dist/e2b/index.js +1 -0
  47. package/dist/e2b/pty.d.ts +24 -0
  48. package/dist/e2b/pty.js +80 -0
  49. package/dist/e2b/sandbox.d.ts +2 -1
  50. package/dist/e2b/sandbox.js +49 -15
  51. package/dist/file-source.d.ts +16 -0
  52. package/dist/file-source.js +162 -0
  53. package/dist/freestyle/index.d.ts +9 -1
  54. package/dist/freestyle/index.js +38 -5
  55. package/dist/index.d.ts +2 -1
  56. package/dist/login.js +16 -10
  57. package/dist/modal/index.js +2 -1
  58. package/dist/network-cli.js +111 -43
  59. package/dist/openai-agents/index.js +9 -45
  60. package/dist/page.d.ts +1 -1
  61. package/dist/page.js +2 -2
  62. package/dist/products/index.d.ts +12 -0
  63. package/dist/products/index.js +10 -0
  64. package/dist/products/observability.d.ts +44 -0
  65. package/dist/products/observability.js +6 -0
  66. package/dist/products/previews.d.ts +8 -2
  67. package/dist/products/previews.js +11 -3
  68. package/dist/products/volumes.d.ts +42 -0
  69. package/dist/products/volumes.js +36 -0
  70. package/dist/products/watch.js +61 -8
  71. package/dist/proxy.js +3 -1
  72. package/dist/runloop/index.d.ts +13 -27
  73. package/dist/runloop/index.js +149 -52
  74. package/dist/sandbox.d.ts +37 -8
  75. package/dist/sandbox.js +432 -68
  76. package/dist/snapshots.d.ts +9 -4
  77. package/dist/sprites/index.js +69 -33
  78. package/dist/tar.d.ts +3 -1
  79. package/dist/tar.js +140 -14
  80. package/dist/transport.d.ts +4 -7
  81. package/dist/transport.js +205 -122
  82. package/dist/vercel/command.d.ts +1 -1
  83. package/dist/vercel/command.js +3 -2
  84. package/dist/vercel/errors.d.ts +1 -1
  85. package/dist/vercel/errors.js +7 -1
  86. package/dist/vercel/sandbox.d.ts +8 -8
  87. package/dist/vercel/sandbox.js +154 -59
  88. package/dist/vercel/snapshot.d.ts +3 -2
  89. package/dist/vercel/snapshot.js +9 -5
  90. package/package.json +2 -1
package/dist/transport.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ConnectionError, DELIBERATE, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
2
2
  import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
3
- export const VERSION = "0.9.0";
3
+ export const VERSION = "0.10.0";
4
4
  export const DEFAULT_BASE_URL = "https://api.withruntime.com";
5
5
  /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
6
6
  * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
@@ -80,18 +80,57 @@ const sleep = (ms, signal) => new Promise((resolve, reject) => {
80
80
  else
81
81
  signal?.addEventListener("abort", abort, { once: true });
82
82
  });
83
+ /** A credential lookup can finish independently, but must not keep its
84
+ * caller waiting after cancellation or hold that caller's queue slot. */
85
+ function untilAborted(pending, signal) {
86
+ if (!signal)
87
+ return pending;
88
+ return new Promise((resolve, reject) => {
89
+ const abort = () => reject(abortReason(signal));
90
+ if (signal.aborted)
91
+ abort();
92
+ else
93
+ signal.addEventListener("abort", abort, { once: true });
94
+ pending.then((value) => {
95
+ signal.removeEventListener("abort", abort);
96
+ resolve(value);
97
+ }, (cause) => {
98
+ signal.removeEventListener("abort", abort);
99
+ reject(cause instanceof Error ? cause : new Error("Credential lookup failed.", { cause }));
100
+ });
101
+ });
102
+ }
103
+ function abortReason(signal) {
104
+ return signal?.reason instanceof Error
105
+ ? signal.reason
106
+ : new Error("Aborted", { cause: signal?.reason });
107
+ }
83
108
  /** At most `maximum` holders at once; the rest wait their turn, first come
84
109
  * first served. */
85
110
  function slots(maximum) {
86
111
  let active = 0;
87
112
  const waiting = [];
88
113
  return {
89
- async take() {
114
+ async take(signal) {
115
+ signal?.throwIfAborted();
90
116
  if (active < maximum) {
91
117
  active++;
92
118
  return;
93
119
  }
94
- await new Promise((resolve) => waiting.push(resolve));
120
+ await new Promise((resolve, reject) => {
121
+ const ready = () => {
122
+ signal?.removeEventListener("abort", abort);
123
+ resolve();
124
+ };
125
+ const abort = () => {
126
+ const at = waiting.indexOf(ready);
127
+ if (at >= 0)
128
+ waiting.splice(at, 1);
129
+ reject(abortReason(signal));
130
+ };
131
+ waiting.push(ready);
132
+ signal?.addEventListener("abort", abort, { once: true });
133
+ });
95
134
  },
96
135
  give() {
97
136
  const next = waiting.shift();
@@ -170,22 +209,71 @@ export class Transport {
170
209
  * the same key every time, and waits out a full house for a call that sets
171
210
  * waitForCapacityMs. */
172
211
  async send(call) {
173
- const apiKey = await this.#key();
174
- const write = call.method !== "GET";
175
- const key = write ? (call.idempotencyKey ?? crypto.randomUUID()) : undefined;
176
- // Time spent waiting for room is added to the call's deadline, not taken
177
- // from it: once admitted, a create still has its whole timeoutMs.
212
+ return this.#send(this.#prepare(call), async (response) => response);
213
+ }
214
+ /** Start the deadline before waiting for a connection; keep one key and
215
+ * signal through every retry and the complete response body. */
216
+ #prepare(call) {
217
+ const timeoutMs = call.timeoutMs ?? this.#timeoutMs;
218
+ const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
219
+ const deadline = timeoutMs === 0 ? undefined : AbortSignal.timeout(timeoutMs + roomMs);
220
+ const signal = deadline
221
+ ? call.signal
222
+ ? AbortSignal.any([call.signal, deadline])
223
+ : deadline
224
+ : call.signal;
225
+ return {
226
+ ...call,
227
+ timeoutMs: 0,
228
+ ...(signal ? { signal } : {}),
229
+ ...(call.method !== "GET"
230
+ ? { idempotencyKey: call.idempotencyKey ?? crypto.randomUUID() }
231
+ : {}),
232
+ };
233
+ }
234
+ #connectionError(call, cause) {
235
+ const cancelled = call.signal?.aborted === true;
236
+ const route = this.#routed && !cancelled
237
+ ? describeRoute(`${this.baseUrl}${call.path}`, cause)
238
+ : { via: "" };
239
+ return new ConnectionError({
240
+ message: cancelled
241
+ ? "The call was cancelled or ran past its deadline."
242
+ : call.method !== "GET"
243
+ ? `No answer from Runtime at ${this.baseUrl}${route.via}. The change may have happened; retrying with the same idempotencyKey is safe.`
244
+ : `No answer from Runtime at ${this.baseUrl}${route.via}.`,
245
+ hint: cancelled
246
+ ? "Raise timeoutMs for a long call, or retry with the same idempotencyKey."
247
+ : (route.hint ?? "Check the network, and RUNTIME_API_URL if you set it."),
248
+ code: cancelled ? "timeout" : "connection_error",
249
+ status: 0,
250
+ ...(call.idempotencyKey ? { idempotencyKey: call.idempotencyKey } : {}),
251
+ cause,
252
+ });
253
+ }
254
+ async #send(call, consume) {
255
+ let apiKey;
256
+ try {
257
+ call.signal?.throwIfAborted();
258
+ apiKey = await untilAborted(this.#key(), call.signal);
259
+ }
260
+ catch (cause) {
261
+ if (call.signal?.aborted)
262
+ throw this.#connectionError(call, cause);
263
+ throw cause;
264
+ }
265
+ const key = call.method !== "GET" ? call.idempotencyKey : undefined;
178
266
  const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
179
267
  const roomUntil = performance.now() + roomMs;
180
268
  let roomAttempt = 0;
181
- const timeoutMs = call.timeoutMs ?? this.#timeoutMs;
182
- const deadline = timeoutMs === 0 ? new AbortController().signal : AbortSignal.timeout(timeoutMs + roomMs);
183
- const signal = call.signal ? AbortSignal.any([call.signal, deadline]) : deadline;
269
+ const signal = call.signal;
184
270
  const url = `${this.baseUrl}${call.path}${encodeQuery(call.query)}`;
185
271
  const body = call.bytes ?? (call.body === undefined ? undefined : JSON.stringify(call.body));
186
272
  const retry = call.retry ?? true;
187
- for (let attempt = 0;; attempt++) {
273
+ let attempt = 0;
274
+ for (;;) {
188
275
  let response;
276
+ let inCallback = false;
189
277
  // Called as a plain function: Cloudflare Workers' fetch throws "Illegal
190
278
  // invocation" when called as a method of another object (this.#fetch()).
191
279
  const send = this.#fetch;
@@ -193,6 +281,7 @@ export class Transport {
193
281
  // "manual", not "error": Workers accept only "follow" and "manual".
194
282
  // The API never redirects, so a redirect is refused here exactly as
195
283
  // "error" refused it: thrown, retried, then a connection_error.
284
+ signal?.throwIfAborted();
196
285
  response = await send(url, {
197
286
  method: call.method,
198
287
  headers: this.#headers(call, key, apiKey),
@@ -205,138 +294,132 @@ export class Transport {
205
294
  await response.body?.cancel().catch(() => { });
206
295
  throw new TypeError(`fetch failed: Runtime answered a redirect (${response.status}).`);
207
296
  }
208
- }
209
- catch (cause) {
210
- // A proxy variable the SDK cannot use: retrying cannot change it.
211
- if (cause instanceof RuntimeError)
212
- throw cause;
213
- if (signal.aborted || !retry || attempt >= this.#maxRetries) {
214
- const route = this.#routed && !signal.aborted ? describeRoute(url, cause) : { via: "" };
297
+ /* An answer that took longer than the API holds its headers (90 s)
298
+ comes as a 200 sent early, and then its JSON: the answer, or
299
+ {"error": ...} when the work failed after the status went
300
+ (runtime-late-answer, api.md). Either way it is read here, so a
301
+ failure is thrown as the error it is, never returned as a success. */
302
+ let late;
303
+ if (response.ok && response.headers.get("runtime-late-answer") === "true") {
304
+ const text = await response.text();
305
+ const failed = lateFailure(text);
306
+ if (failed === undefined)
307
+ return await consume(new Response(text, { headers: response.headers }));
308
+ late = { status: failed, text };
309
+ }
310
+ else if (response.ok) {
311
+ inCallback = true;
312
+ call.onResponse?.(response);
313
+ inCallback = false;
314
+ return await consume(response);
315
+ }
316
+ const status = late?.status ?? response.status;
317
+ // Runtime never answers 407: it is a proxy refusing the tunnel, which
318
+ // Bun's fetch hands back as a response rather than an error.
319
+ if (response.status === 407 && this.#routed) {
320
+ await response.body?.cancel();
321
+ const route = describeRoute(url, 407);
215
322
  throw new ConnectionError({
216
- message: signal.aborted
217
- ? "The call was cancelled or ran past its deadline."
218
- : write
219
- ? `No answer from Runtime at ${this.baseUrl}${route.via}. The change may have happened; retrying with the same idempotencyKey is safe.`
220
- : `No answer from Runtime at ${this.baseUrl}${route.via}.`,
221
- hint: signal.aborted
222
- ? "Raise timeoutMs for a long call, or retry with the same idempotencyKey."
223
- : (route.hint ?? "Check the network, and RUNTIME_API_URL if you set it."),
224
- code: signal.aborted ? "timeout" : "connection_error",
323
+ message: `No answer from Runtime at ${this.baseUrl}${route.via}.`,
324
+ code: "connection_error",
225
325
  status: 0,
326
+ ...(route.hint ? { hint: route.hint } : {}),
226
327
  ...(key ? { idempotencyKey: key } : {}),
227
- cause,
228
328
  });
229
329
  }
230
- await sleep(backoff(attempt), signal);
231
- continue;
232
- }
233
- /* An answer that took longer than the API holds its headers (90 s)
234
- comes as a 200 sent early, and then its JSON: the answer, or
235
- {"error": ...} when the work failed after the status went
236
- (runtime-late-answer, api.md). Either way it is read here, so a
237
- failure is thrown as the error it is, never returned as a success. */
238
- let late;
239
- if (response.ok && response.headers.get("runtime-late-answer") === "true") {
240
- const text = await response.text();
241
- const failed = lateFailure(text);
242
- if (failed === undefined)
243
- return new Response(text, { headers: response.headers });
244
- late = { status: failed, text };
245
- }
246
- else if (response.ok) {
247
- call.onResponse?.(response);
248
- return response;
330
+ const text = late?.text ?? (await response.text());
331
+ let parsed = null;
332
+ try {
333
+ parsed = JSON.parse(text);
334
+ }
335
+ catch {
336
+ parsed = { error: { message: text.slice(0, 500) || `HTTP ${status}` } };
337
+ }
338
+ const error = errorFor(status, parsed, key);
339
+ // A full house: wait for a slot or for room, then send the same call
340
+ // again. A request that can never fit (a fork of more copies than the
341
+ // trial runs) is not waited for.
342
+ if (retry &&
343
+ roomMs > 0 &&
344
+ WAITS_FOR_ROOM.has(error.code) &&
345
+ error.details?.field !== "count") {
346
+ const left = roomUntil - performance.now();
347
+ if (left <= 0)
348
+ throw error;
349
+ const pause = Math.min(left, error.retryAfterMs ?? roomBackoff(roomAttempt++));
350
+ inCallback = true;
351
+ call.onCapacityWait?.(error, pause);
352
+ inCallback = false;
353
+ await sleep(pause, signal);
354
+ continue;
355
+ }
356
+ const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
357
+ if (!retry || !retryable || attempt >= this.#maxRetries)
358
+ throw error;
359
+ const header = Number(response.headers.get("retry-after"));
360
+ const wait = error.retryAfterMs ??
361
+ (Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
362
+ await sleep(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal);
249
363
  }
250
- const status = late?.status ?? response.status;
251
- // Runtime never answers 407: it is a proxy refusing the tunnel, which
252
- // Bun's fetch hands back as a response rather than an error.
253
- if (response.status === 407 && this.#routed) {
254
- await response.body?.cancel();
255
- const route = describeRoute(url, 407);
256
- throw new ConnectionError({
257
- message: `No answer from Runtime at ${this.baseUrl}${route.via}.`,
258
- code: "connection_error",
259
- status: 0,
260
- ...(route.hint ? { hint: route.hint } : {}),
261
- ...(key ? { idempotencyKey: key } : {}),
364
+ catch (cause) {
365
+ // Deliberate API refusals and unusable proxy settings cannot be
366
+ // fixed by repeating the request. Body failures are transport
367
+ // failures too: a write replays under its original key.
368
+ if (inCallback) {
369
+ await response?.body?.cancel().catch(() => undefined);
370
+ throw cause;
371
+ }
372
+ if (cause instanceof RuntimeError)
373
+ throw cause;
374
+ if (signal?.aborted || !retry || attempt >= this.#maxRetries)
375
+ throw this.#connectionError(call, cause);
376
+ await sleep(backoff(attempt), signal).catch((cause) => {
377
+ throw this.#connectionError(call, cause);
262
378
  });
263
379
  }
264
- const text = late?.text ?? (await response.text());
265
- let parsed = null;
266
- try {
267
- parsed = JSON.parse(text);
268
- }
269
- catch {
270
- parsed = { error: { message: text.slice(0, 500) || `HTTP ${status}` } };
271
- }
272
- const error = errorFor(status, parsed, key);
273
- // A full house: wait for a slot or for room, then send the same call
274
- // again. A request that can never fit (a fork of more copies than the
275
- // trial runs) is not waited for.
276
- if (retry &&
277
- roomMs > 0 &&
278
- WAITS_FOR_ROOM.has(error.code) &&
279
- error.details?.field !== "count") {
280
- const left = roomUntil - performance.now();
281
- if (left <= 0)
282
- throw error;
283
- const pause = Math.min(left, error.retryAfterMs ?? roomBackoff(roomAttempt++));
284
- call.onCapacityWait?.(error, pause);
285
- await sleep(pause, signal);
286
- continue;
287
- }
288
- const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
289
- if (!retry || !retryable || attempt >= this.#maxRetries)
290
- throw error;
291
- const header = Number(response.headers.get("retry-after"));
292
- const wait = error.retryAfterMs ??
293
- (Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
294
- await sleep(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal);
380
+ attempt++;
295
381
  }
296
382
  }
297
- async json(call) {
298
- await this.#slots.take();
383
+ async #queued(call, consume) {
299
384
  try {
300
- const text = await (await this.send(call)).text();
301
- return (text ? JSON.parse(text) : null);
385
+ await this.#slots.take(call.signal);
302
386
  }
303
- finally {
304
- this.#slots.give();
387
+ catch (cause) {
388
+ throw this.#connectionError(call, cause);
305
389
  }
306
- }
307
- async bytes(call) {
308
- await this.#slots.take();
309
390
  try {
310
- return new Uint8Array(await (await this.send(call)).arrayBuffer());
391
+ return await this.#send(call, consume);
311
392
  }
312
393
  finally {
313
394
  this.#slots.give();
314
395
  }
315
396
  }
316
- /** A file's bytes, read whole and checked: the API says a body's length
317
- * (x-content-length) and, when small, its SHA-256 (x-content-sha256) before
318
- * sending it, and a body that falls short or differs is read again, twice,
319
- * then refused. Nothing between the API and here reliably turns a stream
320
- * cut part way into an error: on 25 September 2026 a 50 MB file came back
321
- * 29 MB long, exit 0. */
397
+ async json(call) {
398
+ return this.#queued(this.#prepare(call), async (response) => {
399
+ const text = await response.text();
400
+ return (text ? JSON.parse(text) : null);
401
+ });
402
+ }
403
+ async bytes(call) {
404
+ return this.#queued(this.#prepare(call), async (response) => new Uint8Array(await response.arrayBuffer()));
405
+ }
406
+ /** A file's bytes, read whole and checked against its promised length and
407
+ * SHA-256. A short or different body is read again twice, within the same
408
+ * call deadline. */
322
409
  async fileBytes(call) {
410
+ call = this.#prepare(call);
323
411
  for (let attempt = 0;; attempt++) {
324
- await this.#slots.take();
325
- let failure;
326
- try {
327
- const response = await this.send(call);
412
+ const { body, problem } = await this.#queued(call, async (response) => {
328
413
  const body = new Uint8Array(await response.arrayBuffer());
329
- const problem = await checkBody(response.headers, body);
330
- if (!problem)
331
- return body;
332
- failure = problem;
333
- }
334
- finally {
335
- this.#slots.give();
336
- }
337
- if (attempt >= 2 || call.signal?.aborted)
338
- throw failure;
339
- await sleep(backoff(attempt), call.signal);
414
+ return { body, problem: await checkBody(response.headers, body) };
415
+ });
416
+ if (!problem)
417
+ return body;
418
+ if (attempt >= 2)
419
+ throw problem;
420
+ await sleep(backoff(attempt), call.signal).catch((cause) => {
421
+ throw this.#connectionError(call, cause);
422
+ });
340
423
  }
341
424
  }
342
425
  /** A file's bytes as they arrive, for files too big to hold. The stream
@@ -57,7 +57,7 @@ export declare class Command {
57
57
  signal?: AbortSignal;
58
58
  }): Promise<string>;
59
59
  /** Sends a signal, SIGTERM by default. */
60
- kill(signal?: Signal, _opts?: {
60
+ kill(signal?: Signal, opts?: {
61
61
  abortSignal?: AbortSignal;
62
62
  }): Promise<void>;
63
63
  }
@@ -136,13 +136,14 @@ export class Command {
136
136
  return this.output("stderr", opts);
137
137
  }
138
138
  /** Sends a signal, SIGTERM by default. */
139
- async kill(signal = "SIGTERM", _opts = {}) {
139
+ async kill(signal = "SIGTERM", opts = {}) {
140
+ opts.abortSignal?.throwIfAborted();
140
141
  if (!this.init.process)
141
142
  return;
142
143
  const name = typeof signal === "number" ? SIGNAL_NUMBERS[signal] : signal;
143
144
  if (!name)
144
145
  throw new RangeError(`Signal ${signal} has no name Runtime knows; use SIGTERM, SIGKILL, SIGINT, SIGHUP, SIGQUIT, SIGUSR1 or SIGUSR2.`);
145
- await guard(() => this.init.process.kill(name), this.init.sandboxName);
146
+ await guard(() => this.init.process.kill(name, opts.abortSignal ? { signal: opts.abortSignal } : {}), this.init.sandboxName);
146
147
  }
147
148
  }
148
149
  /** A command that has ended; `exitCode` is set. */
@@ -45,7 +45,7 @@ export declare function translate(error: unknown, sandboxName?: string): unknown
45
45
  export declare function guard<T>(work: () => Promise<T>, sandboxName?: string): Promise<T>;
46
46
  /** A node:fs-style error, as Vercel's `sandbox.fs` throws. */
47
47
  export declare function fsError(code: "ENOENT" | "EEXIST" | "EISDIR" | "ENOTDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL", syscall: string, path: string): Error & {
48
- code: "ENOENT" | "EEXIST" | "ENOTDIR" | "EISDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL";
48
+ code: "ENOENT" | "ENOTDIR" | "EEXIST" | "EISDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL";
49
49
  errno: number;
50
50
  syscall: string;
51
51
  path: string;
@@ -73,7 +73,13 @@ export function translate(error, sandboxName) {
73
73
  return new NotSupportedError(error.code.replace(/_unavailable$/, ""), error.hint ?? "", message);
74
74
  if (error.status < 200 || error.status > 599)
75
75
  return error;
76
- const json = { error: { code: error.code, message: error.message } };
76
+ const json = {
77
+ error: {
78
+ code: error.code,
79
+ message: error.message,
80
+ ...(error.details ? { details: error.details } : {}),
81
+ },
82
+ };
77
83
  const out = new APIError(new Response(JSON.stringify(json), {
78
84
  status: error.status,
79
85
  headers: { "content-type": "application/json" },
@@ -60,7 +60,7 @@ export interface CreateSandboxParams extends Credentials {
60
60
  /** Milliseconds a stopped persistent sandbox is kept, as Runtime's
61
61
  * retention (whole days, 1 to 365; 0 is 365). */
62
62
  snapshotExpiration?: number;
63
- /** Accepted: a paused Runtime sandbox keeps exactly its latest state. */
63
+ /** Refused: Runtime does not enforce snapshot count or eviction policies. */
64
64
  keepLastSnapshots?: {
65
65
  count: number;
66
66
  expiration?: number;
@@ -198,10 +198,10 @@ export declare class Sandbox {
198
198
  }): Promise<Command>;
199
199
  runCommand(params: RunCommandParams): Promise<CommandFinished>;
200
200
  /** A command started earlier, by its cmdId. */
201
- getCommand(cmdId: string, _opts?: {
201
+ getCommand(cmdId: string, opts?: {
202
202
  signal?: AbortSignal;
203
203
  }): Promise<Command>;
204
- mkDir(path: string, _opts?: {
204
+ mkDir(path: string, opts?: {
205
205
  signal?: AbortSignal;
206
206
  }): Promise<void>;
207
207
  /** The file's bytes as a Node stream, or null when there is no file. */
@@ -235,7 +235,7 @@ export declare class Sandbox {
235
235
  path: string;
236
236
  content: string | Uint8Array;
237
237
  mode?: number;
238
- }[], _opts?: {
238
+ }[], opts?: {
239
239
  signal?: AbortSignal;
240
240
  }): Promise<void>;
241
241
  /** The public address of a port listed in `ports`, such as
@@ -262,7 +262,7 @@ export declare class Sandbox {
262
262
  signal?: AbortSignal;
263
263
  }): Promise<void>;
264
264
  /** Replaces the network rules; returns the policy given. */
265
- updateNetworkPolicy(networkPolicy: NetworkPolicy, _opts?: {
265
+ updateNetworkPolicy(networkPolicy: NetworkPolicy, opts?: {
266
266
  signal?: AbortSignal;
267
267
  }): Promise<NetworkPolicy>;
268
268
  /** Changes what Runtime can change on a sandbox: `timeout`, `ports`,
@@ -273,11 +273,11 @@ export declare class Sandbox {
273
273
  ports?: number[];
274
274
  networkPolicy?: NetworkPolicy;
275
275
  snapshotExpiration?: number;
276
- } & Record<string, unknown>, _opts?: {
276
+ } & Record<string, unknown>, opts?: {
277
277
  signal?: AbortSignal;
278
278
  }): Promise<void>;
279
- /** Keeps the sandbox's whole machine as a Runtime snapshot (files, memory
280
- * and running processes), then stops the sandbox, as Vercel does. Start
279
+ /** Keeps the sandbox's filesystem as a disk-only snapshot, then stops
280
+ * the source. Restoring it starts fresh processes, as Vercel does. Start
281
281
  * from it with `Sandbox.create({ source: { type: "snapshot", snapshotId } })`. */
282
282
  snapshot(opts?: {
283
283
  expiration?: number;