withruntime 0.9.0 → 0.11.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 (124) hide show
  1. package/BLAXEL.md +4 -4
  2. package/DAYTONA.md +52 -40
  3. package/E2B.md +65 -62
  4. package/README.md +15 -7
  5. package/README.src.md +15 -7
  6. package/VERCEL.md +34 -22
  7. package/compatibility-lock.json +16 -16
  8. package/dist/blaxel/errors.js +1 -1
  9. package/dist/blaxel/lookup.d.ts +3 -2
  10. package/dist/blaxel/lookup.js +3 -2
  11. package/dist/blaxel/pagination.d.ts +2 -2
  12. package/dist/blaxel/pagination.js +45 -17
  13. package/dist/blaxel/preview.d.ts +2 -1
  14. package/dist/blaxel/preview.js +15 -13
  15. package/dist/blaxel/process.js +56 -10
  16. package/dist/blaxel/sandbox.d.ts +2 -0
  17. package/dist/blaxel/sandbox.js +2 -0
  18. package/dist/blaxel/snapshot.js +3 -5
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +262 -56
  21. package/dist/client.d.ts +41 -3
  22. package/dist/client.js +67 -11
  23. package/dist/cloudflare/identity.d.ts +7 -0
  24. package/dist/cloudflare/identity.js +13 -0
  25. package/dist/cloudflare/index.d.ts +9 -0
  26. package/dist/cloudflare/index.js +47 -9
  27. package/dist/cloudflare/worker.d.ts +4 -0
  28. package/dist/cloudflare/worker.js +166 -16
  29. package/dist/codesandbox/guest-service.js +57 -20
  30. package/dist/codesandbox/index.js +42 -8
  31. package/dist/compat/core.d.ts +9 -8
  32. package/dist/compat/core.js +60 -21
  33. package/dist/compat/files.js +22 -4
  34. package/dist/compat/lease.d.ts +36 -0
  35. package/dist/compat/lease.js +87 -0
  36. package/dist/credentials.d.ts +6 -1
  37. package/dist/credentials.js +107 -38
  38. package/dist/daytona/computer-use.d.ts +162 -0
  39. package/dist/daytona/computer-use.js +324 -0
  40. package/dist/daytona/context.d.ts +10 -0
  41. package/dist/daytona/context.js +24 -0
  42. package/dist/daytona/daytona.d.ts +20 -0
  43. package/dist/daytona/daytona.js +134 -20
  44. package/dist/daytona/filesystem.d.ts +17 -4
  45. package/dist/daytona/filesystem.js +129 -38
  46. package/dist/daytona/index.d.ts +3 -27
  47. package/dist/daytona/index.js +2 -7
  48. package/dist/daytona/process.d.ts +63 -6
  49. package/dist/daytona/process.js +224 -23
  50. package/dist/daytona/sandbox.d.ts +20 -6
  51. package/dist/daytona/sandbox.js +55 -32
  52. package/dist/e2b/client.d.ts +5 -0
  53. package/dist/e2b/client.js +6 -2
  54. package/dist/e2b/commands.d.ts +43 -12
  55. package/dist/e2b/commands.js +217 -64
  56. package/dist/e2b/errors.d.ts +7 -0
  57. package/dist/e2b/errors.js +20 -0
  58. package/dist/e2b/filesystem.d.ts +2 -1
  59. package/dist/e2b/filesystem.js +43 -6
  60. package/dist/e2b/index.d.ts +3 -2
  61. package/dist/e2b/index.js +2 -1
  62. package/dist/e2b/pty.d.ts +24 -0
  63. package/dist/e2b/pty.js +83 -0
  64. package/dist/e2b/sandbox.d.ts +17 -6
  65. package/dist/e2b/sandbox.js +239 -43
  66. package/dist/e2b/users.d.ts +30 -0
  67. package/dist/e2b/users.js +185 -0
  68. package/dist/file-source.d.ts +16 -0
  69. package/dist/file-source.js +162 -0
  70. package/dist/freestyle/index.d.ts +9 -1
  71. package/dist/freestyle/index.js +38 -5
  72. package/dist/index.d.ts +3 -1
  73. package/dist/login.js +16 -10
  74. package/dist/mcp.d.ts +2 -1
  75. package/dist/mcp.js +6 -1
  76. package/dist/modal/index.js +2 -1
  77. package/dist/network-cli.js +139 -43
  78. package/dist/openai-agents/index.js +9 -45
  79. package/dist/page.d.ts +1 -1
  80. package/dist/page.js +2 -2
  81. package/dist/products/browser.d.ts +60 -0
  82. package/dist/products/browser.js +37 -0
  83. package/dist/products/desktop.d.ts +2 -2
  84. package/dist/products/desktop.js +2 -2
  85. package/dist/products/images.d.ts +3 -1
  86. package/dist/products/images.js +14 -2
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/network-products.d.ts +7 -0
  90. package/dist/products/network-products.js +3 -0
  91. package/dist/products/observability.d.ts +44 -0
  92. package/dist/products/observability.js +6 -0
  93. package/dist/products/previews.d.ts +8 -2
  94. package/dist/products/previews.js +11 -3
  95. package/dist/products/private-network.d.ts +22 -0
  96. package/dist/products/private-network.js +10 -0
  97. package/dist/products/volumes.d.ts +42 -0
  98. package/dist/products/volumes.js +36 -0
  99. package/dist/products/watch.js +61 -8
  100. package/dist/proxy.js +3 -1
  101. package/dist/runloop/index.d.ts +13 -27
  102. package/dist/runloop/index.js +149 -52
  103. package/dist/sandbox.d.ts +59 -8
  104. package/dist/sandbox.js +479 -68
  105. package/dist/snapshots.d.ts +9 -4
  106. package/dist/sprites/index.js +69 -33
  107. package/dist/tar.d.ts +3 -1
  108. package/dist/tar.js +140 -14
  109. package/dist/transport.d.ts +4 -7
  110. package/dist/transport.js +205 -122
  111. package/dist/types.d.ts +5 -2
  112. package/dist/vercel/command.d.ts +1 -1
  113. package/dist/vercel/command.js +3 -2
  114. package/dist/vercel/errors.d.ts +1 -1
  115. package/dist/vercel/errors.js +7 -1
  116. package/dist/vercel/index.d.ts +1 -7
  117. package/dist/vercel/index.js +1 -3
  118. package/dist/vercel/sandbox-user.d.ts +70 -0
  119. package/dist/vercel/sandbox-user.js +206 -0
  120. package/dist/vercel/sandbox.d.ts +55 -20
  121. package/dist/vercel/sandbox.js +353 -99
  122. package/dist/vercel/snapshot.d.ts +3 -2
  123. package/dist/vercel/snapshot.js +9 -5
  124. package/package.json +2 -1
@@ -1,4 +1,7 @@
1
- import { chmod, writeFile } from "node:fs/promises";
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { lstat, open, rename, unlink } from "node:fs/promises";
4
+ import { basename, dirname, join } from "node:path";
2
5
  import { RuntimeError } from "./errors.js";
3
6
  import { named } from "./cli-name.js";
4
7
  import { generateWireGuardKeyPair } from "./wireguard.js";
@@ -67,6 +70,12 @@ export const NETWORK_HELP = {
67
70
  upstream-proxy remove Connections leave directly again
68
71
  A proxy that refuses or cannot be reached fails the connection; nothing goes
69
72
  around it. Paid accounts only.
73
+
74
+ private on Your sandboxes reach each other by name, over TCP:
75
+ <name>.sandbox.internal:<port> or <id>.sandbox.internal:<port>
76
+ private off They stop; open connections between them are cut
77
+ private status On or off
78
+ Only your own sandboxes answer. Paid accounts only, and free.
70
79
  `,
71
80
  };
72
81
  function usage(product, message) {
@@ -92,15 +101,63 @@ function portNumber(product, text) {
92
101
  throw usage(product, "Give a port from 1 to 65535.");
93
102
  return value;
94
103
  }
104
+ /** A fresh private inode prevents disclosure through permissive files, symlinks
105
+ * and hard links. Rename publishes complete contents without a readable window. */
106
+ async function privateOutput(path, text) {
107
+ const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
108
+ try {
109
+ const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
110
+ try {
111
+ await handle.writeFile(text);
112
+ await handle.sync();
113
+ }
114
+ finally {
115
+ await handle.close();
116
+ }
117
+ return temporary;
118
+ }
119
+ catch (error) {
120
+ await unlink(temporary).catch(() => undefined);
121
+ throw error;
122
+ }
123
+ }
124
+ /** Check and stage before changing a remote peer. If the request or final write
125
+ * fails, this private draft preserves the newly generated key for recovery. */
126
+ async function prepareOutput(path, privateKey) {
127
+ try {
128
+ const info = await lstat(path);
129
+ if (!info.isFile() || info.isSymbolicLink())
130
+ throw usage("tunnel", "The output must be a regular file, not a directory or symbolic link.");
131
+ }
132
+ catch (error) {
133
+ if (error.code !== "ENOENT")
134
+ throw error;
135
+ }
136
+ const recovery = await privateOutput(path, `# DRAFT: request or output incomplete. Keep this file for recovery.\n[Interface]\nPrivateKey = ${privateKey ?? "<your private key>"}\n`);
137
+ return {
138
+ recovery,
139
+ async publish(config) {
140
+ const temporary = await privateOutput(path, config);
141
+ try {
142
+ await rename(temporary, path);
143
+ }
144
+ finally {
145
+ await unlink(temporary).catch(() => undefined);
146
+ }
147
+ await unlink(recovery);
148
+ },
149
+ };
150
+ }
95
151
  /** Writes a wg-quick file readable by its owner alone, or prints it. */
96
- async function deliver(out, config, path, hint, ready = true) {
152
+ async function deliver(out, config, path, hint, ready = true, prepared) {
97
153
  if (!config) {
98
154
  out.error(hint);
99
155
  return;
100
156
  }
101
157
  if (path) {
102
- await writeFile(path, config, { mode: 0o600 });
103
- await chmod(path, 0o600);
158
+ if (!prepared)
159
+ throw new Error("Tunnel output was not prepared.");
160
+ await prepared.publish(config);
104
161
  out.error(ready
105
162
  ? `Wrote ${path}. Bring it up with: sudo wg-quick up ${path.startsWith("/") ? path : `./${path}`}`
106
163
  : `Wrote ${path}. ${hint}`);
@@ -276,52 +333,69 @@ export async function networkProductCommand(product, argv, args, out, client) {
276
333
  case "peers": {
277
334
  const action = rest[0];
278
335
  if (action === "add" || action === "rotate") {
336
+ const argument = need(rest[1], action === "add" ? "the peer's name, such as office" : "a peer id");
279
337
  // The pair is made here unless a key is given: the private key never
280
338
  // leaves this machine, and goes only into the file written below.
281
339
  const given = flag("public-key");
282
340
  const pair = given ? undefined : generateWireGuardKeyPair();
283
341
  const publicKey = given ?? pair.publicKey;
284
- const answer = action === "add"
285
- ? await runtime.tunnel.addPeer({
286
- name: need(rest[1], "the peer's name, such as office"),
287
- publicKey,
288
- routes: args.flags.get("route") ?? [],
289
- })
290
- : await runtime.tunnel.rotatePeer(need(rest[1], "a peer id"), { publicKey });
291
- const configReady = answer.configReady ?? !!answer.config;
292
- const draftHint = "Keep this file: it preserves your private key. Read runtime tunnel get when the gateway is ready, replace PublicKey in [Peer] with gatewayPublicKey, then run wg-quick up.";
293
- // Older servers returned no config while the gateway was starting.
294
- // Preserve the locally generated key in a draft rather than discard it.
295
- const supplied = answer.config ??
296
- [
297
- "# DRAFT: gateway key missing. Keep this file; it preserves your private key.",
298
- "[Interface]",
299
- `PrivateKey = ${pair?.privateKey ?? "<your private key>"}`,
300
- `Address = ${answer.peer.address}/32`,
301
- "",
302
- "[Peer]",
303
- `PublicKey = ${answer.tunnel.gatewayPublicKey ?? "<gatewayPublicKey from runtime tunnel get>"}`,
304
- `Endpoint = ${answer.tunnel.endpoint}`,
305
- `AllowedIPs = ${answer.tunnel.subnet}`,
306
- "PersistentKeepalive = 25",
307
- "",
308
- ].join("\n");
309
- const config = pair
310
- ? supplied.replace(/^PrivateKey = .*$/m, `PrivateKey = ${pair.privateKey}`)
311
- : supplied;
312
- if (out.json) {
313
- print("", {
314
- ...answer,
315
- config,
316
- configReady,
317
- ...(!configReady ? { hint: draftHint } : {}),
318
- });
342
+ const path = out.json ? undefined : (flag("out") ?? (pair ? "runtime.conf" : undefined));
343
+ const prepared = path ? await prepareOutput(path, pair?.privateKey) : undefined;
344
+ try {
345
+ const answer = action === "add"
346
+ ? await runtime.tunnel.addPeer({
347
+ name: argument,
348
+ publicKey,
349
+ routes: args.flags.get("route") ?? [],
350
+ })
351
+ : await runtime.tunnel.rotatePeer(argument, { publicKey });
352
+ const configReady = answer.configReady ?? !!answer.config;
353
+ const draftHint = "Keep this file: it preserves your private key. Read runtime tunnel get when the gateway is ready, replace PublicKey in [Peer] with gatewayPublicKey, then run wg-quick up.";
354
+ // Older servers returned no config while the gateway was starting.
355
+ // Preserve the locally generated key in a draft rather than discard it.
356
+ const supplied = answer.config ??
357
+ [
358
+ "# DRAFT: gateway key missing. Keep this file; it preserves your private key.",
359
+ "[Interface]",
360
+ `PrivateKey = ${pair?.privateKey ?? "<your private key>"}`,
361
+ `Address = ${answer.peer.address}/32`,
362
+ "",
363
+ "[Peer]",
364
+ `PublicKey = ${answer.tunnel.gatewayPublicKey ?? "<gatewayPublicKey from runtime tunnel get>"}`,
365
+ `Endpoint = ${answer.tunnel.endpoint}`,
366
+ `AllowedIPs = ${answer.tunnel.subnet}`,
367
+ "PersistentKeepalive = 25",
368
+ "",
369
+ ].join("\n");
370
+ const privateKeyRow = /^[ \t]*PrivateKey[ \t]*=[^\r\n]*$/gm;
371
+ if (pair && (supplied.match(privateKeyRow)?.length ?? 0) !== 1)
372
+ throw new RuntimeError({
373
+ code: "invalid_response",
374
+ status: 0,
375
+ message: "Runtime returned a tunnel configuration without exactly one private-key field.",
376
+ });
377
+ const config = pair
378
+ ? supplied.replace(privateKeyRow, `PrivateKey = ${pair.privateKey}`)
379
+ : supplied;
380
+ if (out.json) {
381
+ print("", {
382
+ ...answer,
383
+ config,
384
+ configReady,
385
+ ...(!configReady ? { hint: draftHint } : {}),
386
+ });
387
+ return 0;
388
+ }
389
+ if (answer.tunnel.funded === false)
390
+ out.error(fundingState(answer.tunnel));
391
+ await deliver(out, config, path, configReady ? answer.hint : draftHint, configReady, prepared);
319
392
  return 0;
320
393
  }
321
- if (answer.tunnel.funded === false)
322
- out.error(fundingState(answer.tunnel));
323
- await deliver(out, config, flag("out") ?? (pair ? "runtime.conf" : undefined), configReady ? answer.hint : draftHint, configReady);
324
- return 0;
394
+ catch (error) {
395
+ if (prepared)
396
+ out.error(`The peer request or output did not finish. Your private draft is saved at ${prepared.recovery}; keep it for recovery.`);
397
+ throw error;
398
+ }
325
399
  }
326
400
  if (action === "rm" || action === "remove") {
327
401
  const after = await runtime.tunnel.removePeer(need(rest[1], "a peer id"));
@@ -332,6 +406,28 @@ export async function networkProductCommand(product, argv, args, out, client) {
332
406
  }
333
407
  }
334
408
  }
409
+ if (product === "network" && verb === "private") {
410
+ const describe = (state) => state.enabled
411
+ ? "On: your sandboxes reach each other at <name>.sandbox.internal:<port> over TCP."
412
+ : state.allowed
413
+ ? "Off. Turn it on: runtime network private on"
414
+ : "Off. Reaching your sandboxes by name is for paid accounts; add credit to turn it on.";
415
+ switch (rest[0]) {
416
+ case "on":
417
+ case "off": {
418
+ const state = await runtime.network.private.set({ enabled: rest[0] === "on" });
419
+ print(state.enabled ? describe(state) : "Off. Open connections between your sandboxes are cut.", state);
420
+ return 0;
421
+ }
422
+ case "status":
423
+ case undefined: {
424
+ const state = await runtime.network.private.get();
425
+ print(describe(state), state);
426
+ return 0;
427
+ }
428
+ }
429
+ throw usage(product, "private takes on, off or status.");
430
+ }
335
431
  if (product === "network") {
336
432
  if (verb !== "upstream-proxy")
337
433
  throw usage(product, `Unknown network command ${verb}.`);
@@ -531,29 +531,11 @@ export class RuntimeCloudSandboxSession {
531
531
  // Workspace archives.
532
532
  async persistWorkspace() {
533
533
  const root = this.state.manifest.root;
534
- const staging = `${STAGING}/${crypto.randomUUID()}.tar`;
535
- const skip = root === FILES_HOME ? ["--exclude=./.openai-agents-staging"] : [];
536
- try {
537
- const packed = await this.#call((sbx) => sbx.exec([
538
- "sh",
539
- "-c",
540
- `mkdir -p -- ${STAGING} && tar ${skip.join(" ")} -C "$1" -cf - . > "$2"`,
541
- "sh",
542
- root,
543
- staging,
544
- ], {
545
- cwd: "/",
546
- timeoutMs: this.state.execTimeoutMs,
547
- }));
548
- if (packed.exitCode !== 0)
549
- throw new SandboxProviderError(`${PROVIDER} could not archive the workspace.`, {
550
- stderr: packed.stderr,
551
- });
552
- return await this.#call((sbx) => sbx.files.read(staging));
553
- }
554
- finally {
555
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
556
- }
534
+ return this.#call((sbx) => sbx.files.archive(root, {
535
+ gzip: false,
536
+ ...(root === FILES_HOME ? { exclude: [".openai-agents-staging"] } : {}),
537
+ timeoutMs: this.state.execTimeoutMs,
538
+ }));
557
539
  }
558
540
  async hydrateWorkspace(data) {
559
541
  const bytes = typeof data === "string"
@@ -561,28 +543,10 @@ export class RuntimeCloudSandboxSession {
561
543
  : data instanceof Uint8Array
562
544
  ? data
563
545
  : new Uint8Array(data);
564
- const staging = `${STAGING}/${crypto.randomUUID()}.tar`;
565
- try {
566
- await this.#call((sbx) => sbx.files.write(staging, bytes));
567
- const unpacked = await this.#call((sbx) => sbx.exec([
568
- "sh",
569
- "-c",
570
- 'mkdir -p -- "$1" && tar -C "$1" -xf "$2"',
571
- "sh",
572
- this.state.manifest.root,
573
- staging,
574
- ], {
575
- cwd: "/",
576
- timeoutMs: this.state.execTimeoutMs,
577
- }));
578
- if (unpacked.exitCode !== 0)
579
- throw new SandboxProviderError(`${PROVIDER} could not restore the workspace.`, {
580
- stderr: unpacked.stderr,
581
- });
582
- }
583
- finally {
584
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
585
- }
546
+ await this.#call((sbx) => sbx.files.unarchive(this.state.manifest.root, bytes, {
547
+ gzip: false,
548
+ timeoutMs: this.state.execTimeoutMs,
549
+ }));
586
550
  }
587
551
  // Lifecycle.
588
552
  async running() {
package/dist/page.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare class Page<T> implements AsyncIterable<T> {
5
5
  private readonly fetchPage;
6
6
  constructor(data: T[], nextCursor: string | null, fetchPage: (cursor: string) => Promise<Page<T>>);
7
7
  get hasMore(): boolean;
8
- next(): Promise<Page<T> | null>;
8
+ next(cursor?: string | null): Promise<Page<T> | null>;
9
9
  /** Every page, fetched as you go. */
10
10
  pages(): AsyncGenerator<Page<T>>;
11
11
  [Symbol.asyncIterator](): AsyncGenerator<T>;
package/dist/page.js CHANGED
@@ -11,8 +11,8 @@ export class Page {
11
11
  get hasMore() {
12
12
  return this.nextCursor !== null;
13
13
  }
14
- async next() {
15
- return this.nextCursor === null ? null : this.fetchPage(this.nextCursor);
14
+ async next(cursor = this.nextCursor) {
15
+ return cursor === null ? null : this.fetchPage(cursor);
16
16
  }
17
17
  /** Every page, fetched as you go. */
18
18
  async *pages() {
@@ -0,0 +1,60 @@
1
+ import type { Sandbox } from "../sandbox.js";
2
+ import type { RequestOptions, Transport } from "../transport.js";
3
+ export type SandboxBrowser = {
4
+ running: false;
5
+ } | {
6
+ running: true;
7
+ headless: boolean;
8
+ /** Chromium's version, e.g. "Chrome/154.0.8037.92". */
9
+ version: string;
10
+ /** The browser's DevTools WebSocket with its private token: give it as it
11
+ * is to Playwright's `chromium.connectOverCDP`, `puppeteer.connect({
12
+ * browserWSEndpoint })`, browser-use or Stagehand. */
13
+ cdpUrl: string;
14
+ /** The DevTools HTTP endpoint; send `headers` with it. */
15
+ httpUrl: string;
16
+ headers: Record<string, string>;
17
+ /** When the token in cdpUrl ends; `get()` returns a fresh one. */
18
+ expiresAt: string | null;
19
+ /** headless: false only: the desktop's live view. */
20
+ streamUrl?: string;
21
+ };
22
+ export type BrowserStartOptions = {
23
+ /** true (the default): no window. false: on the sandbox's desktop, with a live view. */
24
+ headless?: boolean;
25
+ width?: number;
26
+ height?: number;
27
+ };
28
+ /** `sbx.browser`: Chromium in the sandbox, for agents that speak CDP.
29
+ *
30
+ * const { cdpUrl } = await sbx.browser.start();
31
+ * const browser = await chromium.connectOverCDP(cdpUrl);
32
+ *
33
+ * Inside the sandbox the same browser is at http://127.0.0.1:9222. */
34
+ export declare function sandboxBrowser(t: Transport, sandbox: Sandbox): {
35
+ /** Starts Chromium and returns its CDP address. The first start in a
36
+ * sandbox installs Chromium, about a minute; this waits for it (up to 10
37
+ * minutes). Starting a running browser returns it as it is. */
38
+ start(input?: BrowserStartOptions, options?: RequestOptions): Promise<{
39
+ running: true;
40
+ headless: boolean;
41
+ /** Chromium's version, e.g. "Chrome/154.0.8037.92". */
42
+ version: string;
43
+ /** The browser's DevTools WebSocket with its private token: give it as it
44
+ * is to Playwright's `chromium.connectOverCDP`, `puppeteer.connect({
45
+ * browserWSEndpoint })`, browser-use or Stagehand. */
46
+ cdpUrl: string;
47
+ /** The DevTools HTTP endpoint; send `headers` with it. */
48
+ httpUrl: string;
49
+ headers: Record<string, string>;
50
+ /** When the token in cdpUrl ends; `get()` returns a fresh one. */
51
+ expiresAt: string | null;
52
+ /** headless: false only: the desktop's live view. */
53
+ streamUrl?: string;
54
+ }>;
55
+ /** Whether it runs, and a fresh cdpUrl when it does. */
56
+ get: (options?: RequestOptions) => Promise<SandboxBrowser>;
57
+ stop: (options?: RequestOptions) => Promise<{
58
+ stopped: boolean;
59
+ }>;
60
+ };
@@ -0,0 +1,37 @@
1
+ /** `sbx.browser`: Chromium in the sandbox, for agents that speak CDP.
2
+ *
3
+ * const { cdpUrl } = await sbx.browser.start();
4
+ * const browser = await chromium.connectOverCDP(cdpUrl);
5
+ *
6
+ * Inside the sandbox the same browser is at http://127.0.0.1:9222. */
7
+ export function sandboxBrowser(t, sandbox) {
8
+ const base = () => `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/browser`;
9
+ return {
10
+ /** Starts Chromium and returns its CDP address. The first start in a
11
+ * sandbox installs Chromium, about a minute; this waits for it (up to 10
12
+ * minutes). Starting a running browser returns it as it is. */
13
+ async start(input = {}, options) {
14
+ const deadline = Date.now() + 600_000;
15
+ for (;;) {
16
+ try {
17
+ return await t.json({
18
+ method: "POST",
19
+ path: `${base()}:start`,
20
+ body: input,
21
+ ...options,
22
+ });
23
+ }
24
+ catch (error) {
25
+ const code = error.code;
26
+ if ((code !== "browser_installing" && code !== "desktop_installing") ||
27
+ Date.now() > deadline)
28
+ throw error;
29
+ await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 10_000));
30
+ }
31
+ }
32
+ },
33
+ /** Whether it runs, and a fresh cdpUrl when it does. */
34
+ get: (options) => t.json({ method: "GET", path: base(), ...options }),
35
+ stop: (options) => t.json({ method: "POST", path: `${base()}:stop`, ...options }),
36
+ };
37
+ }
@@ -115,8 +115,8 @@ export declare function sandboxDesktop(t: Transport, sandbox: Sandbox): {
115
115
  focus: (windowId: string, options?: RequestOptions) => Promise<{
116
116
  ok: true;
117
117
  }>;
118
- /** Opens `url` in Firefox on the desktop. Right after a sandbox's first
119
- * start, Firefox may still be installing; this waits for it. */
118
+ /** Opens `url` in Chromium on the desktop. Right after a sandbox's first
119
+ * start, Chromium may still be installing; this waits for it. */
120
120
  open(url: string, options?: RequestOptions): Promise<{
121
121
  ok: true;
122
122
  }>;
@@ -50,8 +50,8 @@ export function sandboxDesktop(t, sandbox) {
50
50
  cursor: (options) => act({ action: "cursor" }, options),
51
51
  windows: async (options) => (await act({ action: "windows" }, options)).windows,
52
52
  focus: (windowId, options) => act({ action: "focus", windowId }, options),
53
- /** Opens `url` in Firefox on the desktop. Right after a sandbox's first
54
- * start, Firefox may still be installing; this waits for it. */
53
+ /** Opens `url` in Chromium on the desktop. Right after a sandbox's first
54
+ * start, Chromium may still be installing; this waits for it. */
55
55
  async open(url, options) {
56
56
  const deadline = Date.now() + 600_000;
57
57
  for (;;) {
@@ -212,7 +212,9 @@ export declare function images(t: Transport): {
212
212
  /** Point `tag` of the image's name at this version. */
213
213
  tag(ref: string, tag: string, options?: RequestOptions): Promise<Image>;
214
214
  untag(ref: string, tag: string, options?: RequestOptions): Promise<Image>;
215
- /** Delete one version (by id, name:tag or name@version) and its tags. */
215
+ /** Delete one version (by id, name:tag or name@version) and its tags. A
216
+ * bare name deletes its latest tag's version, or its only version when
217
+ * it has one and no latest tag; with several, the error names its tags. */
216
218
  delete(ref: string, options?: RequestOptions): Promise<Image>;
217
219
  /** Credentials for private registries. The secret is sealed on arrival
218
220
  * and never returned. */
@@ -1,3 +1,4 @@
1
+ import { RuntimeError } from "../errors.js";
1
2
  import { Page } from "../page.js";
2
3
  const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
3
4
  const PARALLEL_UPLOADS = 4;
@@ -189,11 +190,22 @@ export function images(t) {
189
190
  ...options,
190
191
  });
191
192
  },
192
- /** Delete one version (by id, name:tag or name@version) and its tags. */
193
+ /** Delete one version (by id, name:tag or name@version) and its tags. A
194
+ * bare name deletes its latest tag's version, or its only version when
195
+ * it has one and no latest tag; with several, the error names its tags. */
193
196
  async delete(ref, options) {
197
+ const id = await idOf(ref, options).catch(async (error) => {
198
+ if (!(error instanceof RuntimeError && error.code === "image_not_found") ||
199
+ /[:@]/.test(ref))
200
+ throw error;
201
+ const live = (await list({ name: ref, limit: 100 }, options)).data.filter((image) => image.state !== "deleted" && image.state !== "deleting");
202
+ if (live.length !== 1)
203
+ throw error;
204
+ return live[0].id;
205
+ });
194
206
  return t.json({
195
207
  method: "POST",
196
- path: `/v1/images/${enc(await idOf(ref, options))}:delete`,
208
+ path: `/v1/images/${enc(id)}:delete`,
197
209
  body: {},
198
210
  ...options,
199
211
  });
@@ -2,6 +2,7 @@ import type { Transport } from "../transport.js";
2
2
  import type { Sandbox } from "../sandbox.js";
3
3
  import { images } from "./images.js";
4
4
  import { sandboxDesktop } from "./desktop.js";
5
+ import { sandboxBrowser } from "./browser.js";
5
6
  import { sandboxInterpreter } from "./interpreter.js";
6
7
  import { sandboxNetwork } from "./network.js";
7
8
  import { addresses, domains, network, ports, tunnel } from "./network-products.js";
@@ -40,11 +41,22 @@ export declare const clientExtensions: {
40
41
  tunnel: typeof tunnel;
41
42
  network: typeof network;
42
43
  };
44
+ /** Canonical CLI product names share the existing factory instance. Account
45
+ * commands such as secrets, events and billing retain their current names. */
46
+ export declare const clientProductAliases: {
47
+ readonly image: "images";
48
+ readonly volume: "volumes";
49
+ readonly job: "jobs";
50
+ readonly domain: "domains";
51
+ readonly port: "ports";
52
+ readonly address: "addresses";
53
+ };
43
54
  export declare const sandboxExtensions: {
44
55
  interpreter: typeof sandboxInterpreter;
45
56
  previews: typeof sandboxPreviews;
46
57
  network: typeof sandboxNetwork;
47
58
  desktop: typeof sandboxDesktop;
59
+ browser: typeof sandboxBrowser;
48
60
  mounts: typeof sandboxMounts;
49
61
  tailscale: typeof sandboxTailscale;
50
62
  metrics: typeof sandboxMetrics;
@@ -52,6 +64,8 @@ export declare const sandboxExtensions: {
52
64
  };
53
65
  export type ClientExtensions = {
54
66
  readonly [K in keyof typeof clientExtensions]: ReturnType<(typeof clientExtensions)[K]>;
67
+ } & {
68
+ readonly [K in keyof typeof clientProductAliases]: ReturnType<(typeof clientExtensions)[(typeof clientProductAliases)[K]]>;
55
69
  };
56
70
  export type SandboxExtensions = {
57
71
  readonly [K in keyof typeof sandboxExtensions]: ReturnType<(typeof sandboxExtensions)[K]>;
@@ -1,5 +1,6 @@
1
1
  import { images } from "./images.js";
2
2
  import { sandboxDesktop } from "./desktop.js";
3
+ import { sandboxBrowser } from "./browser.js";
3
4
  import { sandboxInterpreter } from "./interpreter.js";
4
5
  import { sandboxNetwork } from "./network.js";
5
6
  import { addresses, domains, network, ports, tunnel } from "./network-products.js";
@@ -53,11 +54,22 @@ export const clientExtensions = {
53
54
  tunnel,
54
55
  network,
55
56
  };
57
+ /** Canonical CLI product names share the existing factory instance. Account
58
+ * commands such as secrets, events and billing retain their current names. */
59
+ export const clientProductAliases = {
60
+ image: "images",
61
+ volume: "volumes",
62
+ job: "jobs",
63
+ domain: "domains",
64
+ port: "ports",
65
+ address: "addresses",
66
+ };
56
67
  export const sandboxExtensions = {
57
68
  interpreter: sandboxInterpreter,
58
69
  previews: sandboxPreviews,
59
70
  network: sandboxNetwork,
60
71
  desktop: sandboxDesktop,
72
+ browser: sandboxBrowser,
61
73
  mounts: sandboxMounts,
62
74
  tailscale: sandboxTailscale,
63
75
  metrics: sandboxMetrics,
@@ -195,6 +195,13 @@ export type SetUpstreamProxy = {
195
195
  * logs apply. A proxy that refuses or cannot be reached fails the connection
196
196
  * (upstream-proxy-failed); nothing goes around it. Paid accounts only. */
197
197
  export declare function network(t: Transport): {
198
+ /** Your sandboxes reaching each other by name (`private-network.ts`). */
199
+ private: {
200
+ get: (options?: RequestOptions) => Promise<import("./private-network.js").PrivateNetwork>;
201
+ set: (input: {
202
+ enabled: boolean;
203
+ }, options?: RequestOptions) => Promise<import("./private-network.js").PrivateNetwork>;
204
+ };
198
205
  upstreamProxy: {
199
206
  /** Send outbound connections through your proxy, replacing any set before. */
200
207
  set: (input: SetUpstreamProxy, options?: RequestOptions) => Promise<UpstreamProxy & {
@@ -1,3 +1,4 @@
1
+ import { privateNetwork } from "./private-network.js";
1
2
  const enc = encodeURIComponent;
2
3
  /** `runtime.domains`: serve a sandbox's port at your own hostname, with HTTPS. */
3
4
  export function domains(t) {
@@ -91,6 +92,8 @@ export function tunnel(t) {
91
92
  export function network(t) {
92
93
  const path = "/v1/network/upstream-proxy";
93
94
  return {
95
+ /** Your sandboxes reaching each other by name (`private-network.ts`). */
96
+ private: privateNetwork(t),
94
97
  upstreamProxy: {
95
98
  /** Send outbound connections through your proxy, replacing any set before. */
96
99
  set: (input, options) => t.json({
@@ -10,6 +10,11 @@ export type MetricPoint = {
10
10
  cpuCores: number | null;
11
11
  /** The busiest interval between two readings in the bucket. */
12
12
  cpuPeakPercent: number | null;
13
+ /** Measured wall-time percentage ready to run while waiting for CPU; null
14
+ * without two complete readings of the same run. Older servers omit it. */
15
+ cpuWaitPercent?: number | null;
16
+ /** Measured wall-time percentage with a thread waiting for memory. */
17
+ memoryStallPercent?: number | null;
13
18
  /** Average resident memory, bytes. */
14
19
  memoryBytes: number;
15
20
  memoryPeakBytes: number;
@@ -96,6 +101,45 @@ type List<T> = {
96
101
  data: T[];
97
102
  nextCursor: string | null;
98
103
  };
104
+ export type UsageExportQuery = {
105
+ since: string;
106
+ until: string;
107
+ cursor?: string;
108
+ limit?: number;
109
+ };
110
+ export type UsageExportRow = {
111
+ intervalId: string;
112
+ resourceId: string;
113
+ name: string | null;
114
+ kind: string;
115
+ meter: string;
116
+ fundedBy: "credit" | "trial";
117
+ startsAt: string;
118
+ endsAt: string;
119
+ settledAt: string;
120
+ billedMilliseconds: string | null;
121
+ chargedMicros: string;
122
+ quotedRates: Array<{
123
+ component: string;
124
+ unit: string;
125
+ rateMicros: string;
126
+ }>;
127
+ /** Exact JSON text, preserving integers beyond JavaScript's number range. */
128
+ measurementsJson: string | null;
129
+ evidence: "measured" | "partial" | "missing" | null;
130
+ };
131
+ export type UsageExportPage = {
132
+ since: string;
133
+ until: string;
134
+ data: UsageExportRow[];
135
+ nextCursor: string | null;
136
+ /** Concatenate pages in order; only the first page includes the header. */
137
+ csv: string;
138
+ };
139
+ /** Top-level cross-product settled usage, selected by UTC settlement timestamps
140
+ * in [since, until). Pending holds are excluded. Follow nextCursor using the
141
+ * same range; each page checks current permissions. Pages are live reads. */
142
+ export declare function usageExport(t: Transport): (query: UsageExportQuery, options?: RequestOptions) => Promise<UsageExportPage>;
99
143
  /** `sbx.metrics()`: this sandbox's measured CPU and memory. */
100
144
  export declare function sandboxMetrics(t: Transport, sandbox: Sandbox): (options?: {
101
145
  range?: MetricRange;