withruntime 0.8.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/BLAXEL.md +4 -4
  2. package/COMPATIBILITY.md +145 -0
  3. package/DAYTONA.md +13 -7
  4. package/E2B.md +34 -20
  5. package/README.md +22 -11
  6. package/README.src.md +22 -11
  7. package/VERCEL.md +13 -3
  8. package/compatibility-lock.json +357 -0
  9. package/dist/ai-harness/index.js +2 -2
  10. package/dist/api-defaults.d.ts +17 -0
  11. package/dist/api-defaults.js +22 -0
  12. package/dist/blaxel/errors.js +1 -1
  13. package/dist/blaxel/lookup.d.ts +3 -2
  14. package/dist/blaxel/lookup.js +3 -2
  15. package/dist/blaxel/pagination.d.ts +2 -2
  16. package/dist/blaxel/pagination.js +45 -17
  17. package/dist/blaxel/preview.d.ts +3 -2
  18. package/dist/blaxel/preview.js +16 -14
  19. package/dist/blaxel/process.d.ts +1 -1
  20. package/dist/blaxel/process.js +66 -11
  21. package/dist/blaxel/sandbox.d.ts +2 -0
  22. package/dist/blaxel/sandbox.js +2 -0
  23. package/dist/blaxel/snapshot.js +3 -5
  24. package/dist/cli-words.d.ts +27 -0
  25. package/dist/cli-words.js +135 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +351 -85
  28. package/dist/client.d.ts +33 -4
  29. package/dist/client.js +47 -12
  30. package/dist/cloudflare/identity.d.ts +7 -0
  31. package/dist/cloudflare/identity.js +13 -0
  32. package/dist/cloudflare/index.d.ts +303 -0
  33. package/dist/cloudflare/index.js +592 -0
  34. package/dist/cloudflare/worker.d.ts +419 -0
  35. package/dist/cloudflare/worker.js +447 -0
  36. package/dist/codesandbox/guest-service.d.ts +31 -0
  37. package/dist/codesandbox/guest-service.js +490 -0
  38. package/dist/codesandbox/index.d.ts +386 -0
  39. package/dist/codesandbox/index.js +1347 -0
  40. package/dist/compat/core.d.ts +40 -0
  41. package/dist/compat/core.js +238 -0
  42. package/dist/compat/files.d.ts +26 -0
  43. package/dist/compat/files.js +133 -0
  44. package/dist/compat/shell.d.ts +4 -0
  45. package/dist/compat/shell.js +165 -0
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +107 -38
  48. package/dist/daytona/context.js +2 -1
  49. package/dist/daytona/daytona.js +11 -4
  50. package/dist/daytona/filesystem.d.ts +17 -4
  51. package/dist/daytona/filesystem.js +121 -38
  52. package/dist/daytona/index.d.ts +1 -1
  53. package/dist/daytona/process.d.ts +1 -1
  54. package/dist/daytona/process.js +27 -4
  55. package/dist/daytona/sandbox.d.ts +1 -1
  56. package/dist/daytona/sandbox.js +16 -7
  57. package/dist/e2b/client.js +1 -1
  58. package/dist/e2b/code-interpreter.d.ts +4 -5
  59. package/dist/e2b/code-interpreter.js +54 -56
  60. package/dist/e2b/commands.d.ts +35 -12
  61. package/dist/e2b/commands.js +169 -73
  62. package/dist/e2b/errors.js +1 -1
  63. package/dist/e2b/filesystem.d.ts +8 -6
  64. package/dist/e2b/filesystem.js +127 -28
  65. package/dist/e2b/index.d.ts +1 -0
  66. package/dist/e2b/index.js +1 -0
  67. package/dist/e2b/pty.d.ts +24 -0
  68. package/dist/e2b/pty.js +80 -0
  69. package/dist/e2b/sandbox.d.ts +6 -4
  70. package/dist/e2b/sandbox.js +59 -25
  71. package/dist/errors.d.ts +8 -0
  72. package/dist/errors.js +15 -2
  73. package/dist/file-source.d.ts +16 -0
  74. package/dist/file-source.js +162 -0
  75. package/dist/freestyle/index.d.ts +309 -0
  76. package/dist/freestyle/index.js +343 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/login.js +16 -10
  79. package/dist/mcp.js +2 -2
  80. package/dist/modal/index.d.ts +205 -0
  81. package/dist/modal/index.js +486 -0
  82. package/dist/network-cli.js +112 -44
  83. package/dist/openai-agents/index.js +18 -71
  84. package/dist/page.d.ts +1 -1
  85. package/dist/page.js +2 -2
  86. package/dist/products/audit.d.ts +3 -0
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/interpreter.d.ts +11 -4
  90. package/dist/products/interpreter.js +33 -9
  91. package/dist/products/jobs.js +3 -2
  92. package/dist/products/limits.d.ts +10 -0
  93. package/dist/products/observability.d.ts +44 -0
  94. package/dist/products/observability.js +6 -0
  95. package/dist/products/previews.d.ts +18 -4
  96. package/dist/products/previews.js +14 -5
  97. package/dist/products/tailscale.d.ts +57 -0
  98. package/dist/products/tailscale.js +17 -0
  99. package/dist/products/volumes.d.ts +42 -0
  100. package/dist/products/volumes.js +36 -0
  101. package/dist/products/watch.d.ts +25 -4
  102. package/dist/products/watch.js +132 -18
  103. package/dist/proxy.js +3 -1
  104. package/dist/runloop/index.d.ts +326 -0
  105. package/dist/runloop/index.js +535 -0
  106. package/dist/runloop/pagination.d.ts +46 -0
  107. package/dist/runloop/pagination.js +82 -0
  108. package/dist/runloop/snapshots.d.ts +52 -0
  109. package/dist/runloop/snapshots.js +244 -0
  110. package/dist/sandbox.d.ts +76 -16
  111. package/dist/sandbox.js +696 -135
  112. package/dist/snapshots.d.ts +20 -5
  113. package/dist/snapshots.js +11 -1
  114. package/dist/sprites/index.d.ts +251 -0
  115. package/dist/sprites/index.js +588 -0
  116. package/dist/tar.d.ts +10 -1
  117. package/dist/tar.js +311 -35
  118. package/dist/tools/index.js +2 -1
  119. package/dist/transport.d.ts +23 -8
  120. package/dist/transport.js +247 -127
  121. package/dist/types.d.ts +52 -3
  122. package/dist/vercel/command.d.ts +1 -1
  123. package/dist/vercel/command.js +15 -3
  124. package/dist/vercel/errors.d.ts +2 -2
  125. package/dist/vercel/errors.js +19 -2
  126. package/dist/vercel/filesystem.d.ts +21 -18
  127. package/dist/vercel/filesystem.js +95 -48
  128. package/dist/vercel/sandbox.d.ts +8 -8
  129. package/dist/vercel/sandbox.js +159 -64
  130. package/dist/vercel/snapshot.d.ts +3 -2
  131. package/dist/vercel/snapshot.js +9 -5
  132. package/package.json +46 -3
@@ -18,6 +18,7 @@
18
18
  * RUNTIME_API_KEY or this machine's `runtime login`. Plain function tools for
19
19
  * an ordinary `Agent` are `runtimeFunctionTools(sandbox)`. */
20
20
  import { applyDiff, tool, UserError, } from "@openai/agents-core";
21
+ import { OPENAI_AGENTS_EXEC_TIMEOUT_MS } from "../api-defaults.js";
21
22
  import { SandboxProviderError, SandboxUnsupportedFeatureError, normalizeSandboxClientCreateArgs, } from "@openai/agents-core/sandbox";
22
23
  import { deserializeManifest, elapsedSeconds, formatExecResponse, imageOutputFromBytes, materializeEnvironment, mergeManifestDelta, mergeManifestEntryDelta, serializeManifestRecord, shellQuote, truncateOutput, } from "@openai/agents-core/sandbox/internal";
23
24
  import { Runtime } from "../client.js";
@@ -477,31 +478,11 @@ export class RuntimeCloudSandboxSession {
477
478
  await this.#writeBytes(path, await readFile(entry.src));
478
479
  return;
479
480
  }
480
- case "local_dir": {
481
- // One gzipped tar, staged inside /workspace where uploads of any size are accepted.
482
- const { packDirectory } = await import("../tar.js");
483
- const staging = `${STAGING}/${crypto.randomUUID()}.tar.gz`;
484
- const archive = await packDirectory(entry.src);
485
- try {
486
- await this.#call((sbx) => sbx.files.write(staging, archive));
487
- const unpacked = await this.#call((sbx) => sbx.exec([
488
- "sh",
489
- "-c",
490
- 'mkdir -p -- "$1" && tar -xzf "$2" -C "$1"',
491
- "sh",
492
- path,
493
- staging,
494
- ]));
495
- if (unpacked.exitCode !== 0)
496
- throw new SandboxProviderError(`${PROVIDER} could not place ${entry.src}.`, {
497
- stderr: unpacked.stderr,
498
- });
499
- }
500
- finally {
501
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
502
- }
481
+ case "local_dir":
482
+ // One archive through the API's folder routes, unpacked by the
483
+ // sandbox's own tar as it arrives.
484
+ await this.#call((sbx) => sbx.files.upload(entry.src, path));
503
485
  return;
504
- }
505
486
  case "git_repo": {
506
487
  const url = /^[a-z]+:\/\//.test(entry.repo)
507
488
  ? entry.repo
@@ -550,29 +531,11 @@ export class RuntimeCloudSandboxSession {
550
531
  // Workspace archives.
551
532
  async persistWorkspace() {
552
533
  const root = this.state.manifest.root;
553
- const staging = `${STAGING}/${crypto.randomUUID()}.tar`;
554
- const skip = root === FILES_HOME ? ["--exclude=./.openai-agents-staging"] : [];
555
- try {
556
- const packed = await this.#call((sbx) => sbx.exec([
557
- "sh",
558
- "-c",
559
- `mkdir -p -- ${STAGING} && tar ${skip.join(" ")} -C "$1" -cf - . > "$2"`,
560
- "sh",
561
- root,
562
- staging,
563
- ], {
564
- cwd: "/",
565
- timeoutMs: this.state.execTimeoutMs,
566
- }));
567
- if (packed.exitCode !== 0)
568
- throw new SandboxProviderError(`${PROVIDER} could not archive the workspace.`, {
569
- stderr: packed.stderr,
570
- });
571
- return await this.#call((sbx) => sbx.files.read(staging));
572
- }
573
- finally {
574
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
575
- }
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
+ }));
576
539
  }
577
540
  async hydrateWorkspace(data) {
578
541
  const bytes = typeof data === "string"
@@ -580,28 +543,10 @@ export class RuntimeCloudSandboxSession {
580
543
  : data instanceof Uint8Array
581
544
  ? data
582
545
  : new Uint8Array(data);
583
- const staging = `${STAGING}/${crypto.randomUUID()}.tar`;
584
- try {
585
- await this.#call((sbx) => sbx.files.write(staging, bytes));
586
- const unpacked = await this.#call((sbx) => sbx.exec([
587
- "sh",
588
- "-c",
589
- 'mkdir -p -- "$1" && tar -C "$1" -xf "$2"',
590
- "sh",
591
- this.state.manifest.root,
592
- staging,
593
- ], {
594
- cwd: "/",
595
- timeoutMs: this.state.execTimeoutMs,
596
- }));
597
- if (unpacked.exitCode !== 0)
598
- throw new SandboxProviderError(`${PROVIDER} could not restore the workspace.`, {
599
- stderr: unpacked.stderr,
600
- });
601
- }
602
- finally {
603
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
604
- }
546
+ await this.#call((sbx) => sbx.files.unarchive(this.state.manifest.root, bytes, {
547
+ gzip: false,
548
+ timeoutMs: this.state.execTimeoutMs,
549
+ }));
605
550
  }
606
551
  // Lifecycle.
607
552
  async running() {
@@ -674,7 +619,7 @@ export class RuntimeCloudSandboxClient {
674
619
  ...(options.exposedPorts ? { configuredExposedPorts: options.exposedPorts } : {}),
675
620
  previewVisibility: options.previewVisibility ?? "private",
676
621
  pauseOnExit: options.pauseOnExit ?? false,
677
- execTimeoutMs: Math.min(MAX_EXEC_MS, options.execTimeoutMs ?? 3_600_000),
622
+ execTimeoutMs: Math.min(MAX_EXEC_MS, options.execTimeoutMs ?? OPENAI_AGENTS_EXEC_TIMEOUT_MS),
678
623
  };
679
624
  const session = new RuntimeCloudSandboxSession({ state, runtime: this.runtime });
680
625
  await session.sandbox();
@@ -764,7 +709,9 @@ export class RuntimeCloudSandboxClient {
764
709
  : {}),
765
710
  previewVisibility: record.previewVisibility === "public" ? "public" : "private",
766
711
  pauseOnExit: record.pauseOnExit === true,
767
- execTimeoutMs: typeof record.execTimeoutMs === "number" ? record.execTimeoutMs : 3_600_000,
712
+ execTimeoutMs: typeof record.execTimeoutMs === "number"
713
+ ? record.execTimeoutMs
714
+ : OPENAI_AGENTS_EXEC_TIMEOUT_MS,
768
715
  };
769
716
  }
770
717
  }
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() {
@@ -26,6 +26,9 @@ export type AuditEvent = {
26
26
  ip: string | null;
27
27
  requestId: string | null;
28
28
  via: "web" | "api" | "mcp" | "runtime";
29
+ /** The sandbox the request came from, when code in it called the API at
30
+ * http://runtime.internal. */
31
+ sandbox: string | null;
29
32
  };
30
33
  export type AuditPage = {
31
34
  events: AuditEvent[];
@@ -15,6 +15,7 @@ import { limits } from "./limits.js";
15
15
  import { volumes } from "./volumes.js";
16
16
  import { jobs } from "./jobs.js";
17
17
  import { sandboxMounts } from "./mounts.js";
18
+ import { sandboxTailscale } from "./tailscale.js";
18
19
  import { secrets } from "./secrets.js";
19
20
  import { mcp, sandboxMcp } from "./mcp.js";
20
21
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
@@ -39,17 +40,30 @@ export declare const clientExtensions: {
39
40
  tunnel: typeof tunnel;
40
41
  network: typeof network;
41
42
  };
43
+ /** Canonical CLI product names share the existing factory instance. Account
44
+ * commands such as secrets, events and billing retain their current names. */
45
+ export declare const clientProductAliases: {
46
+ readonly image: "images";
47
+ readonly volume: "volumes";
48
+ readonly job: "jobs";
49
+ readonly domain: "domains";
50
+ readonly port: "ports";
51
+ readonly address: "addresses";
52
+ };
42
53
  export declare const sandboxExtensions: {
43
54
  interpreter: typeof sandboxInterpreter;
44
55
  previews: typeof sandboxPreviews;
45
56
  network: typeof sandboxNetwork;
46
57
  desktop: typeof sandboxDesktop;
47
58
  mounts: typeof sandboxMounts;
59
+ tailscale: typeof sandboxTailscale;
48
60
  metrics: typeof sandboxMetrics;
49
61
  mcp: typeof sandboxMcp;
50
62
  };
51
63
  export type ClientExtensions = {
52
64
  readonly [K in keyof typeof clientExtensions]: ReturnType<(typeof clientExtensions)[K]>;
65
+ } & {
66
+ readonly [K in keyof typeof clientProductAliases]: ReturnType<(typeof clientExtensions)[(typeof clientProductAliases)[K]]>;
53
67
  };
54
68
  export type SandboxExtensions = {
55
69
  readonly [K in keyof typeof sandboxExtensions]: ReturnType<(typeof sandboxExtensions)[K]>;
@@ -13,6 +13,7 @@ import { limits } from "./limits.js";
13
13
  import { volumes } from "./volumes.js";
14
14
  import { jobs } from "./jobs.js";
15
15
  import { sandboxMounts } from "./mounts.js";
16
+ import { sandboxTailscale } from "./tailscale.js";
16
17
  import { secrets } from "./secrets.js";
17
18
  import { mcp, sandboxMcp } from "./mcp.js";
18
19
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
@@ -52,12 +53,23 @@ export const clientExtensions = {
52
53
  tunnel,
53
54
  network,
54
55
  };
56
+ /** Canonical CLI product names share the existing factory instance. Account
57
+ * commands such as secrets, events and billing retain their current names. */
58
+ export const clientProductAliases = {
59
+ image: "images",
60
+ volume: "volumes",
61
+ job: "jobs",
62
+ domain: "domains",
63
+ port: "ports",
64
+ address: "addresses",
65
+ };
55
66
  export const sandboxExtensions = {
56
67
  interpreter: sandboxInterpreter,
57
68
  previews: sandboxPreviews,
58
69
  network: sandboxNetwork,
59
70
  desktop: sandboxDesktop,
60
71
  mounts: sandboxMounts,
72
+ tailscale: sandboxTailscale,
61
73
  metrics: sandboxMetrics,
62
74
  mcp: sandboxMcp,
63
75
  };
@@ -50,15 +50,22 @@ export type RunOptions = RequestOptions & {
50
50
  /** A context id; each language's own name ("python", "typescript", "r"...) is
51
51
  * its default context, started on first use. */
52
52
  context?: string;
53
+ /** Cell deadline. 0 runs until completion, cancellation or the sandbox's lease ends. */
53
54
  timeoutMs?: number;
54
- onStdout?: (text: string) => void;
55
- onStderr?: (text: string) => void;
56
- onResult?: (result: ExecutionResult) => void;
55
+ /** Separate HTTP deadline, including retries. 0 disables it. */
56
+ requestTimeoutMs?: number;
57
+ /** false lets the cell continue if this reader disconnects. Default true. */
58
+ interruptOnDisconnect?: boolean;
59
+ /** @internal Compatibility protocols with separate header/body deadlines. */
60
+ onResponse?: (response: Response) => void;
61
+ onStdout?: (text: string) => unknown;
62
+ onStderr?: (text: string) => unknown;
63
+ onResult?: (result: ExecutionResult) => unknown;
57
64
  onError?: (error: {
58
65
  name: string;
59
66
  value: string;
60
67
  traceback: string;
61
- }) => void;
68
+ }) => unknown;
62
69
  };
63
70
  /** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
64
71
  * R, Java, Bash or Go:
@@ -1,3 +1,4 @@
1
+ import { RuntimeError } from "../errors.js";
1
2
  /** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
2
3
  * R, Java, Bash or Go:
3
4
  * `await sbx.interpreter.run("import pandas as pd; pd.DataFrame({'a': [1]})")`.
@@ -7,33 +8,56 @@ export function sandboxInterpreter(t, sandbox) {
7
8
  return {
8
9
  /** Run a cell. With any of the on* callbacks, output streams as it happens. */
9
10
  async run(code, options = {}) {
10
- const { language, context, timeoutMs, onStdout, onStderr, onResult, onError, ...request } = options;
11
+ const { language, context, timeoutMs, requestTimeoutMs, interruptOnDisconnect, onStdout, onStderr, onResult, onError, ...request } = options;
12
+ const transportOptions = {
13
+ ...request,
14
+ ...(requestTimeoutMs !== undefined
15
+ ? { timeoutMs: requestTimeoutMs }
16
+ : timeoutMs !== undefined
17
+ ? { timeoutMs: timeoutMs === 0 ? 0 : timeoutMs + 60_000 }
18
+ : {}),
19
+ };
11
20
  const body = {
12
21
  code,
13
22
  ...(language ? { language } : {}),
14
23
  ...(context ? { context } : {}),
15
- ...(timeoutMs ? { timeoutMs } : {}),
24
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
25
+ ...(interruptOnDisconnect !== undefined ? { interruptOnDisconnect } : {}),
16
26
  };
17
27
  if (!onStdout && !onStderr && !onResult && !onError)
18
- return t.json({ method: "POST", path: `${base()}:run`, body, ...request });
28
+ return t.json({
29
+ method: "POST",
30
+ path: `${base()}:run`,
31
+ body,
32
+ ...transportOptions,
33
+ });
19
34
  for await (const event of t.events({
20
35
  method: "POST",
21
36
  path: `${base()}:run`,
22
37
  body: { ...body, stream: true },
23
- ...request,
38
+ ...transportOptions,
24
39
  })) {
25
40
  if (event.k === "stdout")
26
- onStdout?.(event.text);
41
+ await onStdout?.(event.text);
27
42
  else if (event.k === "stderr")
28
- onStderr?.(event.text);
43
+ await onStderr?.(event.text);
29
44
  else if (event.k === "result")
30
- onResult?.({ main: event.main, data: event.data, refs: event.refs });
45
+ await onResult?.({ main: event.main, data: event.data, refs: event.refs });
31
46
  else if (event.k === "error")
32
- onError?.(event);
47
+ await onError?.(event);
33
48
  else if (event.k === "execution")
34
49
  return event.execution;
35
50
  else if (event.k === "failure")
36
- throw new Error(`${event.code}: ${event.message}`);
51
+ // As every other refusal: its code, words, hint and request id,
52
+ // printed by the CLI as it prints the rest (28 September 2026:
53
+ // "Error: forbidden: cloud forbidden", with no hint and no id).
54
+ throw new RuntimeError({
55
+ code: event.code,
56
+ status: event.status ?? 0,
57
+ message: event.message,
58
+ ...(event.hint ? { hint: event.hint } : {}),
59
+ ...(event.requestId ? { requestId: event.requestId } : {}),
60
+ });
37
61
  }
38
62
  throw new Error("The interpreter stream ended without a result");
39
63
  },
@@ -1,3 +1,4 @@
1
+ import { JOB_START_SECONDS, JOB_TIMEOUT_SECONDS } from "../api-defaults.js";
1
2
  import { Page } from "../page.js";
2
3
  import { RuntimeError, ServiceUnavailableError } from "../errors.js";
3
4
  /** The size a run gets when `compute` leaves a field out: the sandbox
@@ -9,9 +10,9 @@ export const JOB_DEFAULTS = {
9
10
  cpuFloorMillis: 50,
10
11
  memoryMiB: 4096,
11
12
  diskMiB: 4096,
12
- timeoutSeconds: 1800,
13
+ timeoutSeconds: JOB_TIMEOUT_SECONDS,
13
14
  /** Added to the timeout for the sandbox to start, within the hour. */
14
- startSeconds: 60,
15
+ startSeconds: JOB_START_SECONDS,
15
16
  };
16
17
  /** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
17
18
  * the system image, and paid for at most an hour. */
@@ -14,6 +14,16 @@ export type KeyLimits = {
14
14
  remainingMicros: string | null;
15
15
  window: "24h";
16
16
  };
17
+ /** The account's free trial time in milliseconds, the same figures as
18
+ * `usage.get()`, or null when the account has no trial. A trial sandbox
19
+ * that has not ended holds its whole lease in `reservedMs`;
20
+ * `availableMs = totalMs - usedMs - reservedMs`. */
21
+ trial: {
22
+ totalMs: number;
23
+ usedMs: number;
24
+ reservedMs: number;
25
+ availableMs: number;
26
+ } | null;
17
27
  };
18
28
  /** `runtime.limits.get()`: whether this key is read-only, and what its agent
19
29
  * may still spend today. An owner sets the limit at
@@ -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;
@@ -1,3 +1,9 @@
1
+ /** Top-level cross-product settled usage, selected by UTC settlement timestamps
2
+ * in [since, until). Pending holds are excluded. Follow nextCursor using the
3
+ * same range; each page checks current permissions. Pages are live reads. */
4
+ export function usageExport(t) {
5
+ return (query, options = {}) => t.json({ method: "GET", path: "/v1/usage/export", query, ...options });
6
+ }
1
7
  /** `sbx.metrics()`: this sandbox's measured CPU and memory. */
2
8
  export function sandboxMetrics(t, sandbox) {
3
9
  return (options = {}) => {
@@ -17,6 +17,8 @@ export type Preview = {
17
17
  urlWithToken: string | null;
18
18
  /** Turned off by Runtime after a report. */
19
19
  disabled: boolean;
20
+ /** The sites that may show it in an iframe; null for any site. */
21
+ embedOrigins: string[] | null;
20
22
  createdAt: string;
21
23
  /** On create only: how to share the address, in one line. */
22
24
  hint?: string;
@@ -27,6 +29,14 @@ export type CreatePreview = {
27
29
  visibility?: "private" | "public";
28
30
  /** How long the returned token lasts, 60 s to 7 days (default 1 day). */
29
31
  ttlSeconds?: number;
32
+ /** An absolute ISO private-token deadline, a minute to a week ahead.
33
+ * Used exactly without TTL; with TTL, the earlier expiry wins. */
34
+ expiresAt?: string;
35
+ /** The sites that may show it in an iframe, as origins
36
+ * (`https://app.example.com`, `https://*.example.com`, `http://localhost:3000`),
37
+ * up to 16. Any other site's iframe is refused. Omitted: a shared port keeps
38
+ * its list; `[]` or null: any site, the default. */
39
+ embedOrigins?: string[] | null;
30
40
  };
31
41
  /** `sbx.previews`: share ports of this sandbox at public HTTPS addresses.
32
42
  *
@@ -35,15 +45,19 @@ export type CreatePreview = {
35
45
  *
36
46
  * WebSockets work. The server must listen on 0.0.0.0 or localhost. */
37
47
  export declare function sandboxPreviews(t: Transport, sandbox: Sandbox): {
38
- /** Shares `port`, or changes its visibility if it is shared already. */
48
+ /** Shares `port`, or changes its visibility and embedOrigins if it is
49
+ * shared already. */
39
50
  create: (port: number, input?: CreatePreview, options?: RequestOptions) => Promise<Preview>;
40
51
  /** Every shared port, each private one with a fresh token. */
41
52
  list: (options?: RequestOptions) => Promise<Preview[]>;
42
- /** One preview, with a fresh token of `ttlSeconds` if it is private. */
43
- get: (port: number, ttlSeconds?: number, options?: RequestOptions) => Promise<Preview>;
53
+ /** One preview, with a fresh private token. `expiresAt` bounds its expiry
54
+ * to an absolute ISO deadline; with no TTL, that deadline is used exactly. */
55
+ get: (port: number, ttlSeconds?: number, { expiresAt, ...options }?: RequestOptions & {
56
+ expiresAt?: string;
57
+ }) => Promise<Preview>;
44
58
  /** Refuses every token issued for this port so far and returns a new one. */
45
59
  rotate: (port: number, options?: RequestOptions) => Promise<Preview>;
46
- /** Stops sharing `port`. Open connections close within seconds. */
60
+ /** Stops sharing `port`. Its open connections are closed before this returns. */
47
61
  delete: (port: number, options?: RequestOptions) => Promise<{
48
62
  id: string;
49
63
  sandboxId: string;
@@ -7,20 +7,29 @@
7
7
  export function sandboxPreviews(t, sandbox) {
8
8
  const base = () => `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/previews`;
9
9
  return {
10
- /** Shares `port`, or changes its visibility if it is shared already. */
10
+ /** Shares `port`, or changes its visibility and embedOrigins if it is
11
+ * shared already. */
11
12
  create: (port, input = {}, options) => t.json({ method: "POST", path: base(), body: { port, ...input }, ...options }),
12
13
  /** Every shared port, each private one with a fresh token. */
13
14
  list: async (options) => (await t.json({ method: "GET", path: base(), ...options })).data,
14
- /** One preview, with a fresh token of `ttlSeconds` if it is private. */
15
- get: (port, ttlSeconds, options) => t.json({
15
+ /** One preview, with a fresh private token. `expiresAt` bounds its expiry
16
+ * to an absolute ISO deadline; with no TTL, that deadline is used exactly. */
17
+ get: (port, ttlSeconds, { expiresAt, ...options } = {}) => t.json({
16
18
  method: "GET",
17
19
  path: `${base()}/${port}`,
18
- ...(ttlSeconds ? { query: { ttlSeconds } } : {}),
20
+ ...(ttlSeconds !== undefined || expiresAt !== undefined
21
+ ? {
22
+ query: {
23
+ ...(ttlSeconds === undefined ? {} : { ttlSeconds }),
24
+ ...(expiresAt === undefined ? {} : { expiresAt }),
25
+ },
26
+ }
27
+ : {}),
19
28
  ...options,
20
29
  }),
21
30
  /** Refuses every token issued for this port so far and returns a new one. */
22
31
  rotate: (port, options) => t.json({ method: "POST", path: `${base()}/${port}:rotate`, ...options }),
23
- /** Stops sharing `port`. Open connections close within seconds. */
32
+ /** Stops sharing `port`. Its open connections are closed before this returns. */
24
33
  delete: (port, options) => t.json({
25
34
  method: "DELETE",
26
35
  path: `${base()}/${port}`,
@@ -0,0 +1,57 @@
1
+ import type { Sandbox } from "../sandbox.js";
2
+ import type { RequestOptions, Transport } from "../transport.js";
3
+ /** Joining a sandbox to your tailnet. */
4
+ export type TailscaleJoin = {
5
+ /** The name of a secret stored for jobs (`runtime secrets set NAME --jobs`)
6
+ * that holds a Tailscale auth key. Use an ephemeral, tagged key. */
7
+ authKeySecret: string;
8
+ /** The machine's name on the tailnet, a DNS label. Default: the sandbox's
9
+ * name, or runtime- and the first part of its id. */
10
+ hostname?: string;
11
+ /** Tags to advertise, such as `tag:agents`; the key must be allowed them. */
12
+ tags?: string[];
13
+ };
14
+ /** A sandbox's machine on your tailnet, as the sandbox reports it. */
15
+ export type TailscaleNode = {
16
+ /** Logged in and running now. */
17
+ running: boolean;
18
+ /** Tailscale's own word: Running, NeedsLogin, Starting, Stopped. */
19
+ state: string | null;
20
+ /** kernel: a tailscale0 interface reaches 100.x directly. userspace: the
21
+ * tailnet reaches the sandbox's ports, and programs in it reach the tailnet
22
+ * through `proxy`. */
23
+ mode: "kernel" | "userspace" | null;
24
+ /** Its MagicDNS name, such as agent-1.tail1234.ts.net. */
25
+ dnsName: string | null;
26
+ /** Its tailnet addresses, 100.x and fd7a:… */
27
+ addresses: string[];
28
+ /** SOCKS5 and HTTP proxy to the tailnet in userspace mode. */
29
+ proxy: string | null;
30
+ };
31
+ /** What a join answers: the node, and what Runtime recorded. */
32
+ export type TailscaleJoined = TailscaleNode & {
33
+ hostname: string;
34
+ tags: string[];
35
+ /** The secret the key was read from; never the key. */
36
+ authKeySecret: string | null;
37
+ joinedAt: number;
38
+ };
39
+ /** What `status()` answers: the node, and the join, or null on none. */
40
+ export type TailscaleStatus = TailscaleNode & {
41
+ tailnet: Pick<TailscaleJoined, "hostname" | "tags" | "authKeySecret" | "joinedAt"> | null;
42
+ };
43
+ /** `sbx.tailscale`: this sandbox on your own Tailscale network. Paid
44
+ * sandboxes only.
45
+ *
46
+ * const node = await sbx.tailscale.up({ authKeySecret: "TS_AUTHKEY", tags: ["tag:agents"] });
47
+ * console.log(node.addresses);
48
+ *
49
+ * The auth key stays in the sandbox's memory. `down()` takes the machine off
50
+ * the tailnet at once; after a stop or a delete, Tailscale removes it. */
51
+ export declare function sandboxTailscale(t: Transport, sandbox: Sandbox): {
52
+ up: (input: TailscaleJoin, options?: RequestOptions) => Promise<TailscaleJoined>;
53
+ status: (options?: RequestOptions) => Promise<TailscaleStatus>;
54
+ down: (options?: RequestOptions) => Promise<{
55
+ left: boolean;
56
+ }>;
57
+ };
@@ -0,0 +1,17 @@
1
+ const enc = encodeURIComponent;
2
+ /** `sbx.tailscale`: this sandbox on your own Tailscale network. Paid
3
+ * sandboxes only.
4
+ *
5
+ * const node = await sbx.tailscale.up({ authKeySecret: "TS_AUTHKEY", tags: ["tag:agents"] });
6
+ * console.log(node.addresses);
7
+ *
8
+ * The auth key stays in the sandbox's memory. `down()` takes the machine off
9
+ * the tailnet at once; after a stop or a delete, Tailscale removes it. */
10
+ export function sandboxTailscale(t, sandbox) {
11
+ const path = () => `/v1/sandboxes/${enc(sandbox.id)}/tailscale`;
12
+ return {
13
+ up: (input, options) => t.json({ method: "POST", path: path(), body: input, ...options }),
14
+ status: (options) => t.json({ method: "GET", path: path(), ...options }),
15
+ down: (options) => t.json({ method: "DELETE", path: path(), ...options }),
16
+ };
17
+ }