withruntime 0.11.0 → 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 (84) hide show
  1. package/BLAXEL.md +8 -9
  2. package/DAYTONA.md +8 -9
  3. package/E2B.md +63 -21
  4. package/README.md +6 -5
  5. package/README.src.md +6 -5
  6. package/VERCEL.md +3 -2
  7. package/compatibility-lock.json +69 -14
  8. package/dist/api-defaults.d.ts +1 -1
  9. package/dist/api-defaults.js +1 -1
  10. package/dist/blaxel/errors.js +4 -4
  11. package/dist/blaxel/lookup.d.ts +2 -1
  12. package/dist/blaxel/lookup.js +2 -1
  13. package/dist/blaxel/sandbox.d.ts +8 -7
  14. package/dist/blaxel/sandbox.js +16 -12
  15. package/dist/cli.d.ts +1 -1
  16. package/dist/cli.js +99 -27
  17. package/dist/client.d.ts +16 -60
  18. package/dist/client.js +14 -103
  19. package/dist/compat/files.js +5 -1
  20. package/dist/compat/lease.d.ts +1 -0
  21. package/dist/compat/lease.js +14 -8
  22. package/dist/credentials.d.ts +9 -0
  23. package/dist/credentials.js +26 -4
  24. package/dist/daytona/daytona.d.ts +1 -2
  25. package/dist/daytona/daytona.js +30 -14
  26. package/dist/daytona/sandbox.d.ts +9 -4
  27. package/dist/daytona/sandbox.js +24 -5
  28. package/dist/e2b/client.d.ts +15 -2
  29. package/dist/e2b/client.js +61 -7
  30. package/dist/e2b/commands.d.ts +3 -0
  31. package/dist/e2b/commands.js +16 -5
  32. package/dist/e2b/errors.d.ts +26 -2
  33. package/dist/e2b/errors.js +38 -5
  34. package/dist/e2b/filesystem.d.ts +1 -1
  35. package/dist/e2b/filesystem.js +82 -16
  36. package/dist/e2b/index.d.ts +2 -2
  37. package/dist/e2b/index.js +2 -2
  38. package/dist/e2b/sandbox.d.ts +25 -12
  39. package/dist/e2b/sandbox.js +196 -165
  40. package/dist/e2b/unsupported.d.ts +19 -0
  41. package/dist/e2b/unsupported.js +21 -0
  42. package/dist/e2b/users.d.ts +9 -0
  43. package/dist/e2b/users.js +22 -0
  44. package/dist/errors.d.ts +10 -4
  45. package/dist/errors.js +18 -10
  46. package/dist/identity.js +2 -1
  47. package/dist/index.d.ts +2 -1
  48. package/dist/index.js +2 -1
  49. package/dist/machine.d.ts +180 -0
  50. package/dist/machine.js +504 -0
  51. package/dist/modal/index.js +6 -2
  52. package/dist/network-cli.js +7 -7
  53. package/dist/openai-agents/index.d.ts +1 -1
  54. package/dist/openai-agents/index.js +1 -1
  55. package/dist/products/browser.d.ts +2 -1
  56. package/dist/products/browser.js +9 -19
  57. package/dist/products/desktop.d.ts +2 -1
  58. package/dist/products/desktop.js +5 -30
  59. package/dist/products/jobs.d.ts +6 -5
  60. package/dist/products/jobs.js +6 -3
  61. package/dist/products/limits.d.ts +4 -10
  62. package/dist/products/mcp.d.ts +5 -1
  63. package/dist/products/mcp.js +29 -8
  64. package/dist/products/network-products.d.ts +1 -1
  65. package/dist/products/network-products.js +1 -1
  66. package/dist/products/network.d.ts +1 -1
  67. package/dist/products/observability.d.ts +1 -1
  68. package/dist/products/private-network.d.ts +1 -1
  69. package/dist/products/secrets.d.ts +3 -3
  70. package/dist/products/switching.d.ts +2 -3
  71. package/dist/runloop/index.js +6 -2
  72. package/dist/sandbox.d.ts +7 -84
  73. package/dist/sandbox.js +58 -345
  74. package/dist/tar.d.ts +2 -0
  75. package/dist/tar.js +74 -16
  76. package/dist/transport.d.ts +36 -8
  77. package/dist/transport.js +174 -37
  78. package/dist/types.d.ts +102 -12
  79. package/dist/vercel/filesystem.js +3 -1
  80. package/dist/vercel/sandbox.d.ts +18 -12
  81. package/dist/vercel/sandbox.js +27 -15
  82. package/dist/wait.d.ts +12 -0
  83. package/dist/wait.js +47 -0
  84. package/package.json +1 -1
@@ -71,10 +71,12 @@ export function durationMs(value) {
71
71
  total += Number(count) * unit[letter];
72
72
  return total;
73
73
  }
74
- /** The lease and the paused retention that honour Blaxel's TTL, expiry and
75
- * lifecycle: never deleted before Blaxel would delete it. A fixed deadline
76
- * within the hour is a lease that ends the sandbox; any other deadline keeps
77
- * a paused sandbox that many days, rounded up; none keeps it 365 days. */
74
+ /** The time limit and the paused retention that honour Blaxel's TTL, expiry
75
+ * and lifecycle: never deleted before Blaxel would delete it. A fixed
76
+ * deadline within the hour is a time limit that ends the sandbox; past that
77
+ * there is none, so it runs while it works and pauses when idle (0300), and
78
+ * a paused sandbox is kept that many days, rounded up; none keeps it 365
79
+ * days. */
78
80
  export function lifetimeOf(asked) {
79
81
  const now = Date.now();
80
82
  const fixed = [];
@@ -110,7 +112,7 @@ export function lifetimeOf(asked) {
110
112
  : KEEP_DAYS;
111
113
  return soonest <= LEASE_SECONDS * 1000
112
114
  ? { timeoutSeconds: Math.max(60, Math.ceil(soonest / 1000)), onLeaseEnd: "stop", days }
113
- : { timeoutSeconds: LEASE_SECONDS, onLeaseEnd: "pause", days };
115
+ : { onLeaseEnd: "pause", days };
114
116
  }
115
117
  /** Blaxel's network settings as Runtime's rules: allowed and forbidden
116
118
  * domains, from the network itself or from its proxy (Blaxel's current
@@ -402,9 +404,10 @@ export class SandboxInstance {
402
404
  }
403
405
  // ---- the live sandbox ---------------------------------------------------
404
406
  /** The sandbox, ready for a call. A paused one wakes by itself on the call
405
- * (autoWake); one made without autoWake is woken here. A lease nearing its
406
- * end is renewed in the background, so a sandbox in use is not paused by
407
- * its lease. */
407
+ * (autoWake); one made without autoWake is woken here. A time limit
408
+ * nearing its end (an hour, on a sandbox made before 0300) is renewed in
409
+ * the background, so a sandbox in use is not paused by it; one with no
410
+ * time limit renews itself. */
408
411
  async #live() {
409
412
  if (this.#deleted)
410
413
  throw responseError(404, `Sandbox ${this.name} was deleted.`);
@@ -414,6 +417,8 @@ export class SandboxInstance {
414
417
  await this.#wake();
415
418
  else if (state === "running" &&
416
419
  rt.info.onLeaseEnd === "pause" &&
420
+ // One with no time limit renews itself (0300).
421
+ rt.info.endsAt !== null &&
417
422
  !this.#renewing &&
418
423
  Date.parse(rt.info.expiresAt) - Date.now() < RENEW_BELOW_MS)
419
424
  this.#renewing = rt
@@ -461,7 +466,7 @@ export class SandboxInstance {
461
466
  labels: { ...labels, [IDLE_PAUSE]: labels[IDLE_PAUSE] ?? String(current ?? 0) },
462
467
  }))
463
468
  : undefined,
464
- rt.info.onLeaseEnd === "pause" && need >= 1
469
+ rt.info.onLeaseEnd === "pause" && rt.info.endsAt !== null && need >= 1
465
470
  ? rt.extend(Math.ceil(need)).catch(() => undefined)
466
471
  : undefined,
467
472
  ]);
@@ -518,8 +523,7 @@ export class SandboxInstance {
518
523
  // ---- create, get, list ---------------------------------------------------
519
524
  /** Creates a sandbox and answers once it runs. Blaxel's defaults: the base
520
525
  * image (Runtime's stock image) and 4096 MB, with vCPUs at one per 2048 MB.
521
- * Funding is left to Runtime: the free trial while the account has trial
522
- * time, then prepaid credit. A name another live sandbox holds is refused
526
+ * Funding is left to Runtime: the included usage first, then prepaid credit. A name another live sandbox holds is refused
523
527
  * with a 409, as in Blaxel. */
524
528
  static async create(sandbox, { createIfNotExist = false } = {}) {
525
529
  const config = configOf(sandbox);
@@ -545,7 +549,7 @@ export class SandboxInstance {
545
549
  vcpu: Math.min(MAX_VCPU, Math.max(1, Math.round(memory / MEMORY_PER_VCPU))),
546
550
  memoryMiB: memory,
547
551
  ...STANDBY,
548
- timeoutSeconds: lifetime.timeoutSeconds,
552
+ ...(lifetime.timeoutSeconds === undefined ? {} : { timeoutSeconds: lifetime.timeoutSeconds }),
549
553
  onLeaseEnd: lifetime.onLeaseEnd,
550
554
  ...(config.name ? { name: config.name } : {}),
551
555
  labels,
package/dist/cli.d.ts CHANGED
@@ -30,7 +30,7 @@ declare const kit: {
30
30
  };
31
31
  export type CliKit = typeof kit;
32
32
  export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
33
- /** `runtime usage` for a person: the balance, the trial and what each kind
33
+ /** `runtime usage` for a person: the balance, the included usage and what each kind
34
34
  * of resource was charged, in dollars. --json keeps every figure, and each
35
35
  * resource with its rates and CPU time. */
36
36
  /** One row per resource, for a spreadsheet: when it ran, how big it was, how
package/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ Account
36
36
  The account's audit log (owner or admin keys)
37
37
  sso Single sign-on and SCIM directory sync (owner or
38
38
  admin keys); an owner changes it on the website
39
- usage [--csv] Balance, trial time and charges; --csv, each resource
39
+ usage [--csv] Balance, included usage and charges; --csv, each resource
40
40
  usage export --since <UTC time> --until <UTC time>
41
41
  Every settlement in the range as CSV; --json, each page
42
42
  limits Whether this key is read-only, and its daily spending limit
@@ -140,10 +140,10 @@ const SANDBOX_HELP = `runtime sandbox <command>
140
140
  resize <id> [--vcpu 2] [--memory 4096] --restart
141
141
  Change its size by a restart: the whole disk stays,
142
142
  programs stop (snapshot it first to keep memory)
143
- extend <id> <seconds> More time before the lease ends
143
+ extend <id> <seconds> Move a time limit on (one with none needs nothing)
144
144
  update <id> [--name n] [--label k=v]... [--env K=V]... [--unset-env K]... [--idle-pause <seconds>] [--auto-wake on|off]
145
145
  [--persistent on|off] [--max-total-cost <usd>|none]
146
- Change its settings; persistent keeps it running
146
+ Change its settings; persistent keeps it running until stopped
147
147
  snapshot <id> [--name n] [--retention days] Keep its whole machine; prints the snapshot id
148
148
  mount <id> <s3|r2|gcs>://<bucket>[/prefix] <path> [--secret NAME] [--region r]
149
149
  [--endpoint https://...] [--account-id id] [--read-only]
@@ -517,8 +517,18 @@ function describe(info) {
517
517
  ],
518
518
  ["funding", info.funding],
519
519
  ["region", info.region],
520
- ["expires", info.expiresAt ?? "-"],
521
- ["at lease end", info.onLeaseEnd ?? "-"],
520
+ // A sandbox with no time limit runs while it works (0300): endsAt is
521
+ // null. An older server sends no endsAt; its expiresAt was the end.
522
+ ["time limit", info.timeoutSeconds ? `${info.timeoutSeconds} s` : "none"],
523
+ [
524
+ "ends",
525
+ info.endsAt === null
526
+ ? info.state === "running"
527
+ ? "when idle, stopped or out of credit"
528
+ : "-"
529
+ : (info.endsAt ?? info.expiresAt ?? "-"),
530
+ ],
531
+ ["at its end", info.onLeaseEnd ?? "-"],
522
532
  // Why a sandbox is no longer running (troubleshooting.md, "A sandbox
523
533
  // stopped on its own"); `get` left both out until 30 September 2026.
524
534
  ...(info.pausedAt ? [["paused at", info.pausedAt]] : []),
@@ -685,16 +695,18 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
685
695
  const [who, spend] = await Promise.all([rt.me(), rt.usage().catch(() => undefined)]);
686
696
  const trialLeft = spend?.trial ? spend.trial.availableMs / 3_600_000 : 0;
687
697
  const credit = spend ? BigInt(spend.available) : 0n;
698
+ // A pilot's sandboxes run on the pilot, before anything else and free.
688
699
  const funding = !spend
689
700
  ? undefined
690
701
  : [
702
+ ...(spend.pilot ? [pilotFunding(spend.pilot)] : []),
691
703
  ...(trialLeft > 0
692
704
  ? [
693
- `free trial, ${trialLeft.toLocaleString("en-US", { maximumFractionDigits: 1 })} hours left`,
705
+ `included usage, ${trialLeft.toLocaleString("en-US", { maximumFractionDigits: 1 })} hours left without credit`,
694
706
  ]
695
707
  : []),
696
708
  ...(credit > 0n ? [`${dollars(credit)} of credit`] : []),
697
- ].join("; ") || "no trial time or credit left: runtime billing";
709
+ ].join("; ") || "no included usage or credit left: runtime billing";
698
710
  print(table([
699
711
  ["organization", who.orgName ? `${who.orgName} (${who.orgId})` : who.orgId],
700
712
  ...(who.role ? [["role", who.role]] : []),
@@ -755,7 +767,10 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
755
767
  return 0;
756
768
  }
757
769
  if (command === "limits") {
758
- const l = await (await client(env)).limits.get();
770
+ const rt = await client(env);
771
+ // The usage call only adds the pilot's line: a key that cannot read it
772
+ // still gets its limits.
773
+ const [l, spend] = await Promise.all([rt.limits.get(), rt.usage().catch(() => undefined)]);
759
774
  // As `usage` shows money: $0.0014 of spend read $0.00 until 30 September 2026.
760
775
  const usd = (micros) => dollars(micros);
761
776
  const access = l.access === "read"
@@ -773,7 +788,13 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
773
788
  ],
774
789
  ["used, last 24 hours", usd(l.daily.usedMicros)],
775
790
  ...(l.daily.remainingMicros === null ? [] : [["left", usd(l.daily.remainingMicros)]]),
776
- ...(l.trial ? [["free trial", trialLeft(l.trial)]] : []),
791
+ ...(l.trial ? [["free time", trialLeft(l.trial)]] : []),
792
+ ...(spend?.pilot
793
+ ? [
794
+ ["pilot", pilotFunding(spend.pilot)],
795
+ ["pilot sandbox hours", pilotLeft(spend.pilot)],
796
+ ]
797
+ : []),
777
798
  ]), l);
778
799
  return 0;
779
800
  }
@@ -1019,7 +1040,7 @@ export async function run(argv, env = process.env, out = defaultOut(argv)) {
1019
1040
  }
1020
1041
  throw unknown("command", command, COMMANDS);
1021
1042
  }
1022
- /** `runtime usage` for a person: the balance, the trial and what each kind
1043
+ /** `runtime usage` for a person: the balance, the included usage and what each kind
1023
1044
  * of resource was charged, in dollars. --json keeps every figure, and each
1024
1045
  * resource with its rates and CPU time. */
1025
1046
  /** One row per resource, for a spreadsheet: when it ran, how big it was, how
@@ -1071,10 +1092,45 @@ export function usageCsv(u) {
1071
1092
  .join(","));
1072
1093
  return [head.join(","), ...rows].join("\n");
1073
1094
  }
1074
- /** The free trial's time left, as `runtime usage` and `runtime limits` say it. */
1095
+ /** Milliseconds as hours, to a tenth: "8,731.2". */
1096
+ const hours = (ms) => (ms / 3_600_000).toLocaleString("en-US", { maximumFractionDigits: 1 });
1097
+ /** The machine time left without credit, as `runtime usage` and `runtime
1098
+ * limits` say it: this month's included usage, as hours at 2 vCPU and 4 GiB,
1099
+ * or for an account given free hours before 5 October 2026, those hours too. */
1075
1100
  function trialLeft(trial) {
1076
- const hours = (ms) => (ms / 3_600_000).toLocaleString("en-US", { maximumFractionDigits: 1 });
1077
- return `${hours(trial.availableMs)} of ${hours(trial.totalMs)} hours left${trial.reservedMs > 0 ? `, ${hours(trial.reservedMs)} held by running sandboxes` : ""}`;
1101
+ const renews = trial.renewsAt ? `, renews ${trial.renewsAt.slice(0, 10)}` : "";
1102
+ return trial.totalMs > 0
1103
+ ? `${hours(trial.availableMs)} hours left (${hours(trial.totalMs)} free hours given before 5 October 2026, and the included usage${renews})`
1104
+ : `${hours(trial.availableMs)} hours at 2 vCPU and 4 GiB left this month${renews}`;
1105
+ }
1106
+ /** This month's included usage, one row per product, as `runtime usage`
1107
+ * says it. */
1108
+ function allowanceRows(allowances) {
1109
+ const amount = (n) => n.toLocaleString("en-US", { maximumFractionDigits: 1 });
1110
+ return allowances.map((a) => [
1111
+ `included ${a.pool.replace(/-/g, " ")}`,
1112
+ `${amount(a.left)} of ${amount(a.quantity)} ${a.unit}s left`,
1113
+ ]);
1114
+ }
1115
+ /** What a pilot gives, as `runtime whoami` and `runtime limits` say it: the
1116
+ * sandboxes it runs free and until when, past which credit does not run
1117
+ * them. */
1118
+ function pilotFunding(pilot) {
1119
+ const at = (iso) => `${iso.slice(0, 16).replace("T", " ")} UTC`;
1120
+ return `a pilot: up to ${pilot.sandboxes.toLocaleString("en-US")} sandboxes free, running until ${at(pilot.graceEndsAt)}; credit does not run it; to extend it, write to support`;
1121
+ }
1122
+ /** A pilot's sandbox hours left, as `runtime usage` says them. */
1123
+ function pilotLeft(pilot) {
1124
+ const left = `${hours(pilot.leftMs)} of ${pilot.hours.toLocaleString("en-US")} left`;
1125
+ // How long they last at the rate its sandboxes run now (an API before
1126
+ // 0414 does not say how many run).
1127
+ const running = pilot.running ?? 0;
1128
+ const pace = running > 0
1129
+ ? `; at ${running.toLocaleString("en-US")} running, about ${hours(pilot.leftMs / running)} hours`
1130
+ : "";
1131
+ return pilot.leftMs > 0
1132
+ ? `${left}${pace}; new pilot sandboxes until ${pilot.endsAt.slice(0, 16).replace("T", " ")} UTC`
1133
+ : "This pilot's sandbox hours are used.";
1078
1134
  }
1079
1135
  function usageSummary(u) {
1080
1136
  const micros = (value) => typeof value === "string" || typeof value === "number" || typeof value === "bigint"
@@ -1088,15 +1144,19 @@ function usageSummary(u) {
1088
1144
  // its own line, so "spent" reads as what was used.
1089
1145
  ["used", dollars(micros(u.spent) - takenBack)],
1090
1146
  ...(takenBack > 0n ? [["returned by refunds and disputes", dollars(takenBack)]] : []),
1091
- // Holds cover running sandboxes and the hour ahead of stored snapshots,
1092
- // images and volumes (pricing guide), so an account with nothing running
1093
- // can still show one.
1094
- ["held for running sandboxes and this hour's storage", dollars(micros(u.held))],
1147
+ // What running sandboxes and the hour ahead of stored snapshots, images
1148
+ // and volumes are about to use, so an account with nothing running can
1149
+ // still show some. Said without "held": no guide teaches holds.
1150
+ ["set aside for running sandboxes and this hour's storage", dollars(micros(u.held))],
1095
1151
  ];
1096
1152
  if (micros(u.expired) > 0n)
1097
1153
  rows.push(["expired or taken back", dollars(micros(u.expired))]);
1098
1154
  if (u.trial)
1099
- rows.push(["free trial", trialLeft(u.trial)]);
1155
+ rows.push(["free time", trialLeft(u.trial)]);
1156
+ if (u.allowances)
1157
+ rows.push(...allowanceRows(u.allowances));
1158
+ if (u.pilot)
1159
+ rows.push(["pilot sandbox hours", pilotLeft(u.pilot)]);
1100
1160
  if (u.outbound) {
1101
1161
  const gib = (bytes) => `${(bytes / 1_073_741_824).toLocaleString("en-US", { maximumFractionDigits: 1 })} GiB`;
1102
1162
  const left = Math.max(0, u.outbound.allowanceBytes - u.outbound.freeBytes);
@@ -1106,7 +1166,15 @@ function usageSummary(u) {
1106
1166
  ]);
1107
1167
  }
1108
1168
  const kinds = new Map();
1109
- for (const resource of u.resources ?? []) {
1169
+ // Every resource, by kind, from an API that totals them (6 October 2026);
1170
+ // an older one sends only its hundred newest, counted here.
1171
+ for (const k of u.kinds ?? [])
1172
+ kinds.set(k.kind, {
1173
+ count: k.resources,
1174
+ charged: micros(k.chargedMicros),
1175
+ held: micros(k.heldMicros),
1176
+ });
1177
+ for (const resource of u.kinds ? [] : (u.resources ?? [])) {
1110
1178
  const kind = typeof resource.kind === "string" ? resource.kind : "other";
1111
1179
  const entry = kinds.get(kind) ?? { count: 0, charged: 0n, held: 0n };
1112
1180
  entry.count++;
@@ -1285,7 +1353,7 @@ function runningTime(seconds) {
1285
1353
  });
1286
1354
  return `${shown} ${unit}${shown === "1" ? "" : "s"}`;
1287
1355
  }
1288
- /** The sandboxes the free trial paid for, said beside what was priced. */
1356
+ /** The sandboxes free usage paid for, said beside what was priced. */
1289
1357
  function trialNote(trial, all, trialSeconds) {
1290
1358
  if (!trial)
1291
1359
  return "";
@@ -1294,7 +1362,7 @@ function trialNote(trial, all, trialSeconds) {
1294
1362
  ? "It"
1295
1363
  : "All of them"
1296
1364
  : `${trial} of them, ${runningTime(trialSeconds)} of that time,`;
1297
- return ` ${which} ran on the free trial, which charged nothing; Runtime's side prices ${trial === 1 ? "it" : "them"} at the standard rates, what the same work costs on paid credit.`;
1365
+ return ` ${which} ran on free usage, which charged nothing; Runtime's side prices ${trial === 1 ? "it" : "them"} at the standard rates, what the same work costs on paid credit.`;
1298
1366
  }
1299
1367
  async function switching(command, argv, env, out) {
1300
1368
  const args = parse(argv, [], command === "compare" ? ["from", "days"] : ["from"]);
@@ -1474,11 +1542,13 @@ async function sandbox(argv, env, out) {
1474
1542
  s.state,
1475
1543
  `${s.info.vcpu}/${s.info.memoryMiB}`,
1476
1544
  s.info.funding,
1477
- s.info.expiresAt ?? "-",
1545
+ // When it stops or pauses by itself (0381): its time limit's end, or
1546
+ // "-" when it never will. An older server sends no endsAt.
1547
+ (s.info.endsAt === undefined ? s.info.expiresAt : s.info.endsAt) ?? "-",
1478
1548
  ]);
1479
1549
  print([
1480
1550
  rows.length
1481
- ? table([["ID", "NAME", "STATE", "CPU/MIB", "FUNDING", "EXPIRES"], ...rows])
1551
+ ? table([["ID", "NAME", "STATE", "CPU/MIB", "FUNDING", "ENDS"], ...rows])
1482
1552
  : stopped
1483
1553
  ? "No live sandboxes."
1484
1554
  : "No sandboxes. Create one: runtime sandbox create",
@@ -1704,7 +1774,9 @@ async function sandbox(argv, env, out) {
1704
1774
  const sbx = await get(rest[0]);
1705
1775
  const seconds = Number(need(rest[1], "seconds, e.g. runtime sandbox extend <id> 600"));
1706
1776
  await sbx.extend(seconds);
1707
- print(`${sbx.id} now ends at ${sbx.info.expiresAt}`, sbx.info);
1777
+ print(sbx.info.endsAt === null
1778
+ ? `${sbx.id} has no time limit: it runs while it works, so nothing was extended`
1779
+ : `${sbx.id} now ends at ${sbx.info.endsAt ?? sbx.info.expiresAt}`, sbx.info);
1708
1780
  return 0;
1709
1781
  }
1710
1782
  case "exec": {
@@ -2783,8 +2855,8 @@ read = readSecret) {
2783
2855
  }
2784
2856
  const JOB_HELP = `runtime job <command>
2785
2857
 
2786
- A job runs a command in a fresh sandbox, once or on a schedule. Each run is a
2787
- paid sandbox billed at the sandbox rates; the trial's hours do not fund jobs.
2858
+ A job runs a command in a fresh sandbox, once or on a schedule. Each run spends
2859
+ the included usage first, then credit at the sandbox rates.
2788
2860
 
2789
2861
  create <name> (--cron "<min hour day month weekday>" [--timezone Europe/Berlin] | --at <time|now>)
2790
2862
  [--vcpu 2] [--memory 4096] [--disk 4096] [--cpu shared|reserved] [--cpu-floor <thousandths>]
@@ -3230,7 +3302,7 @@ const CREATE_FLAGS = [
3230
3302
  "get-or-create",
3231
3303
  ];
3232
3304
  /** Says on standard error, once for each reason, why a create has not
3233
- * answered yet: it is waiting for a trial slot or room, which can take up to
3305
+ * answered yet: it is waiting for a slot without credit or room, which can take up to
3234
3306
  * two minutes, and silence reads as a hang. */
3235
3307
  function waiting(out) {
3236
3308
  const said = new Set();
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;
@@ -16,11 +16,18 @@ export type RuntimeOptions = {
16
16
  timeoutMs?: number;
17
17
  /** Retries of transport failures, 429, 502, 503 and 504. Default 4. */
18
18
  maxRetries?: number;
19
- /** Calls in flight at once; more wait their turn and reuse connections. Default 32. */
19
+ /** Connections held at once, calls and streams (a command's output, a
20
+ * download) together; more wait their turn, first come first served, and
21
+ * reuse them. Default 48: one address may hold 64 connections to the API
22
+ * before it has used a valid key. 8 are kept for calls whatever the
23
+ * streams hold. */
20
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;
21
28
  /** How long `sandboxes.create` keeps retrying, with the same key and input,
22
- * when the trial's slots, the account's quota or the region is full
23
- * (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
24
31
  * (two minutes); 0 fails at once. The refusal is thrown as it came when the
25
32
  * wait runs out. */
26
33
  waitForCapacityMs?: number;
@@ -53,8 +60,8 @@ export declare class Runtime {
53
60
  /** The role of the member who made this key: owner, admin, developer or billing. */
54
61
  role?: string | null;
55
62
  }>;
56
- /** Balance, holds, trial time and per-resource charges, in integer microdollars. */
57
- /** 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. */
58
65
  usage(options?: RequestOptions): Promise<Usage>;
59
66
  /** This account's authenticated API answers, in hourly windows. Counts are
60
67
  * exact decimal strings, and an empty window has a null error percentage.
@@ -76,60 +83,9 @@ export declare class Runtime {
76
83
  }>;
77
84
  }>;
78
85
  }
79
- /** How `sandboxes.create` waits when every trial slot, the account's quota or
80
- * the region is full. */
81
- export type CreateOptions = RequestOptions & {
82
- /** How long to keep retrying a refusal that clears by itself. Default: the
83
- * client's, two minutes; 0 fails at once. */
84
- waitForCapacityMs?: number;
85
- /** Called before each wait, with the refusal and the milliseconds until the
86
- * next try: to tell a person why nothing has happened yet. */
87
- onCapacityWait?: (refusal: RuntimeError, waitMs: number) => void;
88
- };
89
- export declare class Sandboxes {
90
- private readonly t;
86
+ /** `runtime.sandboxes`: the sandbox product of the machine engine. */
87
+ export declare class Sandboxes extends Machines<Sandbox, SandboxInfo, CreateSandbox, DeletedSandbox> {
91
88
  constructor(t: Transport);
92
- /** Creates a sandbox and waits until it is running. Every field is optional:
93
- * with none you get the free trial while it lasts, the default region and a
94
- * 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
95
- * When every trial slot or the account's quota is taken, it waits for one to
96
- * free, up to `waitForCapacityMs` (the client's, two minutes by default). */
97
- create(input?: CreateSandbox & {
98
- wait?: boolean;
99
- }, options?: CreateOptions): Promise<Sandbox>;
100
- /** The sandbox named `name` in this account, ready to use: running as it
101
- * is, woken if it is paused, restarted if it is stopped and persistent, or
102
- * created with `input` when no sandbox has the name. `sandbox.info.reused`
103
- * says which. The other fields apply only when it is created. */
104
- getOrCreate(name: string, input?: Omit<CreateSandbox, "name" | "getOrCreate"> & {
105
- wait?: boolean;
106
- }, options?: CreateOptions): Promise<Sandbox>;
107
- get(id: string, options?: RequestOptions): Promise<Sandbox>;
108
- /** Deletes a sandbox for good, by id, without reading it first: see
109
- * `sandbox.delete()`. Deleting it again answers the same. */
110
- delete(id: string, options?: RequestOptions): Promise<DeletedSandbox>;
111
- /** Live sandboxes, oldest first. Await for a page, or `for await` over all.
112
- * Request options apply to the initial request and every subsequent page. */
113
- list(filter?: {
114
- state?: SandboxInfo["state"][];
115
- includeStopped?: boolean;
116
- /** Every label must match. */
117
- labels?: Record<string, string>;
118
- name?: string;
119
- limit?: number;
120
- }, options?: RequestOptions): Promise<Page<Sandbox>>;
121
- /** Stops every live sandbox whose labels all match, eight at a time, and
122
- * says which stopped and which failed; one failure does not stop the rest.
123
- * Needs at least one label, so stopping everything is never one call. */
124
- stopAll(filter: {
125
- labels: Record<string, string>;
126
- }, options?: RequestOptions): Promise<{
127
- stopped: string[];
128
- failed: {
129
- id: string;
130
- error: unknown;
131
- }[];
132
- }>;
133
89
  }
134
90
  export declare class FeedbackApi {
135
91
  private readonly t;
package/dist/client.js CHANGED
@@ -1,10 +1,9 @@
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
- import { Transport, missingKey } from "./transport.js";
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
9
8
  * on first use. A constructor override must never receive another API's key. */
10
9
  const savedKey = async (apiOrigin) => {
@@ -30,15 +29,20 @@ export class Runtime {
30
29
  /** Date-range settlements, paged with exact charges and the server's CSV. */
31
30
  usageExport;
32
31
  constructor(options = {}) {
32
+ // The saved key is found by the origin as given, as `runtime login` saved
33
+ // it; the calls go where that origin is reachable from here, which in a
34
+ // sandbox is runtime.internal (transport.ts, `reachable`).
35
+ const origin = options.baseUrl || env("RUNTIME_API_URL") || DEFAULT_BASE_URL;
33
36
  this.transport = new Transport({
34
37
  // A key given, then RUNTIME_API_KEY, then (in Node and Bun) the key
35
38
  // `runtime login` saved for this machine, read on the first call.
36
- apiKey: options.apiKey ?? env("RUNTIME_API_KEY") ?? (() => savedKey(this.transport.baseUrl)),
39
+ apiKey: options.apiKey ?? env("RUNTIME_API_KEY") ?? (() => savedKey(origin)),
37
40
  baseUrl: options.baseUrl ?? env("RUNTIME_API_URL"),
38
41
  ...(options.fetch ? { fetch: options.fetch } : {}),
39
42
  ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
40
43
  ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
41
44
  ...(options.maxConnections !== undefined ? { maxConnections: options.maxConnections } : {}),
45
+ ...(options.onQueued ? { onQueued: options.onQueued } : {}),
42
46
  ...(options.waitForCapacityMs !== undefined
43
47
  ? { waitForCapacityMs: options.waitForCapacityMs }
44
48
  : {}),
@@ -64,8 +68,8 @@ export class Runtime {
64
68
  ...options,
65
69
  });
66
70
  }
67
- /** Balance, holds, trial time and per-resource charges, in integer microdollars. */
68
- /** 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. */
69
73
  usage(options = {}) {
70
74
  return this.transport.json({
71
75
  method: "GET",
@@ -85,103 +89,10 @@ export class Runtime {
85
89
  });
86
90
  }
87
91
  }
88
- export class Sandboxes {
89
- t;
92
+ /** `runtime.sandboxes`: the sandbox product of the machine engine. */
93
+ export class Sandboxes extends Machines {
90
94
  constructor(t) {
91
- this.t = t;
92
- }
93
- /** Creates a sandbox and waits until it is running. Every field is optional:
94
- * with none you get the free trial while it lasts, the default region and a
95
- * 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
96
- * When every trial slot or the account's quota is taken, it waits for one to
97
- * free, up to `waitForCapacityMs` (the client's, two minutes by default). */
98
- async create(input = {}, options = {}) {
99
- const { wait, ...body } = input;
100
- const info = await this.t.json({
101
- method: "POST",
102
- path: "/v1/sandboxes",
103
- body,
104
- wait: wait === false ? 0 : 60,
105
- ...options,
106
- waitForCapacityMs: options.waitForCapacityMs ?? this.t.waitForCapacityMs,
107
- });
108
- const sandbox = new Sandbox(this.t, info);
109
- if (wait !== false && info.state !== "running") {
110
- await sandbox.waitFor("running", {
111
- timeoutSeconds: WAIT_FOR_TIMEOUT_SECONDS,
112
- signal: options.signal,
113
- timeoutMs: options.timeoutMs,
114
- idempotencyKey: options.idempotencyKey,
115
- });
116
- if (sandbox.state !== "running")
117
- throw new RuntimeError({
118
- message: `Sandbox ${info.id} is ${sandbox.state}, not running.`,
119
- code: "start_failed",
120
- status: 0,
121
- hint: "Read it with runtime.sandboxes.get(id); stopReason says why.",
122
- });
123
- }
124
- return sandbox;
125
- }
126
- /** The sandbox named `name` in this account, ready to use: running as it
127
- * is, woken if it is paused, restarted if it is stopped and persistent, or
128
- * created with `input` when no sandbox has the name. `sandbox.info.reused`
129
- * says which. The other fields apply only when it is created. */
130
- async getOrCreate(name, input = {}, options = {}) {
131
- return this.create({ ...input, name, getOrCreate: true }, options);
132
- }
133
- async get(id, options = {}) {
134
- return new Sandbox(this.t, await this.t.json({
135
- method: "GET",
136
- path: `/v1/sandboxes/${encodeURIComponent(id)}`,
137
- ...options,
138
- }));
139
- }
140
- /** Deletes a sandbox for good, by id, without reading it first: see
141
- * `sandbox.delete()`. Deleting it again answers the same. */
142
- delete(id, options = {}) {
143
- return deleteSandbox(this.t, id, options);
144
- }
145
- /** Live sandboxes, oldest first. Await for a page, or `for await` over all.
146
- * Request options apply to the initial request and every subsequent page. */
147
- async list(filter = {}, options = {}) {
148
- const query = {
149
- ...(filter.state ? { state: filter.state } : {}),
150
- ...(filter.includeStopped ? { includeStopped: true } : {}),
151
- ...(filter.labels
152
- ? { label: Object.entries(filter.labels).map(([k, v]) => `${k}:${v}`) }
153
- : {}),
154
- ...(filter.name ? { name: filter.name } : {}),
155
- ...(filter.limit ? { limit: filter.limit } : {}),
156
- };
157
- return sandboxPage(this.t, await this.t.json({ method: "GET", path: "/v1/sandboxes", query, ...options }), query, options);
158
- }
159
- /** Stops every live sandbox whose labels all match, eight at a time, and
160
- * says which stopped and which failed; one failure does not stop the rest.
161
- * Needs at least one label, so stopping everything is never one call. */
162
- async stopAll(filter, options = {}) {
163
- if (!filter?.labels || Object.keys(filter.labels).length === 0)
164
- throw new RuntimeError({
165
- message: "stopAll needs at least one label to match.",
166
- code: "invalid_request",
167
- status: 0,
168
- hint: "Stop one sandbox with sandbox.stop(), or label the ones to stop together.",
169
- });
170
- const live = await (await this.list({ labels: filter.labels, limit: 100 }, options)).toArray(Infinity);
171
- // One key cannot name several stops: each stop makes its own.
172
- const { idempotencyKey: _one, ...each } = options;
173
- // In list order, oldest first, whichever finishes first.
174
- const errors = new Array(live.length);
175
- let next = 0;
176
- const worker = async () => {
177
- for (let at = next++; at < live.length; at = next++)
178
- await live[at].stop(each).catch((error) => (errors[at] = error ?? "failed"));
179
- };
180
- await Promise.all(Array.from({ length: Math.min(8, live.length) }, worker));
181
- return {
182
- stopped: live.filter((_, at) => errors[at] === undefined).map((sbx) => sbx.id),
183
- failed: live.flatMap((sbx, at) => errors[at] === undefined ? [] : [{ id: sbx.id, error: errors[at] }]),
184
- };
95
+ super(SANDBOXES, t, (transport, info) => new Sandbox(transport, info));
185
96
  }
186
97
  }
187
98
  export class FeedbackApi {
@@ -63,10 +63,14 @@ export class GuestFiles {
63
63
  if (!overwrite) {
64
64
  // cp -n alone reports success when it skips a raced destination. Stage
65
65
  // privately, then publish without replacement and verify it moved.
66
+ // mv -n that skips succeeds before coreutils 9.2 and fails from it on
67
+ // (the guest's 9.4 says "not replacing"), so a skip is told by the
68
+ // staged copy still being there beside a destination; with none there,
69
+ // mv's own error stands.
66
70
  await checked(sandbox, [
67
71
  "bash",
68
72
  "-c",
69
- 'set -e; parent=$(dirname -- "$2"); stage="$parent/.runtime-copy-$4"; mkdir -m 700 -- "$stage"; trap \'rm -rf -- "$stage"\' EXIT; if [ "$3" = true ]; then cp -R -n -- "$1" "$stage/item"; else cp -n -- "$1" "$stage/item"; fi; mv -n -T -- "$stage/item" "$2"; if [ -e "$stage/item" ] || [ -L "$stage/item" ]; then printf "Destination exists: %s\\n" "$2" >&2; exit 73; fi',
73
+ 'set -e; parent=$(dirname -- "$2"); stage="$parent/.runtime-copy-$4"; mkdir -m 700 -- "$stage"; trap \'rm -rf -- "$stage"\' EXIT; if [ "$3" = true ]; then cp -R -- "$1" "$stage/item"; else cp -- "$1" "$stage/item"; fi; mv -n -T -- "$stage/item" "$2" 2>"$stage/mv.err" || :; if [ -e "$stage/item" ] || [ -L "$stage/item" ]; then if [ -e "$2" ] || [ -L "$2" ]; then printf "Destination exists: %s\\n" "$2" >&2; exit 73; fi; cat -- "$stage/mv.err" >&2; exit 1; fi',
70
74
  "bash",
71
75
  this.path(from),
72
76
  this.path(to),
@@ -5,6 +5,7 @@ export interface Leased {
5
5
  readonly state: string;
6
6
  readonly info: {
7
7
  expiresAt: string;
8
+ endsAt?: string | null;
8
9
  };
9
10
  extend(seconds: number): Promise<unknown>;
10
11
  refresh(): Promise<unknown>;