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.
- package/DAYTONA.md +129 -0
- package/E2B.md +3 -3
- package/README.md +47 -9
- package/VERCEL.md +127 -0
- package/dist/ai/index.d.ts +33 -0
- package/dist/ai/index.js +38 -0
- package/dist/claude-agent-sdk/index.d.ts +7 -0
- package/dist/claude-agent-sdk/index.js +65 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +959 -102
- package/dist/client.d.ts +25 -2
- package/dist/client.js +14 -1
- package/dist/credentials.d.ts +10 -0
- package/dist/credentials.js +56 -1
- package/dist/daytona/client.d.ts +44 -0
- package/dist/daytona/client.js +40 -0
- package/dist/daytona/context.d.ts +31 -0
- package/dist/daytona/context.js +25 -0
- package/dist/daytona/daytona.d.ts +185 -0
- package/dist/daytona/daytona.js +416 -0
- package/dist/daytona/errors.d.ts +106 -0
- package/dist/daytona/errors.js +182 -0
- package/dist/daytona/filesystem.d.ts +84 -0
- package/dist/daytona/filesystem.js +206 -0
- package/dist/daytona/git.d.ts +33 -0
- package/dist/daytona/git.js +128 -0
- package/dist/daytona/image.d.ts +45 -0
- package/dist/daytona/image.js +140 -0
- package/dist/daytona/index.d.ts +50 -0
- package/dist/daytona/index.js +50 -0
- package/dist/daytona/process.d.ts +86 -0
- package/dist/daytona/process.js +341 -0
- package/dist/daytona/sandbox.d.ts +196 -0
- package/dist/daytona/sandbox.js +448 -0
- package/dist/e2b/commands.js +6 -2
- package/dist/e2b/sandbox.d.ts +22 -2
- package/dist/e2b/sandbox.js +39 -7
- package/dist/errors.d.ts +10 -4
- package/dist/errors.js +26 -6
- package/dist/image-context.d.ts +39 -0
- package/dist/image-context.js +176 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.js +17 -2
- package/dist/keys.d.ts +43 -0
- package/dist/keys.js +196 -0
- package/dist/login.js +14 -2
- package/dist/openai-agents/index.d.ts +132 -0
- package/dist/openai-agents/index.js +791 -0
- package/dist/products/audit.d.ts +48 -0
- package/dist/products/audit.js +10 -0
- package/dist/products/images.d.ts +113 -7
- package/dist/products/images.js +186 -27
- package/dist/products/index.d.ts +11 -0
- package/dist/products/index.js +17 -1
- package/dist/products/observability.d.ts +186 -0
- package/dist/products/observability.js +117 -0
- package/dist/products/secrets.d.ts +40 -0
- package/dist/products/secrets.js +24 -0
- package/dist/products/switching.d.ts +96 -0
- package/dist/products/switching.js +25 -0
- package/dist/sandbox.d.ts +26 -1
- package/dist/sandbox.js +101 -1
- package/dist/ssh.d.ts +21 -0
- package/dist/ssh.js +324 -0
- package/dist/tar.d.ts +1 -0
- package/dist/tar.js +4 -4
- package/dist/tools/index.d.ts +62 -0
- package/dist/tools/index.js +180 -0
- package/dist/transport.d.ts +12 -2
- package/dist/transport.js +31 -4
- package/dist/tunnel.d.ts +70 -0
- package/dist/tunnel.js +316 -0
- package/dist/types.d.ts +57 -1
- package/dist/vercel/client.d.ts +40 -0
- package/dist/vercel/client.js +39 -0
- package/dist/vercel/command.d.ts +70 -0
- package/dist/vercel/command.js +148 -0
- package/dist/vercel/errors.d.ts +53 -0
- package/dist/vercel/errors.js +113 -0
- package/dist/vercel/filesystem.d.ts +150 -0
- package/dist/vercel/filesystem.js +248 -0
- package/dist/vercel/index.d.ts +30 -0
- package/dist/vercel/index.js +38 -0
- package/dist/vercel/sandbox.d.ts +317 -0
- package/dist/vercel/sandbox.js +726 -0
- package/dist/vercel/snapshot.d.ts +93 -0
- package/dist/vercel/snapshot.js +123 -0
- package/package.json +56 -4
package/dist/e2b/sandbox.d.ts
CHANGED
|
@@ -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
|
-
|
|
169
|
-
|
|
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 {};
|
package/dist/e2b/sandbox.js
CHANGED
|
@@ -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: {
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
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
|
-
|
|
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
|
+
}>;
|