withruntime 0.11.2 → 0.12.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 (164) hide show
  1. package/BLAXEL.md +2 -2
  2. package/COMPATIBILITY.md +74 -9
  3. package/DAYTONA.md +33 -5
  4. package/E2B.md +85 -24
  5. package/README.md +9 -8
  6. package/README.src.md +8 -7
  7. package/VERCEL.md +43 -5
  8. package/compatibility-lock.json +58 -49
  9. package/dist/billing-cli.d.ts +1 -1
  10. package/dist/billing-cli.js +77 -1
  11. package/dist/blaxel/errors.d.ts +14 -0
  12. package/dist/blaxel/errors.js +29 -0
  13. package/dist/blaxel/index.d.ts +2 -2
  14. package/dist/blaxel/index.js +4 -4
  15. package/dist/blaxel/interpreter.d.ts +2 -5
  16. package/dist/blaxel/sandbox.d.ts +14 -5
  17. package/dist/blaxel/sandbox.js +63 -7
  18. package/dist/blaxel/snapshot.d.ts +4 -0
  19. package/dist/blaxel/snapshot.js +4 -0
  20. package/dist/cli/logs.d.ts +21 -0
  21. package/dist/cli/logs.js +188 -0
  22. package/dist/cli/logs.test.d.ts +1 -0
  23. package/dist/cli/logs.test.js +272 -0
  24. package/dist/cli-extras.d.ts +3 -3
  25. package/dist/cli-extras.js +17 -12
  26. package/dist/cli-words.d.ts +10 -1
  27. package/dist/cli-words.js +18 -0
  28. package/dist/cli.d.ts +5 -1
  29. package/dist/cli.js +405 -204
  30. package/dist/client.d.ts +2 -4
  31. package/dist/client.js +27 -11
  32. package/dist/cloudflare/errors.d.ts +143 -0
  33. package/dist/cloudflare/errors.js +167 -0
  34. package/dist/cloudflare/index.d.ts +175 -6
  35. package/dist/cloudflare/index.js +416 -26
  36. package/dist/cloudflare/interpreter.d.ts +119 -0
  37. package/dist/cloudflare/interpreter.js +223 -0
  38. package/dist/cloudflare/streams.d.ts +38 -0
  39. package/dist/cloudflare/streams.js +145 -0
  40. package/dist/cloudflare/watch.d.ts +9 -0
  41. package/dist/cloudflare/watch.js +24 -0
  42. package/dist/codesandbox/index.d.ts +216 -28
  43. package/dist/codesandbox/index.js +478 -78
  44. package/dist/compat/core.d.ts +18 -0
  45. package/dist/compat/core.js +29 -0
  46. package/dist/compat/files.d.ts +1 -0
  47. package/dist/compat/files.js +25 -5
  48. package/dist/daytona/computer-use.d.ts +13 -4
  49. package/dist/daytona/computer-use.js +66 -8
  50. package/dist/daytona/daytona.d.ts +6 -0
  51. package/dist/daytona/daytona.js +30 -3
  52. package/dist/daytona/errors.d.ts +11 -0
  53. package/dist/daytona/errors.js +18 -0
  54. package/dist/daytona/filesystem.js +2 -1
  55. package/dist/daytona/image.d.ts +13 -9
  56. package/dist/daytona/image.js +18 -12
  57. package/dist/daytona/index.d.ts +1 -1
  58. package/dist/daytona/sandbox.d.ts +8 -2
  59. package/dist/daytona/sandbox.js +16 -2
  60. package/dist/domains/index.d.ts +9 -0
  61. package/dist/domains/index.js +10 -0
  62. package/dist/e2b/commands.d.ts +1 -1
  63. package/dist/e2b/commands.js +6 -7
  64. package/dist/e2b/errors.d.ts +17 -0
  65. package/dist/e2b/errors.js +40 -1
  66. package/dist/e2b/index.d.ts +2 -2
  67. package/dist/e2b/index.js +1 -1
  68. package/dist/e2b/sandbox.d.ts +27 -21
  69. package/dist/e2b/sandbox.js +34 -8
  70. package/dist/e2b/unsupported.js +1 -1
  71. package/dist/errors.d.ts +10 -1
  72. package/dist/errors.js +16 -4
  73. package/dist/freestyle/index.d.ts +285 -37
  74. package/dist/freestyle/index.js +656 -144
  75. package/dist/identity.d.ts +3 -0
  76. package/dist/identity.js +27 -0
  77. package/dist/images/index.d.ts +9 -0
  78. package/dist/images/index.js +10 -0
  79. package/dist/index.d.ts +1 -0
  80. package/dist/ips/index.d.ts +9 -0
  81. package/dist/ips/index.js +10 -0
  82. package/dist/jobs/index.d.ts +9 -0
  83. package/dist/jobs/index.js +10 -0
  84. package/dist/keys.js +2 -0
  85. package/dist/login.js +2 -0
  86. package/dist/machine.d.ts +12 -3
  87. package/dist/machine.js +20 -3
  88. package/dist/mcp.js +1 -1
  89. package/dist/modal/index.d.ts +339 -61
  90. package/dist/modal/index.js +781 -121
  91. package/dist/network-cli.d.ts +3 -1
  92. package/dist/network-cli.js +78 -36
  93. package/dist/networks/index.d.ts +11 -0
  94. package/dist/networks/index.js +10 -0
  95. package/dist/ports/index.d.ts +9 -0
  96. package/dist/ports/index.js +10 -0
  97. package/dist/product-names.d.ts +52 -0
  98. package/dist/product-names.js +164 -0
  99. package/dist/product-renames.d.ts +103 -0
  100. package/dist/product-renames.js +189 -0
  101. package/dist/products/apps.d.ts +176 -0
  102. package/dist/products/apps.js +165 -0
  103. package/dist/products/billing.d.ts +41 -0
  104. package/dist/products/billing.js +13 -0
  105. package/dist/products/browser.d.ts +10 -0
  106. package/dist/products/browsers.d.ts +71 -0
  107. package/dist/products/browsers.js +102 -0
  108. package/dist/products/builds.d.ts +126 -0
  109. package/dist/products/builds.js +221 -0
  110. package/dist/products/index.d.ts +3 -16
  111. package/dist/products/index.js +3 -14
  112. package/dist/products/jobs.d.ts +17 -0
  113. package/dist/products/jobs.js +27 -10
  114. package/dist/products/logs.d.ts +245 -0
  115. package/dist/products/logs.js +274 -0
  116. package/dist/products/logs.test.d.ts +1 -0
  117. package/dist/products/logs.test.js +343 -0
  118. package/dist/products/network-products.d.ts +38 -7
  119. package/dist/products/network-products.js +31 -18
  120. package/dist/products/observability.d.ts +3 -2
  121. package/dist/products/private-network.js +1 -1
  122. package/dist/products/queues.d.ts +149 -0
  123. package/dist/products/queues.js +213 -0
  124. package/dist/products/referrals.d.ts +21 -7
  125. package/dist/products/referrals.js +3 -3
  126. package/dist/products/secrets.d.ts +7 -3
  127. package/dist/products/secrets.js +39 -18
  128. package/dist/products/signed-requests.d.ts +21 -0
  129. package/dist/products/signed-requests.js +11 -0
  130. package/dist/products/streams.d.ts +73 -0
  131. package/dist/products/streams.js +17 -0
  132. package/dist/products/switching.d.ts +35 -7
  133. package/dist/products/switching.js +4 -3
  134. package/dist/products/volumes.d.ts +3 -0
  135. package/dist/record-cli.d.ts +23 -0
  136. package/dist/record-cli.js +308 -0
  137. package/dist/renamed-products.d.ts +38 -0
  138. package/dist/renamed-products.js +34 -0
  139. package/dist/runloop/index.d.ts +9 -2
  140. package/dist/runloop/index.js +29 -8
  141. package/dist/runloop/sdk.d.ts +131 -0
  142. package/dist/runloop/sdk.js +167 -0
  143. package/dist/sandbox.d.ts +3 -2
  144. package/dist/sandbox.js +14 -9
  145. package/dist/sandboxes/index.d.ts +11 -0
  146. package/dist/sandboxes/index.js +12 -0
  147. package/dist/snapshots/index.d.ts +9 -0
  148. package/dist/snapshots/index.js +11 -0
  149. package/dist/snapshots.d.ts +7 -4
  150. package/dist/sprites/index.d.ts +343 -31
  151. package/dist/sprites/index.js +866 -84
  152. package/dist/ssh.d.ts +1 -1
  153. package/dist/ssh.js +11 -11
  154. package/dist/terminal.d.ts +24 -0
  155. package/dist/terminal.js +32 -0
  156. package/dist/transport.d.ts +15 -1
  157. package/dist/transport.js +56 -3
  158. package/dist/tunnel.js +1 -1
  159. package/dist/types.d.ts +63 -0
  160. package/dist/vercel/sandbox.js +5 -3
  161. package/dist/vercel/snapshot.js +10 -2
  162. package/dist/volumes/index.d.ts +9 -0
  163. package/dist/volumes/index.js +10 -0
  164. package/package.json +48 -2
@@ -13,6 +13,24 @@ export declare class CompatibilityError extends Error {
13
13
  readonly code = "unsupported_compatibility_option";
14
14
  constructor(provider: string, feature: string);
15
15
  }
16
+ /** The error for a vendor feature an adapter does not provide, naming
17
+ * Runtime's alternative. */
18
+ export declare function refusal(provider: string, feature: string, alternative: string): CompatibilityError;
19
+ /** A vendor method an adapter does not provide: declare it on the class as
20
+ * `declare readonly name: Refused` (or `RefusedAsync` where the vendor's
21
+ * method returns a promise), and `refuse` installs it. A call fails with a
22
+ * CompatibilityError naming Runtime's alternative, never "is not a function". */
23
+ export type Refused = (...args: unknown[]) => never;
24
+ export type RefusedAsync = (...args: unknown[]) => Promise<never>;
25
+ export declare function refuse(provider: string, owner: string, target: {
26
+ prototype: object;
27
+ }, members: Record<string, string>, { sync }?: {
28
+ sync?: boolean;
29
+ }): void;
30
+ /** A vendor export (a function, or a class nobody can obtain here) that an
31
+ * adapter does not provide: `export const Name: Refused = refused(...)`.
32
+ * Calling it, or `new` on it, throws. */
33
+ export declare function refused(provider: string, name: string, alternative: string): Refused;
16
34
  export declare function only(provider: string, value: object, keys: readonly string[]): void;
17
35
  export declare function region(provider: string, value?: string | null): string | undefined;
18
36
  export declare const quote: (value: string) => string;
@@ -27,6 +27,35 @@ export class CompatibilityError extends Error {
27
27
  this.name = "CompatibilityError";
28
28
  }
29
29
  }
30
+ /** The error for a vendor feature an adapter does not provide, naming
31
+ * Runtime's alternative. */
32
+ export function refusal(provider, feature, alternative) {
33
+ const error = new CompatibilityError(provider, feature);
34
+ error.message = `${error.message.slice(0, -1)}; ${alternative}.`;
35
+ return error;
36
+ }
37
+ export function refuse(provider, owner, target, members, { sync = false } = {}) {
38
+ for (const [member, alternative] of Object.entries(members)) {
39
+ const error = () => refusal(provider, `${owner}.${member}`, alternative);
40
+ Object.defineProperty(target.prototype, member, {
41
+ configurable: true,
42
+ writable: true,
43
+ value: sync
44
+ ? () => {
45
+ throw error();
46
+ }
47
+ : () => Promise.reject(error()),
48
+ });
49
+ }
50
+ }
51
+ /** A vendor export (a function, or a class nobody can obtain here) that an
52
+ * adapter does not provide: `export const Name: Refused = refused(...)`.
53
+ * Calling it, or `new` on it, throws. */
54
+ export function refused(provider, name, alternative) {
55
+ return function refusedExport() {
56
+ throw refusal(provider, name, alternative);
57
+ };
58
+ }
30
59
  export function only(provider, value, keys) {
31
60
  for (const [key, item] of Object.entries(value))
32
61
  if (item !== undefined && item !== null && !keys.includes(key))
@@ -1,6 +1,7 @@
1
1
  import type { Sandbox } from "../sandbox.js";
2
2
  /** Guest operations use argv, never interpolated shell paths. */
3
3
  export declare class GuestFiles {
4
+ #private;
4
5
  private readonly get;
5
6
  readonly cwd: string;
6
7
  constructor(get: () => Promise<Sandbox>, cwd?: string);
@@ -3,6 +3,9 @@ import { checked, missing, pathAt } from "./core.js";
3
3
  export class GuestFiles {
4
4
  get;
5
5
  cwd;
6
+ /** Staging copies whose removal failed; the next staged write or
7
+ * append tries them again. */
8
+ #leftover = new Set();
6
9
  constructor(get, cwd = "/workspace") {
7
10
  this.get = get;
8
11
  this.cwd = cwd;
@@ -10,6 +13,24 @@ export class GuestFiles {
10
13
  path(path) {
11
14
  return pathAt(path, this.cwd);
12
15
  }
16
+ /** Removes a staging copy, and remembers it for the next staged call if the
17
+ * removal fails. It never throws: the write's outcome, including its
18
+ * original failure, stands, and a successful destination write must not
19
+ * invite a replay. */
20
+ async #discard(sandbox, staging) {
21
+ try {
22
+ await sandbox.files.remove(staging);
23
+ this.#leftover.delete(staging);
24
+ }
25
+ catch {
26
+ this.#leftover.add(staging);
27
+ }
28
+ }
29
+ /** Tries again every staging copy an earlier call could not remove. */
30
+ async #sweep(sandbox) {
31
+ for (const staging of [...this.#leftover])
32
+ await this.#discard(sandbox, staging);
33
+ }
13
34
  async readFile(path) {
14
35
  return (await this.get()).files.read(this.path(path));
15
36
  }
@@ -24,6 +45,7 @@ export class GuestFiles {
24
45
  await sandbox.files.write(target, content, options);
25
46
  return;
26
47
  }
48
+ await this.#sweep(sandbox);
27
49
  const staging = `/workspace/.runtime-compat/write-${crypto.randomUUID()}`;
28
50
  try {
29
51
  await sandbox.files.write(staging, bytes, { mode: 0o600, signal: options.signal });
@@ -39,9 +61,7 @@ export class GuestFiles {
39
61
  ], { signal: options.signal });
40
62
  }
41
63
  finally {
42
- // Cleanup must preserve the write's outcome, including its original
43
- // failure; a successful destination write must not invite a replay.
44
- await sandbox.files.remove(staging).catch(() => undefined);
64
+ await this.#discard(sandbox, staging);
45
65
  }
46
66
  }
47
67
  writeTextFile(path, content) {
@@ -117,6 +137,7 @@ export class GuestFiles {
117
137
  });
118
138
  return;
119
139
  }
140
+ await this.#sweep(sandbox);
120
141
  const staging = `/workspace/.runtime-compat/append-${crypto.randomUUID()}`;
121
142
  try {
122
143
  await sandbox.files.write(staging, bytes, { mode: 0o600 });
@@ -130,8 +151,7 @@ export class GuestFiles {
130
151
  ]);
131
152
  }
132
153
  finally {
133
- // A cleanup failure after success must not invite appending twice.
134
- await sandbox.files.remove(staging).catch(() => undefined);
154
+ await this.#discard(sandbox, staging);
135
155
  }
136
156
  }
137
157
  }
@@ -73,10 +73,16 @@ export declare class Mouse {
73
73
  constructor(live: Live);
74
74
  getPosition(): Promise<Position>;
75
75
  move(x: number, y: number): Promise<Position>;
76
- click(x: number, y: number, button?: string, double?: boolean): Promise<Position>;
77
- drag(startX: number, startY: number, endX: number, endY: number, button?: string): Promise<Position>;
78
- /** `amount` wheel clicks up or down at (x, y). */
79
- scroll(x: number, y: number, direction: "up" | "down", amount?: number): Promise<boolean>;
76
+ /** Presses and holds `button`, first moving to (x, y) if both are given,
77
+ * until `up()`. */
78
+ down(x?: number, y?: number, button?: string): Promise<Position>;
79
+ /** Releases a held `button`, first moving to (x, y) if both are given. */
80
+ up(x?: number, y?: number, button?: string): Promise<Position>;
81
+ /** One click, or two with `double`; `clicks` (1 or 2 here) wins over `double`. */
82
+ click(x: number, y: number, button?: string, double?: boolean, clicks?: number, modifiers?: string[]): Promise<Position>;
83
+ drag(startX: number, startY: number, endX: number, endY: number, button?: string, modifiers?: string[]): Promise<Position>;
84
+ /** `amount` wheel clicks up, down, left or right at (x, y). */
85
+ scroll(x: number, y: number, direction: "up" | "down" | "left" | "right", amount?: number, modifiers?: string[]): Promise<boolean>;
80
86
  }
81
87
  /** `sandbox.computerUse.keyboard`. Key names are Daytona's (enter, esc,
82
88
  * ctrl, cmd, pageup, f5) or xdotool's (Return, Escape, Prior). */
@@ -89,6 +95,9 @@ export declare class Keyboard {
89
95
  press(key: string, modifiers?: string[]): Promise<void>;
90
96
  /** A combination such as "ctrl+c" or "ctrl+shift+t". */
91
97
  hotkey(keys: string): Promise<void>;
98
+ /** Runtime's desktop presses keys and lets go at once; it cannot hold one. */
99
+ down(_key: string): Promise<void>;
100
+ up(_key: string): Promise<void>;
92
101
  }
93
102
  /** `sandbox.computerUse.screenshot`: the whole screen, PNG or JPEG. */
94
103
  export declare class Screenshot {
@@ -21,12 +21,44 @@ export class Mouse {
21
21
  await guard("other", async () => (await desktop(this.#live)).move(x, y));
22
22
  return { x, y };
23
23
  }
24
- async click(x, y, button = "left", double = false) {
25
- await guard("other", async () => (await desktop(this.#live)).click(x, y, { button: buttonOf(button), double }));
24
+ /** Presses and holds `button`, first moving to (x, y) if both are given,
25
+ * until `up()`. */
26
+ down(x, y, button = "left") {
27
+ return this.#hold("mouseDown", x, y, button);
28
+ }
29
+ /** Releases a held `button`, first moving to (x, y) if both are given. */
30
+ up(x, y, button = "left") {
31
+ return this.#hold("mouseUp", x, y, button);
32
+ }
33
+ async #hold(action, x, y, button) {
34
+ const which = buttonOf(button);
35
+ return guard("other", async () => {
36
+ const screen = await desktop(this.#live);
37
+ const at = x !== undefined && y !== undefined;
38
+ if (at)
39
+ await screen.move(x, y);
40
+ await screen[action](which);
41
+ if (at)
42
+ return { x, y };
43
+ const where = await screen.cursor();
44
+ return { x: where.x, y: where.y };
45
+ });
46
+ }
47
+ /** One click, or two with `double`; `clicks` (1 or 2 here) wins over `double`. */
48
+ async click(x, y, button = "left", double = false, clicks, modifiers) {
49
+ const which = buttonOf(button);
50
+ held(modifiers, "click");
51
+ const count = clicks ?? (double ? 2 : 1);
52
+ if (!Number.isInteger(count) || count < 1 || count > 10)
53
+ throw new RangeError(`clicks is a whole number from 1 to 10, not ${JSON.stringify(clicks)}.`);
54
+ if (count > 2)
55
+ throw new NotSupportedError(`${count} clicks at once (clicks)`, "Click once or twice (double: true); to select a line, click once and press shift+End.");
56
+ await guard("other", async () => (await desktop(this.#live)).click(x, y, { button: which, double: count === 2 }));
26
57
  return { x, y };
27
58
  }
28
- async drag(startX, startY, endX, endY, button = "left") {
59
+ async drag(startX, startY, endX, endY, button = "left", modifiers) {
29
60
  const which = buttonOf(button);
61
+ held(modifiers, "drag");
30
62
  await guard("other", async () => {
31
63
  const screen = await desktop(this.#live);
32
64
  if (which === "left")
@@ -38,11 +70,14 @@ export class Mouse {
38
70
  });
39
71
  return { x: endX, y: endY };
40
72
  }
41
- /** `amount` wheel clicks up or down at (x, y). */
42
- async scroll(x, y, direction, amount = 1) {
43
- if (direction !== "up" && direction !== "down")
44
- throw new RangeError(`direction is "up" or "down", not ${JSON.stringify(direction)}.`);
45
- await guard("other", async () => (await desktop(this.#live)).scroll(direction === "down" ? amount : -amount, { x, y }));
73
+ /** `amount` wheel clicks up, down, left or right at (x, y). */
74
+ async scroll(x, y, direction, amount = 1, modifiers) {
75
+ const step = SCROLL[direction];
76
+ if (!step)
77
+ throw new RangeError(`direction is "up", "down", "left" or "right", not ${JSON.stringify(direction)}.`);
78
+ held(modifiers, "scroll");
79
+ const [dy, dx] = [step[0] * amount, step[1] * amount];
80
+ await guard("other", async () => (await desktop(this.#live)).scroll(dy, dx ? { dx, x, y } : { x, y }));
46
81
  return true;
47
82
  }
48
83
  }
@@ -65,6 +100,13 @@ export class Keyboard {
65
100
  async hotkey(keys) {
66
101
  await guard("other", async () => (await desktop(this.#live)).press(keys.split("+").map(keyOf).join("+")));
67
102
  }
103
+ /** Runtime's desktop presses keys and lets go at once; it cannot hold one. */
104
+ down(_key) {
105
+ return Promise.reject(holding());
106
+ }
107
+ up(_key) {
108
+ return Promise.reject(holding());
109
+ }
68
110
  }
69
111
  /** `sandbox.computerUse.screenshot`: the whole screen, PNG or JPEG. */
70
112
  export class Screenshot {
@@ -248,6 +290,22 @@ function processes() {
248
290
  function region() {
249
291
  return new NotSupportedError("Screenshots of part of the screen", "Take the whole screen with takeFullScreen() or takeCompressed() and crop it yourself.");
250
292
  }
293
+ /** [dy, dx] per wheel click: positive is down and right. */
294
+ const SCROLL = {
295
+ up: [-1, 0],
296
+ down: [1, 0],
297
+ left: [0, -1],
298
+ right: [0, 1],
299
+ };
300
+ function holding() {
301
+ return new NotSupportedError("Holding a key down (keyboard.down and keyboard.up)", 'Press the key with its modifiers at once: keyboard.press("t", ["ctrl", "shift"]) or keyboard.hotkey("ctrl+shift+t").');
302
+ }
303
+ /** Keys held through a click, drag or scroll need a key held down, which
304
+ * Runtime's desktop cannot do; refused before anything happens. */
305
+ function held(modifiers, what) {
306
+ if (modifiers?.length)
307
+ throw new NotSupportedError(`Holding keys through a ${what} (modifiers)`, `Leave out modifiers; for a shortcut, keyboard.press(key, modifiers) or keyboard.hotkey("ctrl+a").`);
308
+ }
251
309
  function buttonOf(button) {
252
310
  if (button === "left" || button === "middle" || button === "right")
253
311
  return button;
@@ -46,7 +46,13 @@ export type CreateSandboxBaseParams = {
46
46
  autoArchiveInterval?: number;
47
47
  autoDeleteInterval?: number;
48
48
  ttlMinutes?: number;
49
+ /** Minutes the create may wait for room before it fails with
50
+ * DaytonaQueueTimeoutError: Runtime's own wait for capacity, which is two
51
+ * minutes when not given. */
52
+ queueTimeout?: number;
49
53
  volumes?: VolumeMount[];
54
+ /** Refused: a Runtime sandbox has no /dev/kvm. */
55
+ kvm?: boolean;
50
56
  networkBlockAll?: boolean;
51
57
  networkAllowList?: string;
52
58
  domainAllowList?: string;
@@ -1,5 +1,6 @@
1
1
  import { clientFor, } from "./client.js";
2
- import { DaytonaError, DaytonaNotFoundError, guard, NotSupportedError } from "./errors.js";
2
+ import { DaytonaError, DaytonaInvalidArgumentError, DaytonaNotFoundError, DaytonaQueueTimeoutError, guard, NotSupportedError, } from "./errors.js";
3
+ import { RuntimeError } from "../errors.js";
3
4
  import { LEASE_MAX_SECONDS } from "../compat/lease.js";
4
5
  import { idlePauseOf, idleSeconds, networkRules, notFound, retentionDays, Sandbox, windowSeconds, } from "./sandbox.js";
5
6
  /* Daytona's client over Runtime's. `new Daytona()` then `daytona.create()`
@@ -186,6 +187,9 @@ export class Daytona {
186
187
  * is left to Runtime: the included usage first, then prepaid credit, exactly as withruntime's own create. */
187
188
  async create(params = {}, options = {}) {
188
189
  refuseCreate(params);
190
+ const timeout = options.timeout;
191
+ if (timeout !== undefined && timeout !== null && !(timeout >= 0))
192
+ throw new DaytonaInvalidArgumentError("Timeout must be a non-negative number");
189
193
  const client = params.withruntime?.client ?? this.#client;
190
194
  const lifecycle = lifecycleOf(params);
191
195
  const resources = { ...DEFAULT_RESOURCES, ...params.resources };
@@ -229,7 +233,25 @@ export class Daytona {
229
233
  ...this.#create,
230
234
  ...params.withruntime?.create,
231
235
  };
232
- const runtime = await guard("sandbox", () => client.sandboxes.create(input));
236
+ // Daytona's timeout bounds the create and its wait to run (0: none);
237
+ // queueTimeout bounds the wait for room, which Runtime counts apart.
238
+ let refused;
239
+ const runtime = await guard("sandbox", () => client.sandboxes.create(input, {
240
+ ...(timeout ? { timeoutMs: timeout * 1000 } : {}),
241
+ ...(params.queueTimeout !== undefined
242
+ ? {
243
+ waitForCapacityMs: params.queueTimeout * 60_000,
244
+ onCapacityWait: (refusal) => {
245
+ refused = refusal.code;
246
+ },
247
+ }
248
+ : {}),
249
+ })).catch((error) => {
250
+ const cause = error.cause;
251
+ if (refused !== undefined && cause instanceof RuntimeError && cause.code === refused)
252
+ throw new DaytonaQueueTimeoutError(`No room for the sandbox within its queueTimeout of ${params.queueTimeout} minutes. ${cause.message}`, cause.status, undefined, cause.code);
253
+ throw error;
254
+ });
233
255
  if (user)
234
256
  await asUser(runtime, user);
235
257
  if (lifecycle.autoDeleteInterval > 0)
@@ -279,7 +301,7 @@ export class Daytona {
279
301
  return { snapshot: snapshot.id };
280
302
  throw new DaytonaNotFoundError(`No Runtime image or snapshot is named "${name}". Daytona snapshots do not move to Runtime; build the same ` +
281
303
  `environment as a Runtime image with that name: daytona.snapshot.create({ name: "${name}", image }) with this ` +
282
- `package, or \`npx withruntime image build --dockerfile Dockerfile --name ${imageName(name)}\`.`, 404);
304
+ `package, or \`npx withruntime images build --dockerfile Dockerfile --name ${imageName(name)}\`.`, 404);
283
305
  }
284
306
  async #volumes(client, mounts) {
285
307
  const out = [];
@@ -413,6 +435,11 @@ function refuseCreate(params) {
413
435
  throw new NotSupportedError("GPUs", "Runtime runs sandboxes on CPUs; remove gpu and gpuType from resources.");
414
436
  if (params.volumes?.some((mount) => mount.subpath))
415
437
  throw new NotSupportedError("Mounting part of a volume (subpath)", "Mount the whole volume.");
438
+ if (params.kvm)
439
+ throw new NotSupportedError("Nested virtualization (kvm)", "Remove kvm: a Runtime sandbox is a microVM without /dev/kvm. Run the workload directly in the sandbox, or in a container with `sudo enable-docker`.");
440
+ if (params.queueTimeout !== undefined &&
441
+ (!Number.isInteger(params.queueTimeout) || params.queueTimeout < 1))
442
+ throw new DaytonaInvalidArgumentError("queueTimeout must be a positive integer");
416
443
  const refusals = [
417
444
  ["spot", "Spot GPU sandboxes (spot)", "Remove it: Runtime runs sandboxes on CPUs."],
418
445
  [
@@ -20,7 +20,11 @@ export declare class DaytonaForbiddenError extends DaytonaError {
20
20
  }
21
21
  export declare class DaytonaNotFoundError extends DaytonaError {
22
22
  }
23
+ /** A 408 or 504. As in Daytona, `instanceof DaytonaTimeoutError` also
24
+ * matches a DaytonaConnectionTimeoutError, the class a call's own deadline
25
+ * raises, though that one sits under DaytonaConnectionError. */
23
26
  export declare class DaytonaTimeoutError extends DaytonaError {
27
+ static [Symbol.hasInstance](value: unknown): boolean;
24
28
  }
25
29
  export declare class DaytonaConflictError extends DaytonaError {
26
30
  }
@@ -46,6 +50,13 @@ export declare class DaytonaConnectionError extends DaytonaError {
46
50
  }
47
51
  export declare class DaytonaConnectionTimeoutError extends DaytonaConnectionError {
48
52
  }
53
+ /** The create waited its queueTimeout for room and none came. */
54
+ export declare class DaytonaQueueTimeoutError extends DaytonaTimeoutError {
55
+ }
56
+ /** Daytona's spot preemption; Runtime has no spot sandboxes, so it never
57
+ * comes, and exists for code that names it. */
58
+ export declare class DaytonaSpotEvictedError extends DaytonaError {
59
+ }
49
60
  export declare class DaytonaGitAuthFailedError extends DaytonaAuthenticationError {
50
61
  }
51
62
  export declare class DaytonaGitRepoNotFoundError extends DaytonaNotFoundError {
@@ -38,7 +38,16 @@ export class DaytonaForbiddenError extends DaytonaError {
38
38
  }
39
39
  export class DaytonaNotFoundError extends DaytonaError {
40
40
  }
41
+ /** A 408 or 504. As in Daytona, `instanceof DaytonaTimeoutError` also
42
+ * matches a DaytonaConnectionTimeoutError, the class a call's own deadline
43
+ * raises, though that one sits under DaytonaConnectionError. */
41
44
  export class DaytonaTimeoutError extends DaytonaError {
45
+ static [Symbol.hasInstance](value) {
46
+ // Only the base class widens; its subclasses keep the prototype chain.
47
+ if (this === DaytonaTimeoutError && value instanceof DaytonaConnectionTimeoutError)
48
+ return true;
49
+ return Function.prototype[Symbol.hasInstance].call(this, value);
50
+ }
42
51
  }
43
52
  export class DaytonaConflictError extends DaytonaError {
44
53
  }
@@ -64,6 +73,13 @@ export class DaytonaConnectionError extends DaytonaError {
64
73
  }
65
74
  export class DaytonaConnectionTimeoutError extends DaytonaConnectionError {
66
75
  }
76
+ /** The create waited its queueTimeout for room and none came. */
77
+ export class DaytonaQueueTimeoutError extends DaytonaTimeoutError {
78
+ }
79
+ /** Daytona's spot preemption; Runtime has no spot sandboxes, so it never
80
+ * comes, and exists for code that names it. */
81
+ export class DaytonaSpotEvictedError extends DaytonaError {
82
+ }
67
83
  export class DaytonaGitAuthFailedError extends DaytonaAuthenticationError {
68
84
  }
69
85
  export class DaytonaGitRepoNotFoundError extends DaytonaNotFoundError {
@@ -156,6 +172,8 @@ export function translate(error, subject = "other") {
156
172
  out = new DaytonaProcessNotFoundError(message);
157
173
  else if (error.code === "command_timeout")
158
174
  out = new DaytonaProcessExecutionTimeoutError(message);
175
+ else if (error.status === 0 && error.code === "timeout")
176
+ out = new DaytonaConnectionTimeoutError(message);
159
177
  else if (error.status === 0)
160
178
  out = new DaytonaConnectionError(message);
161
179
  else
@@ -1,3 +1,4 @@
1
+ import { timeoutSignal } from "../transport.js";
1
2
  import { resolvePath, WHOLE_OUTPUT } from "./context.js";
2
3
  import { DaytonaError, DaytonaFileNotFoundError, guard } from "./errors.js";
3
4
  const PERMISSIONS = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"];
@@ -5,7 +6,7 @@ function downloadRequest(timeout, signal) {
5
6
  if (!Number.isFinite(timeout) || timeout < 0)
6
7
  throw new DaytonaError("Download timeout must be a nonnegative finite number.");
7
8
  signal?.throwIfAborted();
8
- const deadline = timeout > 0 ? AbortSignal.timeout(Math.ceil(timeout * 1000)) : undefined;
9
+ const deadline = timeout > 0 ? timeoutSignal(timeout * 1000) : undefined;
9
10
  return {
10
11
  timeoutMs: 0,
11
12
  ...(deadline || signal
@@ -6,6 +6,14 @@ export type ContextFile = {
6
6
  mode?: number;
7
7
  };
8
8
  declare const PYTHON: readonly ["3.9", "3.10", "3.11", "3.12", "3.13"];
9
+ /** Daytona's pip options, as pip flags. */
10
+ export type PipInstallOptions = {
11
+ indexUrl?: string;
12
+ extraIndexUrls?: string[];
13
+ findLinks?: string[];
14
+ pre?: boolean;
15
+ extraOptions?: string;
16
+ };
9
17
  /** Daytona's declarative image, kept as the Dockerfile it stands for and
10
18
  * built as a Runtime image (`runtime.images.build`) when a sandbox or a
11
19
  * snapshot is created from it. Local files go with the build as its context. */
@@ -18,14 +26,8 @@ export declare class Image {
18
26
  static debianSlim(pythonVersion?: (typeof PYTHON)[number]): Image;
19
27
  /** A local Dockerfile; files it copies come from its directory. */
20
28
  static fromDockerfile(path: string): Image;
21
- pipInstall(packages: string | string[], options?: {
22
- indexUrl?: string;
23
- extraIndexUrls?: string[];
24
- findLinks?: string[];
25
- pre?: boolean;
26
- extraOptions?: string;
27
- }): Image;
28
- pipInstallFromRequirements(requirementsTxt: string): Image;
29
+ pipInstall(packages: string | string[], options?: PipInstallOptions): Image;
30
+ pipInstallFromRequirements(requirementsTxt: string, options?: PipInstallOptions): Image;
29
31
  pipInstallFromPyproject(): Image;
30
32
  addLocalFile(localPath: string, remotePath: string): Image;
31
33
  addLocalDir(localPath: string, remotePath: string): Image;
@@ -34,7 +36,9 @@ export declare class Image {
34
36
  workdir(dirPath: string): Image;
35
37
  entrypoint(entrypointCommands: string[]): Image;
36
38
  cmd(cmd: string[]): Image;
37
- dockerfileCommands(dockerfileCommands: string[]): Image;
39
+ dockerfileCommands(dockerfileCommands: string[], contextDir?: string, options?: {
40
+ strictContext?: boolean;
41
+ }): Image;
38
42
  /** The Dockerfile and its context files, read from the local disk. */
39
43
  build(): Promise<{
40
44
  dockerfile: string;
@@ -1,6 +1,15 @@
1
1
  import { createHash } from "node:crypto";
2
- import { NotSupportedError } from "./errors.js";
2
+ import { DaytonaInvalidArgumentError, NotSupportedError } from "./errors.js";
3
3
  const PYTHON = ["3.9", "3.10", "3.11", "3.12", "3.13"];
4
+ function pipFlags(options) {
5
+ return [
6
+ ...(options.indexUrl ? [`--index-url ${options.indexUrl}`] : []),
7
+ ...(options.extraIndexUrls ?? []).map((url) => `--extra-index-url ${url}`),
8
+ ...(options.findLinks ?? []).map((url) => `--find-links ${url}`),
9
+ ...(options.pre ? ["--pre"] : []),
10
+ ...(options.extraOptions ? [options.extraOptions] : []),
11
+ ].join(" ");
12
+ }
4
13
  /** Daytona's declarative image, kept as the Dockerfile it stands for and
5
14
  * built as a Runtime image (`runtime.images.build`) when a sandbox or a
6
15
  * snapshot is created from it. Local files go with the build as its context. */
@@ -37,20 +46,13 @@ export class Image {
37
46
  const list = [packages].flat();
38
47
  if (!list.length)
39
48
  return this;
40
- const flags = [
41
- ...(options.indexUrl ? [`--index-url ${options.indexUrl}`] : []),
42
- ...(options.extraIndexUrls ?? []).map((url) => `--extra-index-url ${url}`),
43
- ...(options.findLinks ?? []).map((url) => `--find-links ${url}`),
44
- ...(options.pre ? ["--pre"] : []),
45
- ...(options.extraOptions ? [options.extraOptions] : []),
46
- ];
47
- this.#lines.push(`RUN python -m pip install ${list.map((one) => JSON.stringify(one)).join(" ")} ${flags.join(" ")}`.trim());
49
+ this.#lines.push(`RUN python -m pip install ${list.map((one) => JSON.stringify(one)).join(" ")} ${pipFlags(options)}`.trim());
48
50
  return this;
49
51
  }
50
- pipInstallFromRequirements(requirementsTxt) {
52
+ pipInstallFromRequirements(requirementsTxt, options = {}) {
51
53
  const remote = `/.requirements/${createHash("sha256").update(requirementsTxt).digest("hex").slice(0, 12)}.txt`;
52
54
  this.#files.push({ local: requirementsTxt, remote, dir: false });
53
- this.#lines.push(`COPY ${remote.slice(1)} ${remote}`, `RUN python -m pip install -r ${remote}`);
55
+ this.#lines.push(`COPY ${remote.slice(1)} ${remote}`, `RUN python -m pip install -r ${remote} ${pipFlags(options)}`.trim());
54
56
  return this;
55
57
  }
56
58
  pipInstallFromPyproject() {
@@ -90,7 +92,11 @@ export class Image {
90
92
  this.#lines.push(`CMD ${JSON.stringify(cmd)}`);
91
93
  return this;
92
94
  }
93
- dockerfileCommands(dockerfileCommands) {
95
+ dockerfileCommands(dockerfileCommands, contextDir, options = {}) {
96
+ if (options.strictContext && !contextDir)
97
+ throw new DaytonaInvalidArgumentError("strictContext requires contextDir so that the build context boundary is explicit");
98
+ if (contextDir)
99
+ throw new NotSupportedError("A build context directory for dockerfileCommands (contextDir)", "Add the files with addLocalDir(contextDir, remotePath) or addLocalFile, then COPY from where they land, or build from a Dockerfile with Image.fromDockerfile(path).");
94
100
  this.#lines.push(...dockerfileCommands);
95
101
  return this;
96
102
  }
@@ -4,7 +4,7 @@ export { Process, CodeRunParams, PtyHandle, type PtyConnectOptions, type PtyCrea
4
4
  export { FileSystem, type FileDownloadRequest, type FileDownloadResponse, type FileInfo, type FilePermissionsParams, type FileUpload, type Match, type ReplaceResult, type SearchFilesResponse, type UploadSource, type UploadProgress, type UploadStreamOptions, } from "./filesystem.js";
5
5
  export { Git, type GitStatus, type ListBranchResponse } from "./git.js";
6
6
  export { Accessibility, ComputerUse, Display, Keyboard, Mouse, RecordingService, Screenshot, type ComputerUseStartResponse, type ComputerUseStatusResponse, type ComputerUseStopResponse, type DisplayInfo, type ListRecordingsResponse, type Recording, type ScreenshotOptions, type ScreenshotRegion, type ScreenshotResponse, type WindowInfo, } from "./computer-use.js";
7
- export { Image } from "./image.js";
7
+ export { Image, type PipInstallOptions } from "./image.js";
8
8
  export { resolvePath } from "./context.js";
9
9
  export type { DaytonaConfig, WithRuntime } from "./client.js";
10
10
  export * from "./errors.js";
@@ -87,7 +87,8 @@ export declare class Sandbox {
87
87
  readonly git: Git;
88
88
  readonly codeInterpreter: CodeInterpreter;
89
89
  env: Record<string, string>;
90
- readonly public: boolean;
90
+ /** Whether its previews are public; setPublic changes it. */
91
+ public: boolean;
91
92
  readonly snapshot: string | undefined;
92
93
  /** Use daytona.create or daytona.get. */
93
94
  constructor(runtime: RuntimeSandbox, client: Runtime, options: SandboxOptions);
@@ -154,8 +155,13 @@ export declare class Sandbox {
154
155
  networkAllowList?: string;
155
156
  domainAllowList?: string;
156
157
  }): Promise<void>;
158
+ /** Makes every port it shares public, or private with a token, and the
159
+ * ports getPreviewLink shares from now on. A port already shared changes
160
+ * at once, keeping its address, as on Daytona. Without credit, public is
161
+ * refused (public_preview_not_allowed) and nothing changes. */
162
+ setPublic(isPublic: boolean): Promise<void>;
157
163
  /** A preview address for `port`: public when the sandbox was created with
158
- * `public: true`, else private with a token. */
164
+ * `public: true` or setPublic(true) since, else private with a token. */
159
165
  getPreviewLink(port: number): Promise<PortPreviewUrl>;
160
166
  getUserHomeDir(): Promise<string>;
161
167
  getUserRootDir(): Promise<string>;
@@ -90,6 +90,7 @@ export class Sandbox {
90
90
  git;
91
91
  codeInterpreter;
92
92
  env;
93
+ /** Whether its previews are public; setPublic changes it. */
93
94
  public;
94
95
  snapshot;
95
96
  #rt;
@@ -349,8 +350,21 @@ export class Sandbox {
349
350
  const runtime = await this.#live();
350
351
  await guard("sandbox", () => runtime.network.set(rules ?? { internet: true }));
351
352
  }
353
+ /** Makes every port it shares public, or private with a token, and the
354
+ * ports getPreviewLink shares from now on. A port already shared changes
355
+ * at once, keeping its address, as on Daytona. Without credit, public is
356
+ * refused (public_preview_not_allowed) and nothing changes. */
357
+ async setPublic(isPublic) {
358
+ const visibility = isPublic ? "public" : "private";
359
+ const runtime = await this.#live();
360
+ const shared = await guard("sandbox", () => runtime.previews.list());
361
+ for (const preview of shared)
362
+ if (preview.visibility !== visibility)
363
+ await guard("sandbox", () => runtime.previews.create(preview.port, { visibility }));
364
+ this.public = isPublic;
365
+ }
352
366
  /** A preview address for `port`: public when the sandbox was created with
353
- * `public: true`, else private with a token. */
367
+ * `public: true` or setPublic(true) since, else private with a token. */
354
368
  async getPreviewLink(port) {
355
369
  const runtime = await this.#live();
356
370
  const preview = await guard("sandbox", () => runtime.previews.create(port, { visibility: this.public ? "public" : "private" }));
@@ -440,7 +454,7 @@ export class Sandbox {
440
454
  return refusal("Signed download URLs", "Use sandbox.fs.downloadFile(path).");
441
455
  }
442
456
  createSshAccess() {
443
- return refusal("SSH access", "Use `npx withruntime sandbox ssh <id>` (also VS Code and JetBrains) or sandbox.withruntime.terminal().");
457
+ return refusal("SSH access", "Use `npx withruntime sandboxes ssh <id>` (also VS Code and JetBrains) or sandbox.withruntime.terminal().");
444
458
  }
445
459
  revokeSshAccess() {
446
460
  return this.createSshAccess();
@@ -0,0 +1,9 @@
1
+ /** `withruntime/domains`: Domains alone. Your own hostname for a sandbox's port, with HTTPS.
2
+ *
3
+ * import { domains } from "withruntime/domains";
4
+ * const client = domains(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime, type RuntimeOptions } from "../client.js";
7
+ export type { Domain, CheckedDomain, DnsRecord } from "../products/network-products.js";
8
+ /** `runtime.domains` of a client made with `options`. */
9
+ export declare function domains(options?: RuntimeOptions): Runtime["domains"];
@@ -0,0 +1,10 @@
1
+ /** `withruntime/domains`: Domains alone. Your own hostname for a sandbox's port, with HTTPS.
2
+ *
3
+ * import { domains } from "withruntime/domains";
4
+ * const client = domains(); // RUNTIME_API_KEY, or this machine's saved connection
5
+ */
6
+ import { Runtime } from "../client.js";
7
+ /** `runtime.domains` of a client made with `options`. */
8
+ export function domains(options) {
9
+ return new Runtime(options).domains;
10
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Process } from "../sandbox.js";
2
2
  import type { OutputEvent, ProcessInfo as RuntimeProcessInfo } from "../types.js";
3
- import type { RequestOptions } from "../transport.js";
3
+ import { type RequestOptions } from "../transport.js";
4
4
  import type { RuntimeSandbox } from "./client.js";
5
5
  import { TimeoutError, type CommandResult } from "./errors.js";
6
6
  export type { CommandResult };