withruntime 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/DAYTONA.md +129 -0
  2. package/E2B.md +3 -3
  3. package/README.md +47 -9
  4. package/VERCEL.md +127 -0
  5. package/dist/ai/index.d.ts +33 -0
  6. package/dist/ai/index.js +38 -0
  7. package/dist/claude-agent-sdk/index.d.ts +7 -0
  8. package/dist/claude-agent-sdk/index.js +65 -0
  9. package/dist/cli.d.ts +6 -0
  10. package/dist/cli.js +959 -102
  11. package/dist/client.d.ts +25 -2
  12. package/dist/client.js +14 -1
  13. package/dist/credentials.d.ts +10 -0
  14. package/dist/credentials.js +56 -1
  15. package/dist/daytona/client.d.ts +44 -0
  16. package/dist/daytona/client.js +40 -0
  17. package/dist/daytona/context.d.ts +31 -0
  18. package/dist/daytona/context.js +25 -0
  19. package/dist/daytona/daytona.d.ts +185 -0
  20. package/dist/daytona/daytona.js +416 -0
  21. package/dist/daytona/errors.d.ts +106 -0
  22. package/dist/daytona/errors.js +182 -0
  23. package/dist/daytona/filesystem.d.ts +84 -0
  24. package/dist/daytona/filesystem.js +206 -0
  25. package/dist/daytona/git.d.ts +33 -0
  26. package/dist/daytona/git.js +128 -0
  27. package/dist/daytona/image.d.ts +45 -0
  28. package/dist/daytona/image.js +140 -0
  29. package/dist/daytona/index.d.ts +50 -0
  30. package/dist/daytona/index.js +50 -0
  31. package/dist/daytona/process.d.ts +86 -0
  32. package/dist/daytona/process.js +341 -0
  33. package/dist/daytona/sandbox.d.ts +196 -0
  34. package/dist/daytona/sandbox.js +448 -0
  35. package/dist/e2b/commands.js +6 -2
  36. package/dist/e2b/sandbox.d.ts +22 -2
  37. package/dist/e2b/sandbox.js +39 -7
  38. package/dist/errors.d.ts +10 -4
  39. package/dist/errors.js +26 -6
  40. package/dist/image-context.d.ts +39 -0
  41. package/dist/image-context.js +176 -0
  42. package/dist/index.d.ts +19 -2
  43. package/dist/index.js +17 -2
  44. package/dist/keys.d.ts +43 -0
  45. package/dist/keys.js +196 -0
  46. package/dist/login.js +14 -2
  47. package/dist/openai-agents/index.d.ts +132 -0
  48. package/dist/openai-agents/index.js +791 -0
  49. package/dist/products/audit.d.ts +48 -0
  50. package/dist/products/audit.js +10 -0
  51. package/dist/products/images.d.ts +113 -7
  52. package/dist/products/images.js +186 -27
  53. package/dist/products/index.d.ts +11 -0
  54. package/dist/products/index.js +17 -1
  55. package/dist/products/observability.d.ts +186 -0
  56. package/dist/products/observability.js +117 -0
  57. package/dist/products/secrets.d.ts +40 -0
  58. package/dist/products/secrets.js +24 -0
  59. package/dist/products/switching.d.ts +96 -0
  60. package/dist/products/switching.js +25 -0
  61. package/dist/sandbox.d.ts +26 -1
  62. package/dist/sandbox.js +101 -1
  63. package/dist/ssh.d.ts +21 -0
  64. package/dist/ssh.js +324 -0
  65. package/dist/tar.d.ts +1 -0
  66. package/dist/tar.js +4 -4
  67. package/dist/tools/index.d.ts +62 -0
  68. package/dist/tools/index.js +180 -0
  69. package/dist/transport.d.ts +12 -2
  70. package/dist/transport.js +31 -4
  71. package/dist/tunnel.d.ts +70 -0
  72. package/dist/tunnel.js +316 -0
  73. package/dist/types.d.ts +57 -1
  74. package/dist/vercel/client.d.ts +40 -0
  75. package/dist/vercel/client.js +39 -0
  76. package/dist/vercel/command.d.ts +70 -0
  77. package/dist/vercel/command.js +148 -0
  78. package/dist/vercel/errors.d.ts +53 -0
  79. package/dist/vercel/errors.js +113 -0
  80. package/dist/vercel/filesystem.d.ts +150 -0
  81. package/dist/vercel/filesystem.js +248 -0
  82. package/dist/vercel/index.d.ts +30 -0
  83. package/dist/vercel/index.js +38 -0
  84. package/dist/vercel/sandbox.d.ts +317 -0
  85. package/dist/vercel/sandbox.js +726 -0
  86. package/dist/vercel/snapshot.d.ts +93 -0
  87. package/dist/vercel/snapshot.js +123 -0
  88. package/package.json +56 -4
@@ -165,8 +165,13 @@ export declare class Sandbox {
165
165
  getPublicHost(port: number): Promise<string>;
166
166
  get pty(): never;
167
167
  get git(): never;
168
- getMetrics(): Promise<never>;
169
- static getMetrics(): Promise<never>;
168
+ /** E2B's metrics, from Runtime's measured readings (`sbx.metrics()`): CPU
169
+ * as a percent of the sandbox's vCPUs and resident memory, one entry per
170
+ * host reading (every minute by default) between `start` and `end`, kept
171
+ * 24 hours at that resolution. Disk use inside the sandbox is not measured:
172
+ * `diskUsed` is null and `diskTotal` is the disk's size. */
173
+ getMetrics(opts?: MetricsOpts): Promise<E2BSandboxMetrics[]>;
174
+ static getMetrics(sandboxId: string, opts?: MetricsOpts & SandboxApiOpts): Promise<E2BSandboxMetrics[]>;
170
175
  updateNetwork(): Promise<never>;
171
176
  uploadUrl(): Promise<never>;
172
177
  downloadUrl(): Promise<never>;
@@ -181,3 +186,18 @@ export declare class SandboxPaginator {
181
186
  get nextToken(): string | undefined;
182
187
  nextItems(): Promise<SandboxInfo[]>;
183
188
  }
189
+ type MetricsOpts = {
190
+ start?: Date | number;
191
+ end?: Date | number;
192
+ };
193
+ export type E2BSandboxMetrics = {
194
+ timestamp: Date;
195
+ cpuUsedPct: number;
196
+ cpuCount: number;
197
+ memUsed: number;
198
+ memTotal: number;
199
+ /** Not measured by Runtime: null. */
200
+ diskUsed: number | null;
201
+ diskTotal: number;
202
+ };
203
+ export {};
@@ -35,8 +35,6 @@ function leaseSeconds(timeoutMs) {
35
35
  function onLeaseEnd(lifecycle) {
36
36
  if (!lifecycle)
37
37
  return "stop";
38
- if (lifecycle.autoResume)
39
- throw new NotSupportedError("Auto-resume on traffic (lifecycle.autoResume)", "Wake a paused sandbox with Sandbox.connect(id) before sending it work.");
40
38
  const action = typeof lifecycle.onTimeout === "string" ? lifecycle.onTimeout : lifecycle.onTimeout.action;
41
39
  if (typeof lifecycle.onTimeout === "object" && lifecycle.onTimeout.keepMemory === false)
42
40
  throw new NotSupportedError("A files-only pause (keepMemory: false)", "Runtime's pause keeps memory and files; leave keepMemory out.");
@@ -119,7 +117,10 @@ function infoOf(runtime) {
119
117
  cpuCount: info.vcpu,
120
118
  memoryMB: info.memoryMiB,
121
119
  envdVersion: "runtime",
122
- lifecycle: { onTimeout: info.onLeaseEnd === "stop" ? "kill" : "pause", autoResume: false },
120
+ lifecycle: {
121
+ onTimeout: info.onLeaseEnd === "stop" ? "kill" : "pause",
122
+ autoResume: info.autoWake === true,
123
+ },
123
124
  };
124
125
  }
125
126
  function unsupported(feature, alternative) {
@@ -186,6 +187,9 @@ export class Sandbox {
186
187
  const input = {
187
188
  timeoutSeconds: leaseSeconds(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
188
189
  onLeaseEnd: onLeaseEnd(opts.lifecycle),
190
+ // With a lifecycle, E2B resumes a paused sandbox on traffic only when
191
+ // asked (autoResume); Runtime's automatic wake is the same thing (0093).
192
+ ...(opts.lifecycle ? { autoWake: opts.lifecycle.autoResume === true } : {}),
189
193
  ...(opts.metadata && Object.keys(opts.metadata).length ? { labels: opts.metadata } : {}),
190
194
  ...(opts.allowInternetAccess === false ? { network: { internet: false } } : {}),
191
195
  };
@@ -347,11 +351,18 @@ export class Sandbox {
347
351
  get git() {
348
352
  throw new NotSupportedError("E2B's git module (deprecated by E2B too)", "Run git with sandbox.commands.run('git ...'); git is installed.");
349
353
  }
350
- getMetrics() {
351
- return unsupported("Sandbox metrics (getMetrics)", "Runtime reports CPU, memory and charges per sandbox in `npx withruntime usage` and runtime.usage().");
354
+ /** E2B's metrics, from Runtime's measured readings (`sbx.metrics()`): CPU
355
+ * as a percent of the sandbox's vCPUs and resident memory, one entry per
356
+ * host reading (every minute by default) between `start` and `end`, kept
357
+ * 24 hours at that resolution. Disk use inside the sandbox is not measured:
358
+ * `diskUsed` is null and `diskTotal` is the disk's size. */
359
+ async getMetrics(opts = {}) {
360
+ return e2bMetrics(this.#runtime, opts);
352
361
  }
353
- static getMetrics() {
354
- return unsupported("Sandbox metrics (getMetrics)", "Runtime reports CPU, memory and charges per sandbox in `npx withruntime usage` and runtime.usage().");
362
+ static async getMetrics(sandboxId, opts = {}) {
363
+ const client = clientFor(opts);
364
+ const runtime = await guard("sandbox", () => client.sandboxes.get(sandboxId, request(opts)));
365
+ return e2bMetrics(runtime, opts);
355
366
  }
356
367
  updateNetwork() {
357
368
  return unsupported("E2B's network rules (updateNetwork)", "Use sandbox.runtime.network.set({ internet, allow, deny }).");
@@ -456,3 +467,24 @@ export class SandboxPaginator {
456
467
  return (page?.data ?? []).map(infoOf);
457
468
  }
458
469
  }
470
+ async function e2bMetrics(runtime, opts) {
471
+ const start = opts.start === undefined ? Date.now() - 15 * 60_000 : new Date(opts.start).getTime();
472
+ const end = opts.end === undefined ? Date.now() : new Date(opts.end).getTime();
473
+ const span = Date.now() - start;
474
+ const range = span <= 15 * 60_000 ? "15m" : span <= 3_600_000 ? "1h" : span <= 6 * 3_600_000 ? "6h" : "24h";
475
+ const metrics = await runtime.metrics({ range });
476
+ return metrics.points
477
+ .filter((point) => {
478
+ const at = Date.parse(point.at);
479
+ return at >= start - metrics.stepSeconds * 1000 && at <= end && point.cpuPercent !== null;
480
+ })
481
+ .map((point) => ({
482
+ timestamp: new Date(point.at),
483
+ cpuUsedPct: point.cpuPercent,
484
+ cpuCount: metrics.vcpu,
485
+ memUsed: point.memoryBytes,
486
+ memTotal: metrics.memoryLimitBytes,
487
+ diskUsed: null,
488
+ diskTotal: metrics.diskLimitBytes,
489
+ }));
490
+ }
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- /** Every failure the SDK raises. `code` is stable and machine-readable,
2
- * `hint` says what to do next, and `requestId` is what to quote in a report
3
- * (runtime.feedback.submit, `runtime feedback`, or support). */
1
+ /** Refusals of a create that clear when a sandbox stops or pauses, or a host
2
+ * frees room: the trial's slots, an email domain's trial slots, the account's
3
+ * quota and the region's capacity. `sandboxes.create` waits them out, retrying
4
+ * with the same key and input, for up to `waitForCapacityMs`. */
5
+ export declare const WAITS_FOR_ROOM: ReadonlySet<string>;
4
6
  export declare class RuntimeError extends Error {
5
7
  readonly code: string;
6
8
  readonly status: number;
@@ -21,7 +23,11 @@ export declare class RuntimeError extends Error {
21
23
  retryAfterMs?: number;
22
24
  cause?: unknown;
23
25
  });
24
- /** Retrying this exact call (the SDK keeps the key) is safe and may work. */
26
+ /** Retrying this exact call (the SDK keeps the key) is safe and may work.
27
+ * True for no_capacity and trial_busy as well, which clear by themselves when
28
+ * a host frees room or a trial sandbox stops. `sandboxes.create` already
29
+ * waits for those (see `waitForCapacityMs`), so seeing one from a create
30
+ * means the wait ran out or was switched off. */
25
31
  get retryable(): boolean;
26
32
  toString(): string;
27
33
  }
package/dist/errors.js CHANGED
@@ -1,6 +1,21 @@
1
1
  /** Every failure the SDK raises. `code` is stable and machine-readable,
2
2
  * `hint` says what to do next, and `requestId` is what to quote in a report
3
3
  * (runtime.feedback.submit, `runtime feedback`, or support). */
4
+ /** Switched off on purpose: retrying cannot change them. */
5
+ const DELIBERATE = new Set(["fork_unavailable", "previews_unavailable", "unavailable"]);
6
+ /** Refusals that pass on their own: a host frees room, a trial slot frees. */
7
+ const PASSING = new Set(["no_capacity", "trial_busy"]);
8
+ /** Refusals of a create that clear when a sandbox stops or pauses, or a host
9
+ * frees room: the trial's slots, an email domain's trial slots, the account's
10
+ * quota and the region's capacity. `sandboxes.create` waits them out, retrying
11
+ * with the same key and input, for up to `waitForCapacityMs`. */
12
+ export const WAITS_FOR_ROOM = new Set([
13
+ "trial_busy",
14
+ "trial_domain_limit",
15
+ "trial_capacity",
16
+ "quota_exceeded",
17
+ "no_capacity",
18
+ ]);
4
19
  export class RuntimeError extends Error {
5
20
  code;
6
21
  status;
@@ -26,13 +41,18 @@ export class RuntimeError extends Error {
26
41
  if (init.retryAfterMs !== undefined)
27
42
  this.retryAfterMs = init.retryAfterMs;
28
43
  }
29
- /** Retrying this exact call (the SDK keeps the key) is safe and may work. */
44
+ /** Retrying this exact call (the SDK keeps the key) is safe and may work.
45
+ * True for no_capacity and trial_busy as well, which clear by themselves when
46
+ * a host frees room or a trial sandbox stops. `sandboxes.create` already
47
+ * waits for those (see `waitForCapacityMs`), so seeing one from a create
48
+ * means the wait ran out or was switched off. */
30
49
  get retryable() {
31
- return (this.status === 0 ||
32
- this.status === 429 ||
33
- this.status === 502 ||
34
- this.status === 503 ||
35
- this.status === 504);
50
+ if (DELIBERATE.has(this.code))
51
+ return false;
52
+ // A fork asking for more copies than the trial runs at once never fits.
53
+ if (PASSING.has(this.code))
54
+ return this.details?.field !== "count";
55
+ return [0, 429, 502, 503, 504].includes(this.status);
36
56
  }
37
57
  toString() {
38
58
  return `${this.name} [${this.code}${this.status ? ` ${this.status}` : ""}]: ${this.message}${this.hint ? `\nHint: ${this.hint}` : ""}${this.requestId ? `\nRequest: ${this.requestId}` : ""}`;
@@ -0,0 +1,39 @@
1
+ export declare const CONTEXT_LIMITS: {
2
+ /** The archive, compressed: what is uploaded. */
3
+ bytes: number;
4
+ files: number;
5
+ chunkBytes: number;
6
+ };
7
+ export type ContextFile = {
8
+ path: string;
9
+ sha256: string;
10
+ size: number;
11
+ mode: number;
12
+ };
13
+ export type PackedContext = {
14
+ archive: Uint8Array;
15
+ files: ContextFile[];
16
+ /** The .dockerignore that was applied, sent so the server applies it too. */
17
+ dockerignore?: string;
18
+ };
19
+ /** A .dockerignore as Docker reads it: `#` comments, `!` re-includes, `**`
20
+ * crosses directories, a pattern naming a directory excludes what is in it,
21
+ * and the last matching pattern decides. The same rules as the server's
22
+ * (packages/cloud/src/images/dockerfile.ts `dockerignoreFilter`). */
23
+ export declare function dockerignoreFilter(text: string | undefined): (path: string) => boolean;
24
+ /** The context of a folder: its .dockerignore (or `<Dockerfile>.dockerignore`)
25
+ * applied, `.git` left out when there is none. Symbolic links are left out:
26
+ * COPY reads files. */
27
+ export declare function packContext(folder: string, options?: {
28
+ dockerignore?: string;
29
+ dockerfile?: string;
30
+ }): Promise<PackedContext>;
31
+ /** The archive cut into the 1 MiB chunks the server stores by digest. */
32
+ export declare function chunkArchive(archive: Uint8Array): Promise<{
33
+ sha256: string;
34
+ size: number;
35
+ chunks: {
36
+ sha256: string;
37
+ bytes: Uint8Array;
38
+ }[];
39
+ }>;
@@ -0,0 +1,176 @@
1
+ import { lstat, readdir, readFile } from "node:fs/promises";
2
+ import { join, relative, resolve, sep } from "node:path";
3
+ import { gzipSync } from "node:zlib";
4
+ import { tarHeader } from "./tar.js";
5
+ /* A Dockerfile's build context, made from a folder the way `docker build`
6
+ * makes it: every file the .dockerignore does not exclude, in one gzipped tar
7
+ * archive, with a list of the files and their SHA-256 for the server to
8
+ * resolve COPY against. The archive is deterministic (sorted, no times, no
9
+ * owners), so an unchanged folder gives the same chunks and a rebuild
10
+ * uploads nothing. Node and Bun only: it reads the file system. */
11
+ export const CONTEXT_LIMITS = {
12
+ /** The archive, compressed: what is uploaded. */
13
+ bytes: 100 * 1_048_576,
14
+ files: 20_000,
15
+ chunkBytes: 1_048_576,
16
+ };
17
+ /** A .dockerignore as Docker reads it: `#` comments, `!` re-includes, `**`
18
+ * crosses directories, a pattern naming a directory excludes what is in it,
19
+ * and the last matching pattern decides. The same rules as the server's
20
+ * (packages/cloud/src/images/dockerfile.ts `dockerignoreFilter`). */
21
+ export function dockerignoreFilter(text) {
22
+ if (text === undefined || text.trim() === "")
23
+ return () => false;
24
+ const rules = [];
25
+ for (const raw of text.replace(/\r\n?/g, "\n").split("\n")) {
26
+ let line = raw.trim();
27
+ if (!line || line.startsWith("#"))
28
+ continue;
29
+ const negate = line.startsWith("!");
30
+ if (negate)
31
+ line = line.slice(1).trim();
32
+ line = normalize(line.replace(/^\/+/, "")).replace(/\/+$/, "");
33
+ if (!line || line === ".")
34
+ continue;
35
+ let source = "";
36
+ for (let i = 0; i < line.length; i++) {
37
+ const char = line[i];
38
+ if (char === "*" && line[i + 1] === "*") {
39
+ if (line[i + 2] === "/") {
40
+ source += "(?:.*/)?";
41
+ i += 2;
42
+ }
43
+ else {
44
+ source += ".*";
45
+ i += 1;
46
+ }
47
+ }
48
+ else if (char === "*")
49
+ source += "[^/]*";
50
+ else if (char === "?")
51
+ source += "[^/]";
52
+ else if (char === "[") {
53
+ const end = line.indexOf("]", i + 1);
54
+ if (end < 0)
55
+ source += "\\[";
56
+ else {
57
+ source += `[${line
58
+ .slice(i + 1, end)
59
+ .replace(/^!/, "^")
60
+ .replace(/\\/g, "\\\\")}]`;
61
+ i = end;
62
+ }
63
+ }
64
+ else if (char === "\\" && i + 1 < line.length) {
65
+ source += line[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66
+ }
67
+ else
68
+ source += char.replace(/[.+^${}()|\\]/g, "\\$&");
69
+ }
70
+ try {
71
+ rules.push({ negate, regex: new RegExp(`^${source}$`) });
72
+ }
73
+ catch {
74
+ throw new Error(`.dockerignore: invalid pattern ${JSON.stringify(raw.trim())}`);
75
+ }
76
+ }
77
+ return (path) => {
78
+ const parts = path.split("/");
79
+ let excluded = false;
80
+ for (const rule of rules) {
81
+ let hit = false;
82
+ for (let n = parts.length; n >= 1 && !hit; n--)
83
+ hit = rule.regex.test(parts.slice(0, n).join("/"));
84
+ if (hit)
85
+ excluded = !rule.negate;
86
+ }
87
+ return excluded;
88
+ };
89
+ }
90
+ /** posix.normalize, without importing a path module into this function. */
91
+ function normalize(path) {
92
+ const out = [];
93
+ for (const part of path.split("/")) {
94
+ if (part === "" || part === ".")
95
+ continue;
96
+ if (part === "..")
97
+ out.pop();
98
+ else
99
+ out.push(part);
100
+ }
101
+ return out.join("/") || ".";
102
+ }
103
+ async function sha256(bytes) {
104
+ const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
105
+ return Buffer.from(digest).toString("hex");
106
+ }
107
+ /** The context of a folder: its .dockerignore (or `<Dockerfile>.dockerignore`)
108
+ * applied, `.git` left out when there is none. Symbolic links are left out:
109
+ * COPY reads files. */
110
+ export async function packContext(folder, options = {}) {
111
+ const root = resolve(folder);
112
+ let dockerignore = options.dockerignore;
113
+ if (dockerignore === undefined)
114
+ for (const candidate of [
115
+ ...(options.dockerfile ? [`${resolve(root, options.dockerfile)}.dockerignore`] : []),
116
+ join(root, ".dockerignore"),
117
+ ]) {
118
+ dockerignore = await readFile(candidate, "utf8").catch(() => undefined);
119
+ if (dockerignore !== undefined)
120
+ break;
121
+ }
122
+ const ignored = dockerignoreFilter(dockerignore ?? ".git\n");
123
+ const reincludes = /^\s*!/m.test(dockerignore ?? "");
124
+ const files = [];
125
+ const parts = [];
126
+ let total = 0;
127
+ async function walk(directory) {
128
+ for (const name of (await readdir(directory)).sort()) {
129
+ const full = join(directory, name);
130
+ const path = relative(root, full).split(sep).join("/");
131
+ const info = await lstat(full);
132
+ if (info.isDirectory()) {
133
+ // A directory excluded outright is not walked, unless a later `!`
134
+ // rule could bring something in it back.
135
+ if (ignored(path) && !reincludes)
136
+ continue;
137
+ await walk(full);
138
+ }
139
+ else if (info.isFile()) {
140
+ if (ignored(path))
141
+ continue;
142
+ if (files.length >= CONTEXT_LIMITS.files)
143
+ throw new Error(`The build context has more than ${CONTEXT_LIMITS.files} files. Leave some out with a .dockerignore.`);
144
+ const data = new Uint8Array(await readFile(full));
145
+ total += data.length;
146
+ const mode = info.mode & 0o777;
147
+ files.push({ path, sha256: await sha256(data), size: data.length, mode });
148
+ parts.push(tarHeader(path, data.length, mode, "0", "", 0), data);
149
+ const pad = (512 - (data.length % 512)) % 512;
150
+ if (pad)
151
+ parts.push(new Uint8Array(pad));
152
+ }
153
+ }
154
+ }
155
+ await walk(root);
156
+ parts.push(new Uint8Array(1024));
157
+ const tar = new Uint8Array(parts.reduce((n, p) => n + p.length, 0) || 1024);
158
+ let offset = 0;
159
+ for (const part of parts) {
160
+ tar.set(part, offset);
161
+ offset += part.length;
162
+ }
163
+ const archive = new Uint8Array(gzipSync(tar, { level: 6 }));
164
+ if (archive.length > CONTEXT_LIMITS.bytes)
165
+ throw new Error(`The build context is ${Math.ceil(archive.length / 1_048_576)} MiB compressed (${Math.ceil(total / 1_048_576)} MiB of files); the most is ${CONTEXT_LIMITS.bytes / 1_048_576} MiB. Leave build outputs and dependencies out with a .dockerignore.`);
166
+ return { archive, files, ...(dockerignore === undefined ? {} : { dockerignore }) };
167
+ }
168
+ /** The archive cut into the 1 MiB chunks the server stores by digest. */
169
+ export async function chunkArchive(archive) {
170
+ const chunks = [];
171
+ for (let at = 0; at < archive.length; at += CONTEXT_LIMITS.chunkBytes) {
172
+ const bytes = archive.subarray(at, Math.min(archive.length, at + CONTEXT_LIMITS.chunkBytes));
173
+ chunks.push({ sha256: await sha256(bytes), bytes });
174
+ }
175
+ return { sha256: await sha256(archive), size: archive.length, chunks };
176
+ }
package/dist/index.d.ts CHANGED
@@ -7,12 +7,16 @@
7
7
  import type { Runtime } from "./client.js";
8
8
  import { type RuntimeOptions } from "./client.js";
9
9
  import { Sandbox as SandboxClass } from "./sandbox.js";
10
- import type { CreateSandbox } from "./types.js";
10
+ import type { CreateSandbox, KeepAliveOptions } from "./types.js";
11
11
  import type { RequestOptions } from "./transport.js";
12
12
  export { Runtime, Sandboxes, FeedbackApi, SupportApi, type RuntimeOptions, type SupportReply, } from "./client.js";
13
13
  export { Files, Process, Processes, Terminal } from "./sandbox.js";
14
+ export { Tunnel, TunnelStream, type PortForward } from "./tunnel.js";
14
15
  export { Page } from "./page.js";
15
16
  export type { KeyLimits } from "./products/limits.js";
17
+ export type { AuditEvent, AuditPage } from "./products/audit.js";
18
+ export type { Secret, SetSecret } from "./products/secrets.js";
19
+ export { verifyWebhook, WebhookVerificationError, type MetricPoint, type MetricRange, type OtelExport, type RuntimeEvent, type SandboxMetrics, type Webhook, type WebhookDelivery, type WebhookEventType, } from "./products/observability.js";
16
20
  export { Snapshots, type Snapshot, type SnapshotOptions } from "./snapshots.js";
17
21
  export { Transport, VERSION, apiOrigin, type RequestOptions, type Call } from "./transport.js";
18
22
  export * from "./errors.js";
@@ -20,9 +24,22 @@ export type * from "./types.js";
20
24
  /** A sandbox. `Sandbox.create()` uses RUNTIME_API_KEY; `runtime.sandboxes`
21
25
  * does the same with an explicit client. */
22
26
  export declare class Sandbox extends SandboxClass {
23
- /** Creates a sandbox (every field optional) and waits until it is running. */
27
+ /** Creates a sandbox (every field optional) and waits until it is running.
28
+ * `keepAlive: true` (or its options) extends its lease in the background
29
+ * until you stop it; see `sandbox.keepAlive()`. */
24
30
  static create(input?: CreateSandbox & {
25
31
  wait?: boolean;
32
+ keepAlive?: boolean | KeepAliveOptions;
33
+ }, options?: RequestOptions & {
34
+ client?: Runtime;
35
+ }): Promise<SandboxClass>;
36
+ /** The sandbox named `name` in this account, ready to use: running as it
37
+ * is, woken if it is paused, restarted if it is stopped and persistent, or
38
+ * created with `input` when no sandbox has the name. `sandbox.info.reused`
39
+ * says which. The other fields apply only when it is created. */
40
+ static getOrCreate(name: string, input?: Omit<CreateSandbox, "name" | "getOrCreate"> & {
41
+ wait?: boolean;
42
+ keepAlive?: boolean | KeepAliveOptions;
26
43
  }, options?: RequestOptions & {
27
44
  client?: Runtime;
28
45
  }): Promise<SandboxClass>;
package/dist/index.js CHANGED
@@ -2,17 +2,32 @@ import { defaultClient } from "./client.js";
2
2
  import { Sandbox as SandboxClass } from "./sandbox.js";
3
3
  export { Runtime, Sandboxes, FeedbackApi, SupportApi, } from "./client.js";
4
4
  export { Files, Process, Processes, Terminal } from "./sandbox.js";
5
+ export { Tunnel, TunnelStream } from "./tunnel.js";
5
6
  export { Page } from "./page.js";
7
+ export { verifyWebhook, WebhookVerificationError, } from "./products/observability.js";
6
8
  export { Snapshots } from "./snapshots.js";
7
9
  export { Transport, VERSION, apiOrigin } from "./transport.js";
8
10
  export * from "./errors.js";
9
11
  /** A sandbox. `Sandbox.create()` uses RUNTIME_API_KEY; `runtime.sandboxes`
10
12
  * does the same with an explicit client. */
11
13
  export class Sandbox extends SandboxClass {
12
- /** Creates a sandbox (every field optional) and waits until it is running. */
14
+ /** Creates a sandbox (every field optional) and waits until it is running.
15
+ * `keepAlive: true` (or its options) extends its lease in the background
16
+ * until you stop it; see `sandbox.keepAlive()`. */
13
17
  static async create(input = {}, options = {}) {
14
18
  const { client, ...rest } = options;
15
- return (client ?? defaultClient()).sandboxes.create(input, rest);
19
+ const { keepAlive, ...body } = input;
20
+ const sandbox = await (client ?? defaultClient()).sandboxes.create(body, rest);
21
+ if (keepAlive)
22
+ sandbox.keepAlive(keepAlive === true ? {} : keepAlive);
23
+ return sandbox;
24
+ }
25
+ /** The sandbox named `name` in this account, ready to use: running as it
26
+ * is, woken if it is paused, restarted if it is stopped and persistent, or
27
+ * created with `input` when no sandbox has the name. `sandbox.info.reused`
28
+ * says which. The other fields apply only when it is created. */
29
+ static async getOrCreate(name, input = {}, options = {}) {
30
+ return Sandbox.create({ ...input, name, getOrCreate: true }, options);
16
31
  }
17
32
  /** Reconnects to an existing sandbox by id. */
18
33
  static async connect(id, options = {}) {
package/dist/keys.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ export type KeyAccess = "full" | "read";
2
+ export type KeyRequest = {
3
+ /** The key's name, and its agent's. */
4
+ name: string;
5
+ access: KeyAccess;
6
+ /** The most its agent may spend in any 24 hours, or null for none. */
7
+ dailyLimitMicros: number | null;
8
+ };
9
+ export type CreatedKey = KeyRequest & {
10
+ key: string;
11
+ keyId: string;
12
+ agentId: string;
13
+ orgId: string;
14
+ /** Always until revoked, as on the keys page. */
15
+ expires: null;
16
+ };
17
+ export type KeyRequestOptions = {
18
+ fetch?: typeof fetch;
19
+ sleep?: (ms: number) => Promise<void>;
20
+ /** Where the link, the code and progress go: standard error, never stdout. */
21
+ notify?: (message: string) => void;
22
+ openBrowser?: (url: string) => Promise<void>;
23
+ /** Whether a person is at this terminal, so a browser is worth opening. */
24
+ interactive?: boolean;
25
+ /** What this computer calls itself, shown to the owner beside the code. */
26
+ machine?: string;
27
+ /** Runs `cancel` if the command is interrupted before the key arrives;
28
+ * returns a function that stops listening. */
29
+ onInterrupt?: (cancel: () => Promise<void>) => () => void;
30
+ };
31
+ /** Whole microdollars from a daily limit typed in dollars ("25", "7.5",
32
+ * "$1,000.10"), read as digits rather than through a float, from $0.01 to
33
+ * $1,000,000: the keys page's own rule (apps/cloud-web/src/app/account/keys/limit.ts). */
34
+ export declare function dailyLimitMicros(typed: string): number;
35
+ /**
36
+ * Asks for a key, waits for an owner to approve it in the browser, and hands
37
+ * it back with `confirm`, which tells the website it arrived. Call `confirm`
38
+ * after the key is shown: it clears the sealed copy the website held for
39
+ * delivery and turns the owner's page to "Key created".
40
+ */
41
+ export declare function createKey(request: KeyRequest, env: NodeJS.ProcessEnv, options?: KeyRequestOptions): Promise<CreatedKey & {
42
+ confirm: () => Promise<void>;
43
+ }>;