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
@@ -8,30 +8,58 @@ export function unwrapListData(response) {
8
8
  return response.data ?? [];
9
9
  }
10
10
  /** Blaxel's own helper, for a list fetched by `fetchPage`. */
11
- export async function createPaginatedList({ response, fetchPage, mapItem, query, }) {
12
- const meta = !response || Array.isArray(response) ? {} : (response.meta ?? {});
11
+ export async function createPaginatedList({ response, fetchPage, mapItem, query, seenCursors, }) {
12
+ const meta = !response || Array.isArray(response)
13
+ ? { hasMore: false }
14
+ : (response.meta ?? { hasMore: false });
13
15
  const data = await Promise.all(unwrapListData(response).map(mapItem));
14
- return build(data, meta.nextCursor || undefined, async (cursor) => createPaginatedList({
15
- response: await fetchPage({ ...(query ?? {}), cursor }),
16
- fetchPage,
17
- mapItem,
18
- ...(query ? { query: { ...query, cursor } } : {}),
19
- }));
16
+ const cursors = new Set(seenCursors);
17
+ if (query?.cursor)
18
+ cursors.add(query.cursor);
19
+ return build(data, meta.nextCursor || undefined, async (cursor) => {
20
+ if (cursors.has(cursor))
21
+ throw new Error("Pagination returned a repeated cursor");
22
+ const nextQuery = { ...(query ?? {}), cursor };
23
+ const nextSeenCursors = new Set(cursors);
24
+ nextSeenCursors.add(cursor);
25
+ return createPaginatedList({
26
+ response: await fetchPage(nextQuery),
27
+ fetchPage,
28
+ mapItem,
29
+ query: nextQuery,
30
+ seenCursors: nextSeenCursors,
31
+ });
32
+ }, meta);
20
33
  }
21
34
  /** A Runtime page, each item mapped, as Blaxel's paginated list. */
22
- export function paginate(page, map) {
23
- return build(page.data.map(map), page.nextCursor ?? undefined, async () => {
24
- const next = await guard(() => page.next());
25
- return next ? paginate(next, map) : null;
35
+ export function paginate(page, map, seenCursors = new Set()) {
36
+ return build(page.data.map(map), page.nextCursor ?? undefined, async (cursor) => {
37
+ if (cursor && seenCursors.has(cursor))
38
+ throw new Error("Pagination returned a repeated cursor");
39
+ const nextSeenCursors = new Set(seenCursors);
40
+ if (cursor)
41
+ nextSeenCursors.add(cursor);
42
+ const next = await guard(() => page.next(cursor));
43
+ return next ? paginate(next, map, nextSeenCursors) : null;
26
44
  });
27
45
  }
28
- function build(data, nextCursor, more) {
46
+ function build(data, nextCursor, more, meta = {
47
+ hasMore: nextCursor !== undefined,
48
+ ...(nextCursor ? { nextCursor } : {}),
49
+ }) {
29
50
  const list = {
30
51
  data,
31
- meta: { hasMore: nextCursor !== undefined, ...(nextCursor ? { nextCursor } : {}) },
32
- hasMore: nextCursor !== undefined,
33
- ...(nextCursor ? { nextCursor } : {}),
34
- nextPage: async () => (nextCursor ? more(nextCursor) : null),
52
+ meta,
53
+ get hasMore() {
54
+ return Boolean(list.nextCursor);
55
+ },
56
+ get nextCursor() {
57
+ return meta.nextCursor || undefined;
58
+ },
59
+ nextPage: async () => {
60
+ const cursor = list.nextCursor;
61
+ return cursor ? more(cursor) : null;
62
+ },
35
63
  async autoPagingEach(onItem) {
36
64
  for await (const item of list)
37
65
  if ((await onItem(item)) === false)
@@ -67,7 +67,8 @@ export declare class SandboxPreviewTokens {
67
67
  constructor(preview: SandboxPreview, ctx: PreviewContext);
68
68
  get previewName(): string;
69
69
  get resourceName(): string;
70
- /** A token that lasts until `expiresAt` (at least a minute, at most a week). */
70
+ /** A token bounded by `expiresAt`; native signing floors milliseconds and
71
+ * caps it to the caller's session. Older servers refuse without a TTL fallback. */
71
72
  create(expiresAt: Date): Promise<SandboxPreviewToken>;
72
73
  list(): Promise<never>;
73
74
  /** Revokes the preview's tokens. Runtime cannot revoke one token alone, so
@@ -1,8 +1,5 @@
1
1
  import { guard, isNotFound, NotSupportedError, responseError } from "./errors.js";
2
2
  import { PREVIEW } from "./lookup.js";
3
- /** Runtime's shortest and longest token, in seconds. */
4
- const TOKEN_MIN = 60;
5
- const TOKEN_MAX = 7 * 86_400;
6
3
  function checkName(name) {
7
4
  if (!/^[A-Za-z0-9._-]{1,48}$/.test(name))
8
5
  throw new NotSupportedError(`The preview name "${name}"`, "Use 1 to 48 letters, digits, dots, dashes or underscores.");
@@ -56,19 +53,24 @@ export class SandboxPreviewTokens {
56
53
  get resourceName() {
57
54
  return this.#ctx.sandboxName;
58
55
  }
59
- /** A token that lasts until `expiresAt` (at least a minute, at most a week). */
56
+ /** A token bounded by `expiresAt`; native signing floors milliseconds and
57
+ * caps it to the caller's session. Older servers refuse without a TTL fallback. */
60
58
  async create(expiresAt) {
61
- const seconds = Math.ceil((expiresAt.getTime() - Date.now()) / 1000);
62
- if (seconds > TOKEN_MAX)
63
- throw new NotSupportedError("A preview token lasting more than a week", "Create one for at most seven days, and a new one when it runs out.");
64
- const port = this.#preview.spec.port;
65
- const got = await this.#ctx.run((runtime) => runtime.previews.get(port, Math.max(TOKEN_MIN, seconds)));
59
+ const requested = expiresAt.getTime();
60
+ if (!Number.isFinite(requested))
61
+ throw responseError(400, "A preview token needs a valid expiration date.");
62
+ const remaining = requested - Date.now();
63
+ if (remaining < 60_000 || remaining > 7 * 86_400_000)
64
+ throw new NotSupportedError("A preview token lasting less than a minute or more than a week", "Request an expiration at least one minute and at most seven days away.");
65
+ const got = await this.#ctx.run((runtime) => runtime.previews.get(this.#preview.spec.port, undefined, {
66
+ expiresAt: expiresAt.toISOString(),
67
+ }));
68
+ const actual = got.tokenExpiresAt === null ? Number.NaN : Date.parse(got.tokenExpiresAt);
69
+ if (!got.token || !Number.isFinite(actual) || actual > requested)
70
+ throw new NotSupportedError("A preview token with the requested expiration", "The server must issue a token that never outlives the requested expiration; no duration-based fallback was requested.");
66
71
  return new SandboxPreviewToken({
67
72
  metadata: { name: `token-${Date.now()}`, previewName: this.previewName },
68
- spec: {
69
- ...(got.token ? { token: got.token } : {}),
70
- ...(got.tokenExpiresAt ? { expiresAt: got.tokenExpiresAt } : {}),
71
- },
73
+ spec: { token: got.token, expiresAt: got.tokenExpiresAt },
72
74
  });
73
75
  }
74
76
  list() {
@@ -152,14 +152,17 @@ export class SandboxProcess {
152
152
  }
153
153
  /** A call that found the sandbox paused (autoWake off) is made again once,
154
154
  * after a wake: it never started, so it cannot run twice. */
155
- async #retryPaused(work) {
155
+ async #retryPaused(work, signal) {
156
+ signal?.throwIfAborted();
156
157
  try {
157
158
  return await work();
158
159
  }
159
160
  catch (error) {
161
+ signal?.throwIfAborted();
160
162
  if (codeOf(error) !== "sandbox_paused")
161
163
  throw translate(error);
162
164
  await this.#ctx.wake();
165
+ signal?.throwIfAborted();
163
166
  return guard(work);
164
167
  }
165
168
  }
@@ -281,16 +284,20 @@ export class SandboxProcess {
281
284
  };
282
285
  }
283
286
  /** A process by pid or name: the registry first, then Runtime's list. */
284
- async #find(identifier) {
287
+ async #find(identifier, signal) {
288
+ signal?.throwIfAborted();
285
289
  const runtime = await this.#ctx.live();
290
+ signal?.throwIfAborted();
286
291
  const registry = this.#registry;
287
292
  const known = registry.byName.get(identifier) ?? (registry.meta.has(identifier) ? identifier : undefined);
288
293
  if (known !== undefined) {
289
- const process = await this.#retryPaused(() => runtime.processes.get(known));
294
+ const process = await this.#retryPaused(() => runtime.processes.get(known, { signal }), signal);
295
+ signal?.throwIfAborted();
290
296
  registry.processes.set(known, process);
291
297
  return { id: known, info: process.info, process };
292
298
  }
293
- const all = await this.#retryPaused(() => runtime.processes.list());
299
+ const all = await this.#retryPaused(() => runtime.processes.list({ signal }), signal);
300
+ signal?.throwIfAborted();
294
301
  let found;
295
302
  for (const info of all) {
296
303
  const parsed = parseProcessLine(info.command);
@@ -323,7 +330,9 @@ export class SandboxProcess {
323
330
  const running = found.info.state === "running";
324
331
  if (running && !untilExit && found.info.outputBytes <= found.info.firstOffset)
325
332
  return out;
333
+ signal?.throwIfAborted();
326
334
  const runtime = await this.#ctx.live();
335
+ signal?.throwIfAborted();
327
336
  const abort = new AbortController();
328
337
  const forward = () => abort.abort();
329
338
  signal?.throwIfAborted();
@@ -369,9 +378,30 @@ export class SandboxProcess {
369
378
  /** The process, with the output it has written so far. */
370
379
  async get(identifier, options = {}) {
371
380
  options.signal?.throwIfAborted();
372
- const found = await this.#find(identifier);
373
- const out = await this.#output(found, false, options.signal);
374
- return this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
381
+ const signal = options.signal;
382
+ let onAbort;
383
+ const interrupted = new Promise((_, reject) => {
384
+ onAbort = () => reject(new Error("Process read was interrupted.", { cause: signal?.reason }));
385
+ signal?.addEventListener("abort", onAbort, { once: true });
386
+ });
387
+ try {
388
+ const reading = (async () => {
389
+ const found = await this.#find(identifier, signal);
390
+ const out = await this.#output(found, false, signal);
391
+ signal?.throwIfAborted();
392
+ return this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
393
+ })();
394
+ return await Promise.race([reading, interrupted]);
395
+ }
396
+ catch (error) {
397
+ if (signal?.aborted)
398
+ throw signal.reason ?? error;
399
+ throw error;
400
+ }
401
+ finally {
402
+ if (onAbort)
403
+ signal?.removeEventListener("abort", onAbort);
404
+ }
375
405
  }
376
406
  /** Waits for the process to end (at most `maxWait` ms, -1 for no limit) and
377
407
  * answers it with its output. The process is never stopped by the wait. */
@@ -390,10 +420,24 @@ export class SandboxProcess {
390
420
  signal?.throwIfAborted();
391
421
  signal?.addEventListener("abort", forward, { once: true });
392
422
  const timer = maxWait === -1 ? undefined : setTimeout(() => abort.abort(late()), maxWait);
423
+ let onAbort;
424
+ const interrupted = new Promise((_, reject) => {
425
+ onAbort = () => {
426
+ const reason = abort.signal.reason;
427
+ reject(reason instanceof Error
428
+ ? reason
429
+ : new Error("Process wait was interrupted.", { cause: reason }));
430
+ };
431
+ abort.signal.addEventListener("abort", onAbort, { once: true });
432
+ });
393
433
  try {
394
- const found = await this.#find(identifier);
395
- const out = await this.#output(found, true, abort.signal);
396
- return await this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
434
+ const result = (async () => {
435
+ const found = await this.#find(identifier, abort.signal);
436
+ const out = await this.#output(found, true, abort.signal);
437
+ abort.signal.throwIfAborted();
438
+ return await this.#settled(this.#response(found.info, this.#registry.meta.get(found.id), out));
439
+ })();
440
+ return await Promise.race([result, interrupted]);
397
441
  }
398
442
  catch (error) {
399
443
  if (abort.signal.aborted)
@@ -402,6 +446,8 @@ export class SandboxProcess {
402
446
  }
403
447
  finally {
404
448
  clearTimeout(timer);
449
+ if (onAbort)
450
+ abort.signal.removeEventListener("abort", onAbort);
405
451
  signal?.removeEventListener("abort", forward);
406
452
  }
407
453
  }
@@ -32,6 +32,8 @@ export type SandboxForkOptions = {
32
32
  /** A snapshot's id or, of this sandbox's snapshots, its name. */
33
33
  snapshotId?: string;
34
34
  envs?: Env[];
35
+ /** Blaxel's fork override; Runtime cannot enforce this override during a fork. */
36
+ lifecycle?: SandboxLifecycle;
35
37
  };
36
38
  export type SandboxArchiveOptions = {
37
39
  wait?: boolean;
@@ -750,6 +750,8 @@ export class SandboxInstance {
750
750
  * (files, memory and running processes), or started from a snapshot with
751
751
  * `snapshotId`. `envs` are added over this sandbox's. */
752
752
  async fork(targetName, options = {}) {
753
+ if (options.lifecycle !== undefined)
754
+ throw new NotSupportedError("Overriding lifecycle during a fork", "Omit lifecycle to use the existing Runtime fork behavior.");
753
755
  refuseApplication(options);
754
756
  const envs = envRecord(options.envs);
755
757
  checkEnvNames(Object.keys(envs));
@@ -22,13 +22,11 @@ export function snapshotModel(snapshot, sandboxName) {
22
22
  }
23
23
  /** Envs as a record; a nameless one is refused. */
24
24
  export function envRecord(envs) {
25
- const out = {};
26
- for (const env of envs ?? []) {
25
+ return Object.fromEntries((envs ?? []).map((env) => {
27
26
  if (!env.name)
28
27
  throw responseError(400, "every env needs a name");
29
- out[env.name] = env.value ?? "";
30
- }
31
- return out;
28
+ return [env.name, env.value ?? ""];
29
+ }));
32
30
  }
33
31
  /** Appends envs to a sandbox's env file: they win over the ones it has. */
34
32
  export async function appendEnvs(runtime, envs) {
package/dist/cli.d.ts CHANGED
@@ -7,6 +7,8 @@ type Out = {
7
7
  json: boolean;
8
8
  write: (text: string) => void;
9
9
  error: (text: string) => void;
10
+ /** Wait when an output pipe cannot accept another page yet. */
11
+ drain?: () => Promise<void>;
10
12
  };
11
13
  type Args = {
12
14
  positional: string[];