withruntime 0.11.0 → 0.11.1

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 (60) hide show
  1. package/BLAXEL.md +7 -8
  2. package/DAYTONA.md +7 -8
  3. package/E2B.md +16 -2
  4. package/README.md +3 -3
  5. package/README.src.md +3 -3
  6. package/VERCEL.md +3 -2
  7. package/compatibility-lock.json +55 -0
  8. package/dist/api-defaults.d.ts +1 -1
  9. package/dist/api-defaults.js +1 -1
  10. package/dist/blaxel/lookup.d.ts +2 -1
  11. package/dist/blaxel/lookup.js +2 -1
  12. package/dist/blaxel/sandbox.d.ts +7 -5
  13. package/dist/blaxel/sandbox.js +15 -10
  14. package/dist/cli.js +39 -15
  15. package/dist/client.d.ts +5 -1
  16. package/dist/client.js +19 -3
  17. package/dist/compat/files.js +5 -1
  18. package/dist/compat/lease.d.ts +1 -0
  19. package/dist/compat/lease.js +14 -8
  20. package/dist/credentials.d.ts +9 -0
  21. package/dist/credentials.js +26 -4
  22. package/dist/daytona/daytona.js +29 -12
  23. package/dist/daytona/sandbox.d.ts +8 -3
  24. package/dist/daytona/sandbox.js +23 -4
  25. package/dist/e2b/sandbox.d.ts +7 -3
  26. package/dist/e2b/sandbox.js +21 -7
  27. package/dist/errors.d.ts +6 -0
  28. package/dist/errors.js +6 -0
  29. package/dist/identity.js +2 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/modal/index.js +6 -2
  33. package/dist/network-cli.js +7 -7
  34. package/dist/products/browser.d.ts +2 -1
  35. package/dist/products/browser.js +9 -19
  36. package/dist/products/desktop.d.ts +2 -1
  37. package/dist/products/desktop.js +5 -30
  38. package/dist/products/jobs.d.ts +7 -5
  39. package/dist/products/jobs.js +7 -3
  40. package/dist/products/mcp.d.ts +5 -1
  41. package/dist/products/mcp.js +29 -8
  42. package/dist/products/network-products.d.ts +1 -1
  43. package/dist/products/network-products.js +1 -1
  44. package/dist/products/observability.d.ts +1 -1
  45. package/dist/products/private-network.d.ts +1 -1
  46. package/dist/products/secrets.d.ts +2 -2
  47. package/dist/runloop/index.js +6 -2
  48. package/dist/sandbox.d.ts +27 -19
  49. package/dist/sandbox.js +121 -54
  50. package/dist/tar.d.ts +2 -0
  51. package/dist/tar.js +74 -16
  52. package/dist/transport.d.ts +33 -7
  53. package/dist/transport.js +171 -36
  54. package/dist/types.d.ts +54 -9
  55. package/dist/vercel/filesystem.js +3 -1
  56. package/dist/vercel/sandbox.d.ts +17 -10
  57. package/dist/vercel/sandbox.js +26 -13
  58. package/dist/wait.d.ts +12 -0
  59. package/dist/wait.js +47 -0
  60. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { whileInstalling } from "../wait.js";
1
2
  /** `sbx.desktop`: a Linux desktop in the sandbox, driven like a person would.
2
3
  *
3
4
  * const { streamUrl } = await sbx.desktop.start();
@@ -12,25 +13,10 @@ export function sandboxDesktop(t, sandbox) {
12
13
  const act = (body, options) => t.json({ method: "POST", path: `${base()}:act`, body, ...options });
13
14
  return {
14
15
  /** Starts the desktop. The first start in a sandbox installs it, which
15
- * takes a minute or two; this waits for that (up to 10 minutes). */
16
+ * takes a minute or two; this waits for that (up to 10 minutes, or the
17
+ * call's timeoutMs). */
16
18
  async start(size = {}, options) {
17
- const deadline = Date.now() + 600_000;
18
- for (;;) {
19
- try {
20
- return await t.json({
21
- method: "POST",
22
- path: `${base()}:start`,
23
- body: size,
24
- ...options,
25
- });
26
- }
27
- catch (error) {
28
- const code = error.code;
29
- if (code !== "desktop_installing" || Date.now() > deadline)
30
- throw error;
31
- await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 10_000));
32
- }
33
- }
19
+ return whileInstalling(["desktop_installing"], options, (request) => t.json({ method: "POST", path: `${base()}:start`, body: size, ...request }));
34
20
  },
35
21
  stop: (options) => t.json({ method: "POST", path: `${base()}:stop`, ...options }),
36
22
  /** PNG bytes, or JPEG with `format: "jpeg"`. */
@@ -53,18 +39,7 @@ export function sandboxDesktop(t, sandbox) {
53
39
  /** Opens `url` in Chromium on the desktop. Right after a sandbox's first
54
40
  * start, Chromium may still be installing; this waits for it. */
55
41
  async open(url, options) {
56
- const deadline = Date.now() + 600_000;
57
- for (;;) {
58
- try {
59
- return await act({ action: "open", url }, options);
60
- }
61
- catch (error) {
62
- const code = error.code;
63
- if (code !== "desktop_installing" || Date.now() > deadline)
64
- throw error;
65
- await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 10_000));
66
- }
67
- }
42
+ return whileInstalling(["desktop_installing"], options, (request) => act({ action: "open", url }, request));
68
43
  },
69
44
  /** Starts a program on the desktop, detached. */
70
45
  launch: (argv, options) => act({ action: "launch", argv }, options),
@@ -1,7 +1,7 @@
1
1
  import { Page } from "../page.js";
2
2
  import type { RequestOptions, Transport } from "../transport.js";
3
- /** A run's sandbox. Every field has a default; a run is a paid sandbox of this
4
- * size, billed at the sandbox rates. */
3
+ /** A run's sandbox. Every field has a default; a run is a sandbox of this
4
+ * size, on the trial's hours first and then on credit at the sandbox rates. */
5
5
  export type JobCompute = {
6
6
  region: string;
7
7
  vcpu: number;
@@ -147,11 +147,12 @@ export declare const JOB_DEFAULTS: {
147
147
  startSeconds: number;
148
148
  };
149
149
  /** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
150
- * the system image, and paid for at most an hour. */
150
+ * the system image and no larger than 400 GiB, and paid for at most an hour. */
151
151
  export declare const JOB_LIMITS: {
152
152
  maxVcpu: number;
153
153
  maxMemoryMiB: number;
154
154
  minDiskMiB: number;
155
+ maxDiskMiB: number;
155
156
  maxDurationSeconds: number;
156
157
  maxAttempts: number;
157
158
  maxSecrets: number;
@@ -167,8 +168,9 @@ export declare function jobBody(input: CreateJob): Record<string, unknown>;
167
168
  * request id, and says no retry will change it. */
168
169
  export declare function switchedOff(product: string): (error: unknown) => never;
169
170
  /** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
170
- * schedule. Each run is a paid sandbox, charged at the sandbox rates through
171
- * the same holds and spending limits; the trial's hours do not fund jobs.
171
+ * schedule. Each run is a sandbox, paid for as one: from the free trial's
172
+ * hours first while the run fits the trial's size and count, then from credit
173
+ * at the sandbox rates, through the same holds and spending limits.
172
174
  *
173
175
  * const job = await runtime.jobs.create({
174
176
  * name: "nightly-report",
@@ -15,11 +15,12 @@ export const JOB_DEFAULTS = {
15
15
  startSeconds: JOB_START_SECONDS,
16
16
  };
17
17
  /** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
18
- * the system image, and paid for at most an hour. */
18
+ * the system image and no larger than 400 GiB, and paid for at most an hour. */
19
19
  export const JOB_LIMITS = {
20
20
  maxVcpu: 16,
21
21
  maxMemoryMiB: 65_536,
22
22
  minDiskMiB: 3072,
23
+ maxDiskMiB: 409_600,
23
24
  maxDurationSeconds: 3600,
24
25
  maxAttempts: 5,
25
26
  maxSecrets: 16,
@@ -88,6 +89,8 @@ export function jobBody(input) {
88
89
  throw invalid(`A run's sandbox is at most ${JOB_LIMITS.maxVcpu} vCPU and ${JOB_LIMITS.maxMemoryMiB / 1024} GiB of memory; asked for ${compute.vcpu} vCPU and ${compute.memoryMiB} MiB.`, compute.vcpu > JOB_LIMITS.maxVcpu ? "compute.vcpu" : "compute.memoryMiB");
89
90
  if (compute.diskMiB < JOB_LIMITS.minDiskMiB)
90
91
  throw invalid(`A run's disk is at least ${JOB_LIMITS.minDiskMiB} MiB, the size of the system image; asked for ${compute.diskMiB}.`, "compute.diskMiB");
92
+ if (compute.diskMiB > JOB_LIMITS.maxDiskMiB)
93
+ throw invalid(`A run's disk is at most ${JOB_LIMITS.maxDiskMiB} MiB (${JOB_LIMITS.maxDiskMiB / 1024} GiB); asked for ${compute.diskMiB}.`, "compute.diskMiB");
91
94
  if (timeoutSeconds > compute.durationSeconds ||
92
95
  compute.durationSeconds > JOB_LIMITS.maxDurationSeconds)
93
96
  throw invalid(`timeoutSeconds (${timeoutSeconds}) must fit in the time a run is paid for (compute.durationSeconds ${compute.durationSeconds}), which is at most ${JOB_LIMITS.maxDurationSeconds} seconds.`, "timeoutSeconds");
@@ -125,8 +128,9 @@ export function switchedOff(product) {
125
128
  };
126
129
  }
127
130
  /** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
128
- * schedule. Each run is a paid sandbox, charged at the sandbox rates through
129
- * the same holds and spending limits; the trial's hours do not fund jobs.
131
+ * schedule. Each run is a sandbox, paid for as one: from the free trial's
132
+ * hours first while the run fits the trial's size and count, then from credit
133
+ * at the sandbox rates, through the same holds and spending limits.
130
134
  *
131
135
  * const job = await runtime.jobs.create({
132
136
  * name: "nightly-report",
@@ -68,10 +68,14 @@ export declare function sandboxMcp(t: Transport, sandbox: Sandbox): {
68
68
  }, options?: RequestOptions) => Promise<McpGateway>;
69
69
  /** Their state, and fresh URLs. */
70
70
  get: (options?: RequestOptions) => Promise<McpGateway>;
71
- /** Wait until every server is ready (or one failed). */
71
+ /** Wait until every server is ready (or one failed). Past `timeoutMs`
72
+ * (default ten minutes), the last state read; a status request still
73
+ * unanswered then is cut off at the deadline, and throws its timeout when
74
+ * no state was read at all. */
72
75
  ready(input?: {
73
76
  timeoutMs?: number;
74
77
  intervalMs?: number;
78
+ signal?: AbortSignal;
75
79
  }): Promise<McpGateway>;
76
80
  stop: (options?: RequestOptions) => Promise<{
77
81
  stopped: boolean;
@@ -1,3 +1,4 @@
1
+ import { pause } from "../wait.js";
1
2
  /** `runtime.mcp`: the catalog of MCP servers a sandbox can run. */
2
3
  export function mcp(t) {
3
4
  return {
@@ -20,16 +21,36 @@ export function sandboxMcp(t, sandbox) {
20
21
  start: (servers, input = {}, options) => t.json({ method: "POST", path: path(), body: { servers, ...input }, ...options }),
21
22
  /** Their state, and fresh URLs. */
22
23
  get: (options) => t.json({ method: "GET", path: path(), ...options }),
23
- /** Wait until every server is ready (or one failed). */
24
+ /** Wait until every server is ready (or one failed). Past `timeoutMs`
25
+ * (default ten minutes), the last state read; a status request still
26
+ * unanswered then is cut off at the deadline, and throws its timeout when
27
+ * no state was read at all. */
24
28
  async ready(input = {}) {
25
- const deadline = Date.now() + (input.timeoutMs ?? 600_000);
29
+ const deadline = performance.now() + (input.timeoutMs ?? 600_000);
30
+ let last;
26
31
  for (;;) {
27
- const state = await t.json({ method: "GET", path: path() });
28
- if (!state.running || state.servers.every((server) => server.status !== "installing"))
29
- return state;
30
- if (Date.now() > deadline)
31
- return state;
32
- await new Promise((resolve) => setTimeout(resolve, input.intervalMs ?? 2_000));
32
+ const left = Math.ceil(deadline - performance.now());
33
+ if (left <= 0 && last)
34
+ return last;
35
+ try {
36
+ last = await t.json({
37
+ method: "GET",
38
+ path: path(),
39
+ timeoutMs: Math.max(1, left),
40
+ ...(input.signal ? { signal: input.signal } : {}),
41
+ });
42
+ }
43
+ catch (error) {
44
+ if (last && !input.signal?.aborted && error.code === "timeout")
45
+ return last;
46
+ throw error;
47
+ }
48
+ if (!last.running || last.servers.every((server) => server.status !== "installing"))
49
+ return last;
50
+ const rest = deadline - performance.now();
51
+ if (rest <= 0)
52
+ return last;
53
+ await pause(Math.min(input.intervalMs ?? 2_000, rest), input.signal);
33
54
  }
34
55
  },
35
56
  stop: (options) => t.json({ method: "DELETE", path: path(), ...options }),
@@ -193,7 +193,7 @@ export type SetUpstreamProxy = {
193
193
  * `upstreamProxy`: after Runtime's own rules allow a connection, the host
194
194
  * reaches it with CONNECT through your proxy, so your proxy's controls and
195
195
  * logs apply. A proxy that refuses or cannot be reached fails the connection
196
- * (upstream-proxy-failed); nothing goes around it. Paid accounts only. */
196
+ * (upstream-proxy-failed); nothing goes around it. Needs a kept top-up. */
197
197
  export declare function network(t: Transport): {
198
198
  /** Your sandboxes reaching each other by name (`private-network.ts`). */
199
199
  private: {
@@ -88,7 +88,7 @@ export function tunnel(t) {
88
88
  * `upstreamProxy`: after Runtime's own rules allow a connection, the host
89
89
  * reaches it with CONNECT through your proxy, so your proxy's controls and
90
90
  * logs apply. A proxy that refuses or cannot be reached fails the connection
91
- * (upstream-proxy-failed); nothing goes around it. Paid accounts only. */
91
+ * (upstream-proxy-failed); nothing goes around it. Needs a kept top-up. */
92
92
  export function network(t) {
93
93
  const path = "/v1/network/upstream-proxy";
94
94
  return {
@@ -35,7 +35,7 @@ export type SandboxMetrics = {
35
35
  latest: MetricPoint | null;
36
36
  points: MetricPoint[];
37
37
  };
38
- export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted";
38
+ export type WebhookEventType = "sandbox.created" | "sandbox.running" | "sandbox.paused" | "sandbox.woken" | "sandbox.stopped" | "sandbox.start_failed" | "sandbox.wake_failed" | "sandbox.host_unreachable" | "sandbox.host_reachable" | "sandbox.recovered" | "sandbox.recovery_failed" | "snapshot.ready" | "snapshot.failed" | "snapshot.deleted" | "volume.ready" | "volume.failed" | "volume.deleted";
39
39
  /** An event as /v1/events lists it and a webhook delivers it. */
40
40
  export type RuntimeEvent = {
41
41
  id: string;
@@ -4,7 +4,7 @@ export type PrivateNetwork = {
4
4
  enabled: boolean;
5
5
  /** `sandbox.internal`. */
6
6
  suffix: string;
7
- /** Whether this account may turn it on: paid accounts only. */
7
+ /** Whether this account may turn it on: it must have a kept top-up. */
8
8
  allowed: boolean;
9
9
  /** Why not, when `allowed` is false: `not_paid`, `revoked` or `suspended`. */
10
10
  why: string | null;
@@ -4,7 +4,7 @@ import type { RequestOptions, Transport } from "../transport.js";
4
4
  * absent) and `paths`, each exact (`/v1/chat/completions`) or a prefix ending
5
5
  * in `/*` (`/repos/acme/*`), written canonically: starting with `/`, no `.` or
6
6
  * `..` segments, no `;` or `\`, no encoded slash. A request whose path could be
7
- * read two ways gets no secret that has rules. Paid accounts only. */
7
+ * read two ways gets no secret that has rules. Needs credit on the account. */
8
8
  export type SecretRule = {
9
9
  methods?: string[];
10
10
  paths: string[];
@@ -37,7 +37,7 @@ export type SetSecret = {
37
37
  header?: string;
38
38
  /** With `header`: its value, `{value}` where the secret goes. Default `{value}`. */
39
39
  format?: string;
40
- /** Only requests a rule allows carry the value. Paid accounts only. 1 to 16.
40
+ /** Only requests a rule allows carry the value. Needs credit on the account. 1 to 16.
41
41
  * Absent: every request to the hosts; replacing a secret without rules
42
42
  * clears them. */
43
43
  rules?: SecretRule[];
@@ -172,8 +172,12 @@ export class Devboxes {
172
172
  vcpu: p.custom_cpu_cores ?? size?.[0],
173
173
  memoryMiB: p.custom_gb_memory ? p.custom_gb_memory * 1024 : size?.[1],
174
174
  diskMiB: p.custom_disk_size ? p.custom_disk_size * 1024 : size?.[2],
175
- timeoutSeconds: p.keep_alive_time_seconds ?? 3600,
176
- idlePauseSeconds: 0,
175
+ // keep_alive_time_seconds is the customer's limit, with no idle pause
176
+ // before it. None is no time limit (0300): it runs while it works and
177
+ // pauses when idle.
178
+ ...(p.keep_alive_time_seconds === undefined || p.keep_alive_time_seconds === null
179
+ ? {}
180
+ : { timeoutSeconds: p.keep_alive_time_seconds, idlePauseSeconds: 0 }),
177
181
  onLeaseEnd: "stop",
178
182
  }, input.environment_variables ?? undefined, async (sandbox) => {
179
183
  for (const [path, content] of Object.entries(input.file_mounts ?? {})) {
package/dist/sandbox.d.ts CHANGED
@@ -105,12 +105,15 @@ export declare class Sandbox implements AsyncDisposable {
105
105
  * lists. Its snapshots, usage and audit entries stay. Deleting it again
106
106
  * answers the same. */
107
107
  delete(options?: RequestOptions): Promise<DeletedSandbox>;
108
- /** Carries on a paused sandbox; `timeoutSeconds` is its new lease. */
108
+ /** Carries on a paused sandbox; for one with a time limit,
109
+ * `timeoutSeconds` is its new one. */
109
110
  wake(options?: RequestOptions & {
110
111
  wait?: boolean;
111
112
  timeoutSeconds?: number;
112
113
  }): Promise<this>;
113
- /** More time before the lease ends (at most an hour ahead of now). */
114
+ /** More time before its time limit ends (at most an hour ahead of now). A
115
+ * sandbox with no time limit needs none: the call answers at once and
116
+ * changes nothing. */
114
117
  extend(seconds: number, options?: RequestOptions): Promise<this>;
115
118
  /** Changes its name, labels, automatic wake, idle pause or persistence;
116
119
  * fields left out stay as they are. `persistent: true` keeps it running
@@ -120,33 +123,36 @@ export declare class Sandbox implements AsyncDisposable {
120
123
  * its id, /workspace (its home: dotfiles, pip --user and npm -g installs),
121
124
  * volumes, environment, name and previews. Its processes restart, and
122
125
  * everything else on its old disk (sudo installs, apt packages, /etc) is
123
- * lost, which `keep: "workspace"` says you know; snapshot it first to keep
124
- * everything. A running sandbox is paused first. A switch that fails is
125
- * undone (`switch_undone`), the sandbox on its old image with nothing lost.
126
- * Charged as a wake. */
127
- switchImage(image: string, options: RequestOptions & {
128
- keep: "workspace";
126
+ * lost; snapshot it first to keep everything. A running sandbox is paused
127
+ * first. A switch that fails is undone (`switch_undone`), the sandbox on
128
+ * its old image with nothing lost. Charged as a wake. `keep` is optional:
129
+ * "workspace" is the only value, and it is sent either way, so an API from
130
+ * before it was optional accepts the call too. */
131
+ switchImage(image: string, options?: RequestOptions & {
132
+ keep?: "workspace";
129
133
  }): Promise<this>;
130
134
  /** Gives it more or fewer vCPUs or more or less memory by a restart: its id,
131
135
  * whole disk, volumes, environment, name and previews stay, and its
132
- * programs stop, which `restart: true` says you know (snapshot it first to
133
- * keep its memory too). A running sandbox is paused, or stopped if it is
136
+ * programs stop (snapshot it first to keep its memory too). A running sandbox is paused, or stopped if it is
134
137
  * persistent, and comes back running at the new size on the same server; a
135
138
  * paused or stopped one is started. Memory bills on the new size from then.
136
139
  * Refused, with nothing changed, when the server has no room
137
140
  * (`no_capacity`), above your quota or the trial's 2 vCPU and 4 GiB, or
138
- * while a snapshot or fork of it is being taken. */
141
+ * while a snapshot or fork of it is being taken. `restart` is optional:
142
+ * true is the only value, and it is sent either way, so an API from before
143
+ * it was optional accepts the call too. */
139
144
  resize(size: {
140
145
  vcpu?: number;
141
146
  memoryMiB?: number;
142
- }, options: RequestOptions & {
143
- restart: true;
147
+ }, options?: RequestOptions & {
148
+ restart?: true;
144
149
  }): Promise<this>;
145
- /** Keeps a running sandbox's lease ahead of now, in the background, until
146
- * stop() or the returned function ends it: every `everySeconds` (60) it
147
- * extends the lease so that `marginSeconds` (600) remain, never more than
148
- * the hour ahead the API allows. Running time is billed as it is used, as
149
- * for any extension. A paused sandbox is left paused (a request wakes it,
150
+ /** Keeps a sandbox with a time limit running past it, in the background,
151
+ * until stop() or the returned function ends it: every `everySeconds` (60)
152
+ * it extends the limit so that `marginSeconds` (600) remain, never more
153
+ * than the hour ahead the API allows. A sandbox with no time limit
154
+ * (`endsAt` null) needs none, and is only watched. Running time is billed
155
+ * as it is used. A paused sandbox is left paused (a request wakes it,
150
156
  * unless autoWake is off); a stopped one ends the loop. It does not keep a
151
157
  * Node or Bun process alive by itself. */
152
158
  keepAlive(options?: KeepAliveOptions): () => void;
@@ -154,7 +160,9 @@ export declare class Sandbox implements AsyncDisposable {
154
160
  setRetention(days: number, options?: RequestOptions): Promise<this>;
155
161
  /** Keeps this sandbox's whole machine (files, memory, running processes) as
156
162
  * a snapshot to start new sandboxes from. A running sandbox is paused for
157
- * the moment it takes, then woken; a paused one stays paused. */
163
+ * the moment it takes, then woken; a paused one stays paused. Past
164
+ * `timeoutMs` (ten minutes unless given) mid-capture, it stays paused and
165
+ * the error names the snapshot. */
158
166
  snapshot(options?: SnapshotOptions & RequestOptions): Promise<Snapshot>;
159
167
  /** Copies of this sandbox as it is now (files, memory, running processes),
160
168
  * each its own sandbox, answered once they run. A running sandbox is paused