@promigence/sdk 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +54 -0
- package/dist/blake3.d.ts +10 -0
- package/dist/blake3.js +111 -0
- package/dist/bundle.d.ts +96 -0
- package/dist/bundle.js +227 -0
- package/dist/ca-guard.d.ts +1 -0
- package/dist/ca-guard.js +166 -0
- package/dist/caller.d.ts +73 -0
- package/dist/caller.js +47 -0
- package/dist/cjs/blake3.d.ts +10 -0
- package/dist/cjs/blake3.js +116 -0
- package/dist/cjs/bundle.d.ts +96 -0
- package/dist/cjs/bundle.js +242 -0
- package/dist/cjs/ca-guard.d.ts +1 -0
- package/dist/cjs/ca-guard.js +174 -0
- package/dist/cjs/caller.d.ts +73 -0
- package/dist/cjs/caller.js +57 -0
- package/dist/cjs/client.d.ts +3015 -0
- package/dist/cjs/client.js +2617 -0
- package/dist/cjs/command-errors.d.ts +41 -0
- package/dist/cjs/command-errors.js +43 -0
- package/dist/cjs/context.d.ts +119 -0
- package/dist/cjs/context.js +598 -0
- package/dist/cjs/core.d.ts +285 -0
- package/dist/cjs/core.js +290 -0
- package/dist/cjs/credstore.d.ts +90 -0
- package/dist/cjs/credstore.js +219 -0
- package/dist/cjs/dispatcher.d.ts +58 -0
- package/dist/cjs/dispatcher.js +91 -0
- package/dist/cjs/github.d.ts +158 -0
- package/dist/cjs/github.js +84 -0
- package/dist/cjs/gitlab.d.ts +104 -0
- package/dist/cjs/gitlab.js +24 -0
- package/dist/cjs/handles.d.ts +82 -0
- package/dist/cjs/handles.js +139 -0
- package/dist/cjs/hint.d.ts +1 -0
- package/dist/cjs/hint.js +10 -0
- package/dist/cjs/http2.d.ts +206 -0
- package/dist/cjs/http2.js +1932 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/keyed.gen.d.ts +1 -0
- package/dist/cjs/keyed.gen.js +10 -0
- package/dist/cjs/models.gen.d.ts +6162 -0
- package/dist/cjs/models.gen.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/proxy.d.ts +40 -0
- package/dist/cjs/proxy.js +257 -0
- package/dist/cjs/pty.d.ts +171 -0
- package/dist/cjs/pty.js +504 -0
- package/dist/cjs/sandbox.d.ts +537 -0
- package/dist/cjs/sandbox.js +736 -0
- package/dist/cjs/ssh.d.ts +25 -0
- package/dist/cjs/ssh.js +117 -0
- package/dist/cjs/trace.d.ts +33 -0
- package/dist/cjs/trace.js +88 -0
- package/dist/cjs/transport-worker.d.ts +1 -0
- package/dist/cjs/transport-worker.js +468 -0
- package/dist/cjs/warm.d.ts +1 -0
- package/dist/cjs/warm.js +43 -0
- package/dist/client.d.ts +3015 -0
- package/dist/client.js +2583 -0
- package/dist/command-errors.d.ts +41 -0
- package/dist/command-errors.js +36 -0
- package/dist/context.d.ts +119 -0
- package/dist/context.js +588 -0
- package/dist/core.d.ts +285 -0
- package/dist/core.js +266 -0
- package/dist/credstore.d.ts +90 -0
- package/dist/credstore.js +206 -0
- package/dist/dispatcher.d.ts +58 -0
- package/dist/dispatcher.js +86 -0
- package/dist/github.d.ts +158 -0
- package/dist/github.js +78 -0
- package/dist/gitlab.d.ts +104 -0
- package/dist/gitlab.js +20 -0
- package/dist/handles.d.ts +82 -0
- package/dist/handles.js +135 -0
- package/dist/hint.d.ts +1 -0
- package/dist/hint.js +7 -0
- package/dist/http2.d.ts +206 -0
- package/dist/http2.js +1921 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +15 -0
- package/dist/keyed.gen.d.ts +1 -0
- package/dist/keyed.gen.js +7 -0
- package/dist/models.gen.d.ts +6162 -0
- package/dist/models.gen.js +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.js +249 -0
- package/dist/pty.d.ts +171 -0
- package/dist/pty.js +496 -0
- package/dist/sandbox.d.ts +537 -0
- package/dist/sandbox.js +725 -0
- package/dist/ssh.d.ts +25 -0
- package/dist/ssh.js +114 -0
- package/dist/trace.d.ts +33 -0
- package/dist/trace.js +80 -0
- package/dist/transport-worker.d.ts +1 -0
- package/dist/transport-worker.js +466 -0
- package/dist/warm.d.ts +1 -0
- package/dist/warm.js +40 -0
- package/package.json +54 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Sandbox = exports.DEFAULT_SNAPSHOT = exports.DEFAULT_PTY_CMD = void 0;
|
|
4
|
+
exports.shellQuote = shellQuote;
|
|
5
|
+
exports.ptyCommand = ptyCommand;
|
|
6
|
+
exports.toEntryInfo = toEntryInfo;
|
|
7
|
+
exports.toEntryInfoFromPath = toEntryInfoFromPath;
|
|
8
|
+
exports.imageSnapshot = imageSnapshot;
|
|
9
|
+
exports.spendCapFromEnv = spendCapFromEnv;
|
|
10
|
+
exports.resolveSpendCap = resolveSpendCap;
|
|
11
|
+
const node_util_1 = require("node:util");
|
|
12
|
+
const core_js_1 = require("./core.js");
|
|
13
|
+
const client_js_1 = require("./client.js");
|
|
14
|
+
const handles_js_1 = require("./handles.js");
|
|
15
|
+
const command_errors_js_1 = require("./command-errors.js");
|
|
16
|
+
const hint_js_1 = require("./hint.js");
|
|
17
|
+
const EXEC_TIMEOUT_DEFAULT_MS = 15 * 60_000;
|
|
18
|
+
function textSink(cb) {
|
|
19
|
+
if (!cb)
|
|
20
|
+
return undefined;
|
|
21
|
+
const dec = new TextDecoder();
|
|
22
|
+
return {
|
|
23
|
+
push: (b) => { const t = dec.decode(b, { stream: true }); if (t)
|
|
24
|
+
cb(t); },
|
|
25
|
+
end: () => { const t = dec.decode(); if (t)
|
|
26
|
+
cb(t); },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let uploadSeq = 0;
|
|
30
|
+
exports.DEFAULT_PTY_CMD = "TERM=${TERM:-xterm-256color} exec /bin/sh -l";
|
|
31
|
+
function shellQuote(s) {
|
|
32
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
33
|
+
}
|
|
34
|
+
function ptyCommand(opts) {
|
|
35
|
+
const envs = Object.entries(opts.envs ?? {});
|
|
36
|
+
if (!opts.cwd && envs.length === 0)
|
|
37
|
+
return opts.cmd;
|
|
38
|
+
const parts = [];
|
|
39
|
+
if (opts.cwd)
|
|
40
|
+
parts.push(`cd ${shellQuote(opts.cwd)} &&`);
|
|
41
|
+
for (const [k, v] of envs) {
|
|
42
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {
|
|
43
|
+
throw new core_js_1.InvalidArgumentError(`env name ${JSON.stringify(k)} is not a valid variable name`, "Use [A-Za-z_][A-Za-z0-9_]*.");
|
|
44
|
+
}
|
|
45
|
+
parts.push(`export ${k}=${shellQuote(v)};`);
|
|
46
|
+
}
|
|
47
|
+
parts.push(opts.cmd ?? exports.DEFAULT_PTY_CMD);
|
|
48
|
+
return parts.join(" ");
|
|
49
|
+
}
|
|
50
|
+
function permString(mode) {
|
|
51
|
+
const r = (m) => `${m & 4 ? "r" : "-"}${m & 2 ? "w" : "-"}${m & 1 ? "x" : "-"}`;
|
|
52
|
+
return r((mode >> 6) & 7) + r((mode >> 3) & 7) + r(mode & 7);
|
|
53
|
+
}
|
|
54
|
+
function toEntryInfo(e) {
|
|
55
|
+
const mode = parseInt(e.mode, 8) || 0;
|
|
56
|
+
return {
|
|
57
|
+
name: e.name,
|
|
58
|
+
path: e.path,
|
|
59
|
+
type: e.is_dir ? "dir" : "file",
|
|
60
|
+
size: e.size,
|
|
61
|
+
mode,
|
|
62
|
+
permissions: permString(mode),
|
|
63
|
+
modifiedTime: new Date(Math.floor(e.mtime_unix_ns / 1e6)),
|
|
64
|
+
isSymlink: e.is_symlink,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function toEntryInfoFromPath(f) {
|
|
68
|
+
const at = f.path.lastIndexOf("/");
|
|
69
|
+
return toEntryInfo({ ...f, name: at < 0 ? f.path : f.path.slice(at + 1) });
|
|
70
|
+
}
|
|
71
|
+
function buildIsFree(b) {
|
|
72
|
+
return b.deduped === true || b.joined === true || b.idempotent_replay === true || b.state === "verified" || b.state === "built";
|
|
73
|
+
}
|
|
74
|
+
function buildNotice(image) {
|
|
75
|
+
return (b) => {
|
|
76
|
+
if (buildIsFree(b))
|
|
77
|
+
return;
|
|
78
|
+
const price = typeof b.build_price === "number"
|
|
79
|
+
? typeof b.per_second_usd === "number"
|
|
80
|
+
? `billed per second while it builds ($${Number(b.per_second_usd.toPrecision(12))}/s), at most $${b.build_price.toFixed(2)}`
|
|
81
|
+
: `build_price $${b.build_price.toFixed(2)}, charged once it verifies`
|
|
82
|
+
: "billed at your plan's rates";
|
|
83
|
+
process.emitWarning(`building a snapshot of ${image}: ${price} (later creates from this image reuse it free)`, { code: "PROMIGENCE_BUILD" });
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildNotAllowed(image) {
|
|
87
|
+
return new core_js_1.InvalidArgumentError(`Sandbox.create({ image: ${JSON.stringify(image)} }) builds that image's snapshot unless one already exists, and a new build is billed per second while it runs (\`promigence pricing\` shows the rates; reusing a snapshot already built is free). Nothing was sent.`, `To allow the build: Sandbox.create({ image: ${JSON.stringify(image)}, allowBuild: true }). Or build it once, ` +
|
|
88
|
+
`\`promigence snapshot create --image ${(0, hint_js_1.hintArg)(image)} --alias NAME\`, and create from its name: Sandbox.create("NAME").`);
|
|
89
|
+
}
|
|
90
|
+
async function imageSnapshot(client, opts, hooks = {}) {
|
|
91
|
+
const create = { image: opts.image };
|
|
92
|
+
if (opts.setup !== undefined)
|
|
93
|
+
create.setup = opts.setup;
|
|
94
|
+
if (opts.verify !== undefined)
|
|
95
|
+
create.verify = opts.verify;
|
|
96
|
+
if (opts.start !== undefined)
|
|
97
|
+
create.start = opts.start;
|
|
98
|
+
if (opts.tier !== undefined)
|
|
99
|
+
create.tier = opts.tier;
|
|
100
|
+
if (opts.alias !== undefined)
|
|
101
|
+
create.alias = opts.alias;
|
|
102
|
+
if (opts.registrySecret !== undefined)
|
|
103
|
+
create.registrySecret = opts.registrySecret;
|
|
104
|
+
let build = (await client.snapshots.create(create)) ?? {};
|
|
105
|
+
if (hooks.onBuild)
|
|
106
|
+
await hooks.onBuild(build);
|
|
107
|
+
const buildId = build.build_id;
|
|
108
|
+
if (build.state === "queued" || build.state === "running") {
|
|
109
|
+
if (!buildId) {
|
|
110
|
+
throw new core_js_1.ApiError({
|
|
111
|
+
code: "internal", message: "POST /snapshots started a build but returned no build_id",
|
|
112
|
+
fix: "Not your fault. Re-run; if it repeats, include the request_id when reporting.", exitCode: 1,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const bound = opts.buildTimeoutMs ?? core_js_1.IMAGE_BUILD_TIMEOUT_MS;
|
|
116
|
+
const abortedBuild = () => new core_js_1.TimeoutError(`Sandbox.create was aborted by the caller's signal while image build ${buildId} ran; no sandbox was started, and the build goes on`, `Nothing to fix: the caller aborted it. The build is charged if it verifies, and later creates from this image reuse it: \`promigence snapshot status ${(0, hint_js_1.hintArg)(buildId)} --wait\`.`);
|
|
117
|
+
let waited;
|
|
118
|
+
try {
|
|
119
|
+
waited = await client.snapshots.waitBuild(buildId, { waitTimeoutMs: bound, ...(hooks.signal ? { signal: hooks.signal } : {}) });
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (hooks.signal?.aborted)
|
|
123
|
+
throw abortedBuild();
|
|
124
|
+
throw e;
|
|
125
|
+
}
|
|
126
|
+
if (hooks.signal?.aborted)
|
|
127
|
+
throw abortedBuild();
|
|
128
|
+
if (waited.timedOut) {
|
|
129
|
+
throw new core_js_1.TimeoutError(`image build ${buildId} was still running after ${bound} ms; nothing was cancelled`, `Wait for it: \`promigence snapshot status ${(0, hint_js_1.hintArg)(buildId)} --wait\`, then call Sandbox.create({ image }) again — the same digest reuses the build.`);
|
|
130
|
+
}
|
|
131
|
+
build = waited.build ?? {};
|
|
132
|
+
}
|
|
133
|
+
const state = build.state;
|
|
134
|
+
if (build.snapshot_id && state !== "rejected" && state !== "failed")
|
|
135
|
+
return build.snapshot_id;
|
|
136
|
+
const err = build.error && typeof build.error === "object" ? build.error : {};
|
|
137
|
+
throw (0, client_js_1.errorFor)(err.code ?? "snapshot_failed", err.message ?? `image build ${buildId ?? "?"} ended ${state ?? "without a state"} with no snapshot`, buildId ? `Read the build log: \`promigence snapshot status ${(0, hint_js_1.hintArg)(buildId)} --log 80\`.` : "Re-run; if it repeats, include the request_id when reporting.");
|
|
138
|
+
}
|
|
139
|
+
function deferred() {
|
|
140
|
+
let resolve;
|
|
141
|
+
let reject;
|
|
142
|
+
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
143
|
+
promise.catch(() => { });
|
|
144
|
+
const d = { promise, settled: false, resolve: (v) => { d.settled = true; resolve(v); }, reject: (e) => { d.settled = true; reject(e); } };
|
|
145
|
+
return d;
|
|
146
|
+
}
|
|
147
|
+
async function abandonQueued(client, sandboxId, runId, bound) {
|
|
148
|
+
const ids = { sandboxId, runId };
|
|
149
|
+
try {
|
|
150
|
+
await client.runs.kill(runId);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return new core_js_1.TimeoutError(`sandbox ${sandboxId} was still queued after ${bound} ms, and cancelling it failed: it may still start`, `Cancel it: \`promigence run kill ${(0, hint_js_1.hintArg)(runId)}\`, or keep it: \`promigence run status ${(0, hint_js_1.hintArg)(runId)} --wait\`.`, ids);
|
|
154
|
+
}
|
|
155
|
+
return new core_js_1.TimeoutError(`sandbox ${sandboxId} was still queued after ${bound} ms; it was cancelled, so it will not start later`, "Create again, or wait longer with `waitTimeoutMs`. A sandbox cancelled while queued is not billed.", ids);
|
|
156
|
+
}
|
|
157
|
+
const CREATE_CLEANUP_MS = 10_000;
|
|
158
|
+
async function endInterruptedCreate(client, runId, sent) {
|
|
159
|
+
try {
|
|
160
|
+
let id = runId;
|
|
161
|
+
if (id === undefined && sent !== undefined) {
|
|
162
|
+
const { waitMs: _park, signal: _gone, ...again } = sent;
|
|
163
|
+
id = (await client.runs.start({ ...again, signal: AbortSignal.timeout(CREATE_CLEANUP_MS) })).run_id;
|
|
164
|
+
}
|
|
165
|
+
if (id)
|
|
166
|
+
await client.runs.kill(id);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const creatingNow = new Set();
|
|
172
|
+
let sigintHeld = false;
|
|
173
|
+
function onSigint() {
|
|
174
|
+
sigintHeld = false;
|
|
175
|
+
const cleanups = [...creatingNow];
|
|
176
|
+
creatingNow.clear();
|
|
177
|
+
if (process.listenerCount("SIGINT") > 0)
|
|
178
|
+
return;
|
|
179
|
+
void Promise.allSettled(cleanups.map((f) => f())).finally(() => { process.kill(process.pid, "SIGINT"); });
|
|
180
|
+
}
|
|
181
|
+
function holdSigint(cleanup) {
|
|
182
|
+
if (typeof process === "undefined" || typeof process.listenerCount !== "function")
|
|
183
|
+
return () => { };
|
|
184
|
+
if (process.listenerCount("SIGINT") - (sigintHeld ? 1 : 0) > 0)
|
|
185
|
+
return () => { };
|
|
186
|
+
creatingNow.add(cleanup);
|
|
187
|
+
if (!sigintHeld) {
|
|
188
|
+
process.once("SIGINT", onSigint);
|
|
189
|
+
sigintHeld = true;
|
|
190
|
+
}
|
|
191
|
+
return () => {
|
|
192
|
+
creatingNow.delete(cleanup);
|
|
193
|
+
if (creatingNow.size === 0 && sigintHeld) {
|
|
194
|
+
process.removeListener("SIGINT", onSigint);
|
|
195
|
+
sigintHeld = false;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
exports.DEFAULT_SNAPSHOT = "promigence/base";
|
|
200
|
+
function spendCapFromEnv(spendCap) {
|
|
201
|
+
if (spendCap !== undefined)
|
|
202
|
+
return spendCap;
|
|
203
|
+
const env = process.env["PROMIGENCE_SPEND_CAP"];
|
|
204
|
+
if (env !== undefined && env !== "" && Number.isFinite(Number(env)))
|
|
205
|
+
return Number(env);
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
async function resolveSpendCap(client, spendCap) {
|
|
209
|
+
if (spendCap !== undefined)
|
|
210
|
+
return spendCap;
|
|
211
|
+
const env = process.env["PROMIGENCE_SPEND_CAP"];
|
|
212
|
+
if (env !== undefined && env !== "" && Number.isFinite(Number(env)))
|
|
213
|
+
return Number(env);
|
|
214
|
+
const me = await client.me();
|
|
215
|
+
if (typeof me.default_spend_cap === "number")
|
|
216
|
+
return me.default_spend_cap;
|
|
217
|
+
throw new core_js_1.InvalidArgumentError("no spend cap: every run must be capped before it starts", "Pass { spendCap: 1 } (USD), set PROMIGENCE_SPEND_CAP, or ask for an org default_spend_cap (GET /me).");
|
|
218
|
+
}
|
|
219
|
+
class Sandbox {
|
|
220
|
+
sandboxId;
|
|
221
|
+
runId;
|
|
222
|
+
snapshot;
|
|
223
|
+
#client;
|
|
224
|
+
#envs;
|
|
225
|
+
timeoutMs;
|
|
226
|
+
dirty = false;
|
|
227
|
+
commands;
|
|
228
|
+
pty;
|
|
229
|
+
ports;
|
|
230
|
+
terminals = new Map();
|
|
231
|
+
files;
|
|
232
|
+
constructor(init) {
|
|
233
|
+
this.sandboxId = init.sandboxId;
|
|
234
|
+
this.runId = init.runId;
|
|
235
|
+
this.snapshot = init.snapshot;
|
|
236
|
+
this.#client = init.client;
|
|
237
|
+
this.#envs = init.envs;
|
|
238
|
+
this.timeoutMs = init.timeoutMs;
|
|
239
|
+
const runBackground = async (cmd, opts) => {
|
|
240
|
+
this.dirty = true;
|
|
241
|
+
const env = { ...(this.#envs ?? {}), ...(opts.envs ?? {}) };
|
|
242
|
+
const rec = await this.#client.sandboxes.execBackground(this.sandboxId, {
|
|
243
|
+
sh: cmd,
|
|
244
|
+
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
245
|
+
...(Object.keys(env).length === 0 ? {} : { env }),
|
|
246
|
+
...(opts.user === undefined ? {} : { user: opts.user }),
|
|
247
|
+
...(opts.stdin ? { stdin: true } : {}),
|
|
248
|
+
...(opts.timeoutMs === undefined || opts.timeoutMs === 0 ? {} : { timeoutMs: opts.timeoutMs }),
|
|
249
|
+
...(opts.maxOutputBytes === undefined ? {} : { maxOutputBytes: opts.maxOutputBytes }),
|
|
250
|
+
});
|
|
251
|
+
return new handles_js_1.CommandHandle(this.#client, rec, {
|
|
252
|
+
...(opts.onStdout ? { onStdout: opts.onStdout } : {}),
|
|
253
|
+
...(opts.onStderr ? { onStderr: opts.onStderr } : {}),
|
|
254
|
+
...(opts.timeoutMs === undefined || opts.timeoutMs === 0 ? {} : { timeoutMs: opts.timeoutMs }),
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
this.commands = {
|
|
258
|
+
run: (async (cmd, opts = {}) => {
|
|
259
|
+
if (opts.background)
|
|
260
|
+
return runBackground(cmd, opts);
|
|
261
|
+
const fg = opts;
|
|
262
|
+
this.dirty = true;
|
|
263
|
+
const env = this.#envs === undefined && fg.envs === undefined ? undefined : { ...(this.#envs ?? {}), ...(fg.envs ?? {}) };
|
|
264
|
+
const bound = fg.timeoutMs === undefined || fg.timeoutMs === 0 ? (this.timeoutMs || undefined) : fg.timeoutMs;
|
|
265
|
+
const exec = { sh: cmd };
|
|
266
|
+
if (fg.cwd !== undefined)
|
|
267
|
+
exec.cwd = fg.cwd;
|
|
268
|
+
if (env !== undefined && Object.keys(env).length !== 0)
|
|
269
|
+
exec.env = env;
|
|
270
|
+
if (fg.user !== undefined)
|
|
271
|
+
exec.user = fg.user;
|
|
272
|
+
if (bound !== undefined)
|
|
273
|
+
exec.timeoutMs = bound;
|
|
274
|
+
if (fg.maxOutputBytes !== undefined)
|
|
275
|
+
exec.maxOutputBytes = fg.maxOutputBytes;
|
|
276
|
+
let res;
|
|
277
|
+
const out = textSink(fg.onStdout);
|
|
278
|
+
const err = textSink(fg.onStderr);
|
|
279
|
+
if (out || err) {
|
|
280
|
+
res = await this.#client.sandboxes.execStream(this.sandboxId, exec, {
|
|
281
|
+
...(out ? { stdout: out.push } : {}),
|
|
282
|
+
...(err ? { stderr: err.push } : {}),
|
|
283
|
+
});
|
|
284
|
+
out?.end();
|
|
285
|
+
err?.end();
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
res = await this.#client.sandboxes.exec(this.sandboxId, exec);
|
|
289
|
+
}
|
|
290
|
+
const result = (0, client_js_1.toCommandResult)(res);
|
|
291
|
+
if (result.exitCode !== 0 && fg.throwOnExit !== false) {
|
|
292
|
+
if ((0, command_errors_js_1.answerTimedOut)(res))
|
|
293
|
+
throw new command_errors_js_1.CommandTimeoutError(result, bound ?? EXEC_TIMEOUT_DEFAULT_MS);
|
|
294
|
+
throw new core_js_1.CommandExitError(result);
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
}),
|
|
298
|
+
list: async () => {
|
|
299
|
+
const execs = await this.#client.sandboxes.execs.list(this.sandboxId);
|
|
300
|
+
return execs.map((e) => {
|
|
301
|
+
const code = e.exit ? (e.exit.exit_code ?? (e.exit.signal ? 128 + e.exit.signal : -1)) : undefined;
|
|
302
|
+
return { pid: e.exec_id, cmd: e.argv, state: e.state, ...(code === undefined ? {} : { exitCode: code }) };
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
kill: async (pid) => (await this.#client.sandboxes.execs.signal(this.sandboxId, pid, "SIGKILL")).delivered,
|
|
306
|
+
connect: async (pid, opts = {}) => {
|
|
307
|
+
const rec = await this.#client.sandboxes.execs.get(this.sandboxId, pid);
|
|
308
|
+
return new handles_js_1.CommandHandle(this.#client, rec, opts);
|
|
309
|
+
},
|
|
310
|
+
sendStdin: async (pid, data) => (await this.#client.sandboxes.execs.stdin(this.sandboxId, pid, { data })).written,
|
|
311
|
+
closeStdin: async (pid) => {
|
|
312
|
+
await this.#client.sandboxes.execs.stdin(this.sandboxId, pid, { eof: true });
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
const terminal = (pid) => {
|
|
316
|
+
const t = this.terminals.get(pid);
|
|
317
|
+
if (!t) {
|
|
318
|
+
throw new core_js_1.NotFoundError(`no terminal with pid ${pid} is open from this Sandbox object`, "A terminal belongs to the connection that opened it: use the session pty.create() returned, or open a new one.");
|
|
319
|
+
}
|
|
320
|
+
return t;
|
|
321
|
+
};
|
|
322
|
+
this.pty = {
|
|
323
|
+
create: async (opts = {}) => {
|
|
324
|
+
const cmd = ptyCommand({
|
|
325
|
+
...(opts.cmd === undefined ? {} : { cmd: opts.cmd }),
|
|
326
|
+
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
327
|
+
envs: { ...(this.#envs ?? {}), ...(opts.envs ?? {}) },
|
|
328
|
+
});
|
|
329
|
+
let pid = -1;
|
|
330
|
+
const session = await this.#client.sandboxes.pty(this.sandboxId, {
|
|
331
|
+
...(opts.cols === undefined ? {} : { cols: opts.cols }),
|
|
332
|
+
...(opts.rows === undefined ? {} : { rows: opts.rows }),
|
|
333
|
+
...(cmd === undefined ? {} : { cmd }),
|
|
334
|
+
...(opts.secrets?.length ? { secrets: opts.secrets } : {}),
|
|
335
|
+
...(opts.onData ? { onData: opts.onData } : {}),
|
|
336
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
337
|
+
...(opts.resume === undefined ? {} : { resume: opts.resume }),
|
|
338
|
+
reconnect: opts.reconnect ?? opts.cmd === undefined,
|
|
339
|
+
...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
|
|
340
|
+
onExit: (e) => { this.terminals.delete(pid); opts.onExit?.(e); },
|
|
341
|
+
});
|
|
342
|
+
pid = session.pid;
|
|
343
|
+
this.dirty = true;
|
|
344
|
+
this.terminals.set(session.pid, session);
|
|
345
|
+
return session;
|
|
346
|
+
},
|
|
347
|
+
sendInput: async (pid, data) => { terminal(pid).write(data); },
|
|
348
|
+
resize: async (pid, size) => { terminal(pid).resize(size); },
|
|
349
|
+
kill: async (pid) => {
|
|
350
|
+
const t = this.terminals.get(pid);
|
|
351
|
+
if (!t)
|
|
352
|
+
return false;
|
|
353
|
+
t.signal(9);
|
|
354
|
+
return true;
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
this.ports = {
|
|
358
|
+
expose: (port) => this.#client.sandboxes.ports.expose(this.sandboxId, port),
|
|
359
|
+
list: () => this.#client.sandboxes.ports.list(this.sandboxId),
|
|
360
|
+
revoke: async (tokenOrPort) => (await this.#client.sandboxes.ports.revoke(this.sandboxId, tokenOrPort)).revoked,
|
|
361
|
+
};
|
|
362
|
+
this.files = {
|
|
363
|
+
read: (async (path, opts = {}) => {
|
|
364
|
+
const format = opts.format ?? "text";
|
|
365
|
+
if (format !== "text" && format !== "bytes") {
|
|
366
|
+
throw new core_js_1.InvalidArgumentError(`files.read format must be "text" or "bytes", got ${JSON.stringify(format)}`, "Pass { format: 'bytes' } and wrap the bytes yourself (new Blob([bytes]) for a Blob).");
|
|
367
|
+
}
|
|
368
|
+
const bytes = await this.#client.sandboxes.files.get(this.sandboxId, path);
|
|
369
|
+
return format === "bytes" ? bytes : new TextDecoder().decode(bytes);
|
|
370
|
+
}),
|
|
371
|
+
write: async (path, data, opts = {}) => {
|
|
372
|
+
let body;
|
|
373
|
+
if (typeof data === "string" || data instanceof Uint8Array)
|
|
374
|
+
body = data;
|
|
375
|
+
else if (data instanceof ArrayBuffer)
|
|
376
|
+
body = new Uint8Array(data);
|
|
377
|
+
else if (ArrayBuffer.isView(data))
|
|
378
|
+
body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
379
|
+
else if (typeof Blob !== "undefined" && data instanceof Blob)
|
|
380
|
+
body = new Uint8Array(await data.arrayBuffer());
|
|
381
|
+
else {
|
|
382
|
+
throw new core_js_1.InvalidArgumentError(`files.write data must be a string, a typed array, an ArrayBuffer or a Blob, got ${data === null ? "null" : typeof data}`, "Pass text, or the bytes as a Uint8Array.");
|
|
383
|
+
}
|
|
384
|
+
this.dirty = true;
|
|
385
|
+
await this.#client.sandboxes.files.put(this.sandboxId, path, body, opts.mode === undefined ? {} : { mode: opts.mode });
|
|
386
|
+
},
|
|
387
|
+
remove: async (path, opts = {}) => {
|
|
388
|
+
this.dirty = true;
|
|
389
|
+
await this.#client.sandboxes.files.delete(this.sandboxId, path, opts);
|
|
390
|
+
},
|
|
391
|
+
exists: async (path) => {
|
|
392
|
+
try {
|
|
393
|
+
await this.#client.sandboxes.files.stat(this.sandboxId, path);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
if (e instanceof core_js_1.NotFoundError)
|
|
398
|
+
return false;
|
|
399
|
+
throw e;
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
list: async (path = "/", opts = {}) => {
|
|
403
|
+
const o = opts.depth !== undefined && opts.depth > 1 && opts.recursive === undefined ? { ...opts, recursive: true } : opts;
|
|
404
|
+
const r = await this.#client.sandboxes.files.list(this.sandboxId, path, o);
|
|
405
|
+
return r.entries.map(toEntryInfo);
|
|
406
|
+
},
|
|
407
|
+
mkdir: async (path, opts = {}) => {
|
|
408
|
+
this.dirty = true;
|
|
409
|
+
return toEntryInfoFromPath(await this.#client.sandboxes.files.mkdir(this.sandboxId, path, opts));
|
|
410
|
+
},
|
|
411
|
+
move: async (from, to, opts = {}) => {
|
|
412
|
+
this.dirty = true;
|
|
413
|
+
return toEntryInfoFromPath(await this.#client.sandboxes.files.move(this.sandboxId, from, to, opts));
|
|
414
|
+
},
|
|
415
|
+
rename: async (from, to, opts = {}) => this.files.move(from, to, opts),
|
|
416
|
+
copy: async (from, to, opts = {}) => {
|
|
417
|
+
this.dirty = true;
|
|
418
|
+
return toEntryInfoFromPath(await this.#client.sandboxes.files.copy(this.sandboxId, from, to, opts));
|
|
419
|
+
},
|
|
420
|
+
chmod: async (path, opts) => {
|
|
421
|
+
this.dirty = true;
|
|
422
|
+
return toEntryInfoFromPath(await this.#client.sandboxes.files.chmod(this.sandboxId, path, opts));
|
|
423
|
+
},
|
|
424
|
+
search: async (path, opts) => (await this.#client.sandboxes.files.search(this.sandboxId, path, opts)).matches,
|
|
425
|
+
downloadDir: async (path, opts = {}) => {
|
|
426
|
+
this.dirty = true;
|
|
427
|
+
const made = await this.#client.sandboxes.files.archive(this.sandboxId, path, opts);
|
|
428
|
+
try {
|
|
429
|
+
return await this.#client.sandboxes.files.get(this.sandboxId, made.archive_path);
|
|
430
|
+
}
|
|
431
|
+
finally {
|
|
432
|
+
await this.#client.sandboxes.files.delete(this.sandboxId, made.archive_path).catch(() => { });
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
uploadDir: async (tar, into, opts = {}) => {
|
|
436
|
+
this.dirty = true;
|
|
437
|
+
const scratch = `/tmp/.promigence-upload/up-${Date.now().toString(36)}-${(uploadSeq++).toString(36)}.tar`;
|
|
438
|
+
await this.#client.sandboxes.files.put(this.sandboxId, scratch, tar, {});
|
|
439
|
+
try {
|
|
440
|
+
return await this.#client.sandboxes.files.extract(this.sandboxId, scratch, into, { strip: opts.strip ?? 1 });
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
await this.#client.sandboxes.files.delete(this.sandboxId, scratch).catch(() => { });
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
static async create(arg, second = {}) {
|
|
449
|
+
const opts = typeof arg === "object" && arg !== null ? arg : second;
|
|
450
|
+
let snapshotIdOrAlias = typeof arg === "string" ? arg : opts.snapshot;
|
|
451
|
+
if (snapshotIdOrAlias !== undefined && opts.image !== undefined) {
|
|
452
|
+
throw new core_js_1.InvalidArgumentError("Sandbox.create takes a snapshot or an image, not both", "Pass Sandbox.create('promigence/base') or Sandbox.create({ image: 'registry/name@sha256:…' }).");
|
|
453
|
+
}
|
|
454
|
+
if (opts.tier !== undefined && opts.image === undefined) {
|
|
455
|
+
throw new core_js_1.InvalidArgumentError(`Sandbox.create: tier "${opts.tier}" needs an image; a sandbox from a snapshot has the snapshot's size (the built-in base is small)`, "Pass image: 'registry/name@sha256:…' with the tier, or build a snapshot at that size once " +
|
|
456
|
+
"(promigence snapshot create --image REF --tier large) and create from its alias.");
|
|
457
|
+
}
|
|
458
|
+
if (opts.image !== undefined) {
|
|
459
|
+
(0, client_js_1.checkImageRef)(opts.image);
|
|
460
|
+
(0, client_js_1.checkImageVersioned)(opts.image);
|
|
461
|
+
if (opts.allowBuild !== true)
|
|
462
|
+
throw buildNotAllowed(opts.image.trim());
|
|
463
|
+
}
|
|
464
|
+
const client = opts.client ?? new client_js_1.PromigenceClient({
|
|
465
|
+
...(opts.apiKey === undefined ? {} : { apiKey: opts.apiKey }),
|
|
466
|
+
...(opts.apiUrl === undefined ? {} : { apiUrl: opts.apiUrl }),
|
|
467
|
+
});
|
|
468
|
+
const aborted = () => new core_js_1.TimeoutError("Sandbox.create was aborted by the caller's signal; anything it had started was ended", "Nothing to fix: the caller aborted it.");
|
|
469
|
+
if (opts.signal?.aborted)
|
|
470
|
+
throw aborted();
|
|
471
|
+
if (opts.image !== undefined) {
|
|
472
|
+
try {
|
|
473
|
+
snapshotIdOrAlias = await imageSnapshot(client, opts, {
|
|
474
|
+
onBuild: opts.onBuild ?? buildNotice(opts.image.trim()),
|
|
475
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
if (opts.signal?.aborted && !(e instanceof core_js_1.TimeoutError))
|
|
480
|
+
throw aborted();
|
|
481
|
+
throw e;
|
|
482
|
+
}
|
|
483
|
+
if (opts.signal?.aborted)
|
|
484
|
+
throw aborted();
|
|
485
|
+
}
|
|
486
|
+
else if (snapshotIdOrAlias === undefined || snapshotIdOrAlias === "base") {
|
|
487
|
+
snapshotIdOrAlias = exports.DEFAULT_SNAPSHOT;
|
|
488
|
+
}
|
|
489
|
+
const stop = new AbortController();
|
|
490
|
+
const key = (0, client_js_1.newIdempotencyKey)();
|
|
491
|
+
let sent;
|
|
492
|
+
let runId;
|
|
493
|
+
let ending;
|
|
494
|
+
const end = () => (ending ??= endInterruptedCreate(client, runId, sent));
|
|
495
|
+
const release = holdSigint(() => { stop.abort(); return end(); });
|
|
496
|
+
const onAbort = () => { stop.abort(); };
|
|
497
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
498
|
+
try {
|
|
499
|
+
let handles;
|
|
500
|
+
try {
|
|
501
|
+
({ runId, handles } = await Sandbox.forkHandlesWith(snapshotIdOrAlias, { ...opts, count: 1, client }, {
|
|
502
|
+
idempotencyKey: key, signal: stop.signal, onStart: (s) => { sent = s; },
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
catch (e) {
|
|
506
|
+
if (stop.signal.aborted) {
|
|
507
|
+
await end();
|
|
508
|
+
throw aborted();
|
|
509
|
+
}
|
|
510
|
+
throw e;
|
|
511
|
+
}
|
|
512
|
+
const first = handles[0];
|
|
513
|
+
try {
|
|
514
|
+
if (!first)
|
|
515
|
+
throw new core_js_1.EnvFailedError("not_started", "no sandbox was started", "Re-run; if it repeats, check `promigence run report <run_id>`.");
|
|
516
|
+
return await first;
|
|
517
|
+
}
|
|
518
|
+
catch (e) {
|
|
519
|
+
if (stop.signal.aborted) {
|
|
520
|
+
await end();
|
|
521
|
+
throw aborted();
|
|
522
|
+
}
|
|
523
|
+
if (e instanceof core_js_1.TimeoutError && e.runId === runId && e.sandboxId !== undefined) {
|
|
524
|
+
throw await abandonQueued(client, e.sandboxId, runId, opts.waitTimeoutMs ?? core_js_1.WAIT_TIMEOUT_MS);
|
|
525
|
+
}
|
|
526
|
+
await client.runs.kill(runId).catch(() => { });
|
|
527
|
+
throw e;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
release();
|
|
532
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
static async fork(snapshotIdOrAlias, opts = {}) {
|
|
536
|
+
const { handles } = await Sandbox.forkHandles(snapshotIdOrAlias, opts);
|
|
537
|
+
return Promise.all(handles);
|
|
538
|
+
}
|
|
539
|
+
static async forkHandles(snapshotIdOrAlias, opts = {}) {
|
|
540
|
+
return Sandbox.forkHandlesWith(snapshotIdOrAlias, opts, {});
|
|
541
|
+
}
|
|
542
|
+
static async forkHandlesWith(snapshotIdOrAlias, opts, extra) {
|
|
543
|
+
const client = opts.client ?? new client_js_1.PromigenceClient({
|
|
544
|
+
...(opts.apiKey === undefined ? {} : { apiKey: opts.apiKey }),
|
|
545
|
+
...(opts.apiUrl === undefined ? {} : { apiUrl: opts.apiUrl }),
|
|
546
|
+
});
|
|
547
|
+
const count = opts.count ?? 1;
|
|
548
|
+
const spendCap = spendCapFromEnv(opts.spendCap);
|
|
549
|
+
const start = { snapshot: snapshotIdOrAlias, count };
|
|
550
|
+
if (spendCap !== undefined)
|
|
551
|
+
start.spendCap = spendCap;
|
|
552
|
+
if (count === 1)
|
|
553
|
+
start.waitMs = Math.min(opts.waitTimeoutMs ?? core_js_1.WAIT_TIMEOUT_MS, client_js_1.LONG_POLL_MS);
|
|
554
|
+
if (opts.timeoutMs !== undefined)
|
|
555
|
+
start.timeoutMs = opts.timeoutMs;
|
|
556
|
+
if (opts.allowInternetAccess === false)
|
|
557
|
+
start.network = "none";
|
|
558
|
+
if (opts.allowHosts?.length)
|
|
559
|
+
start.network = { allow: opts.allowHosts };
|
|
560
|
+
if (opts.metadata)
|
|
561
|
+
start.labels = opts.metadata;
|
|
562
|
+
if (opts.secrets?.length)
|
|
563
|
+
start.secrets = opts.secrets;
|
|
564
|
+
if (opts.optimizeFor !== undefined)
|
|
565
|
+
start.optimizeFor = opts.optimizeFor;
|
|
566
|
+
if (extra.idempotencyKey !== undefined)
|
|
567
|
+
start.idempotencyKey = extra.idempotencyKey;
|
|
568
|
+
extra.onStart?.({ ...start });
|
|
569
|
+
const run = await client.runs.start(extra.signal === undefined ? start : { ...start, signal: extra.signal });
|
|
570
|
+
const envs = opts.envs;
|
|
571
|
+
const mk = (ref) => {
|
|
572
|
+
const init = { sandboxId: ref.sandbox_id, runId: run.run_id, snapshot: snapshotIdOrAlias, client };
|
|
573
|
+
if (envs !== undefined)
|
|
574
|
+
init.envs = envs;
|
|
575
|
+
if (opts.timeoutMs !== undefined)
|
|
576
|
+
init.timeoutMs = opts.timeoutMs;
|
|
577
|
+
return new Sandbox(init);
|
|
578
|
+
};
|
|
579
|
+
const only = run.sandboxes?.length === 1 ? run.sandboxes[0] : undefined;
|
|
580
|
+
if (only !== undefined && !(0, client_js_1.isWaiting)(only.state) && only.state !== "ended") {
|
|
581
|
+
const sb = mk(only);
|
|
582
|
+
opts.onReady?.(sb, 0);
|
|
583
|
+
return { runId: run.run_id, handles: [Promise.resolve(sb)] };
|
|
584
|
+
}
|
|
585
|
+
const pending = new Map();
|
|
586
|
+
const order = [];
|
|
587
|
+
for (const ref of run.sandboxes ?? []) {
|
|
588
|
+
order.push(ref.sandbox_id);
|
|
589
|
+
pending.set(ref.sandbox_id, deferred());
|
|
590
|
+
}
|
|
591
|
+
const settle = (ref, index) => {
|
|
592
|
+
const d = pending.get(ref.sandbox_id);
|
|
593
|
+
if (!d || d.settled)
|
|
594
|
+
return;
|
|
595
|
+
if (ref.state === "ended") {
|
|
596
|
+
d.reject(new core_js_1.EnvFailedError("env_failed", `sandbox ${ref.sandbox_id} ended before hand-over`, `Not billed. Read the evidence: \`promigence run report ${(0, hint_js_1.hintArg)(run.run_id)}\`.`));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const sb = mk(ref);
|
|
600
|
+
d.resolve(sb);
|
|
601
|
+
opts.onReady?.(sb, index);
|
|
602
|
+
};
|
|
603
|
+
(run.sandboxes ?? []).forEach((ref, i) => { if (!(0, client_js_1.isWaiting)(ref.state))
|
|
604
|
+
settle(ref, i); });
|
|
605
|
+
if ([...pending.values()].some((d) => !d.settled)) {
|
|
606
|
+
const bound = opts.waitTimeoutMs ?? core_js_1.WAIT_TIMEOUT_MS;
|
|
607
|
+
void client.runs
|
|
608
|
+
.waitReady(run.run_id, {
|
|
609
|
+
waitTimeoutMs: bound, onReady: (ref) => settle(ref, order.indexOf(ref.sandbox_id)),
|
|
610
|
+
...(extra.signal === undefined ? {} : { signal: extra.signal }),
|
|
611
|
+
})
|
|
612
|
+
.then((res) => {
|
|
613
|
+
if (!res.timedOut)
|
|
614
|
+
return;
|
|
615
|
+
for (const [id, d] of pending) {
|
|
616
|
+
if (d.settled)
|
|
617
|
+
continue;
|
|
618
|
+
d.reject(new core_js_1.TimeoutError(`sandbox ${id} was still queued after ${bound} ms; nothing was killed`, `Run the id-based command: \`promigence run status ${(0, hint_js_1.hintArg)(run.run_id)} --wait\` or \`promigence run report ${(0, hint_js_1.hintArg)(run.run_id)}\`.`, { sandboxId: id, runId: run.run_id }));
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
.catch((err) => {
|
|
622
|
+
for (const d of pending.values())
|
|
623
|
+
if (!d.settled)
|
|
624
|
+
d.reject(err);
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return { runId: run.run_id, handles: order.map((id) => pending.get(id).promise) };
|
|
628
|
+
}
|
|
629
|
+
static async connect(sandboxId, opts = {}) {
|
|
630
|
+
(0, client_js_1.checkSandboxId)(sandboxId, "Sandbox.connect");
|
|
631
|
+
const client = opts.client ?? new client_js_1.PromigenceClient({
|
|
632
|
+
...(opts.apiKey === undefined ? {} : { apiKey: opts.apiKey }),
|
|
633
|
+
...(opts.apiUrl === undefined ? {} : { apiUrl: opts.apiUrl }),
|
|
634
|
+
});
|
|
635
|
+
const rec = await client.sandboxes.get(sandboxId);
|
|
636
|
+
return new Sandbox({
|
|
637
|
+
sandboxId: rec.sandbox_id ?? sandboxId,
|
|
638
|
+
runId: rec.run_id ?? "",
|
|
639
|
+
snapshot: rec.snapshot_id ?? "",
|
|
640
|
+
client,
|
|
641
|
+
...(opts.envs === undefined ? {} : { envs: opts.envs }),
|
|
642
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
static async list(opts = {}) {
|
|
646
|
+
return (await Sandbox.listPage(opts)).items;
|
|
647
|
+
}
|
|
648
|
+
static async listPage(opts = {}) {
|
|
649
|
+
const c = opts.client ?? new client_js_1.PromigenceClient();
|
|
650
|
+
return c.sandboxes.listPage({
|
|
651
|
+
limit: opts.limit ?? 50,
|
|
652
|
+
...(opts.labels ? { labels: opts.labels } : {}),
|
|
653
|
+
...(opts.state?.length ? { state: opts.state } : {}),
|
|
654
|
+
...(opts.cursor ? { cursor: opts.cursor } : {}),
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
async fork(opts = {}) {
|
|
658
|
+
if (this.dirty && opts.fromOrigin !== true) {
|
|
659
|
+
throw new core_js_1.UnsupportedError("sandbox.fork() on a sandbox that has changed since it started", `To fork it as it is now: \`const s = await client.sandboxes.snapshot(${JSON.stringify(this.sandboxId)})\` (a live snapshot; it ends background commands), then \`Sandbox.fork(s.snapshot_id, { count })\`. To fork the snapshot it came from instead, pass { fromOrigin: true }.`);
|
|
660
|
+
}
|
|
661
|
+
let origin = this.snapshot;
|
|
662
|
+
if (!origin && this.runId)
|
|
663
|
+
origin = (await this.#client.runs.get(this.runId)).snapshot_id ?? "";
|
|
664
|
+
if (!origin) {
|
|
665
|
+
throw new core_js_1.UnsupportedError("sandbox.fork() without a known origin snapshot", "Use Sandbox.fork('<snapshot>', { count }) or client.runs.start({ snapshot, count }).");
|
|
666
|
+
}
|
|
667
|
+
return Sandbox.fork(origin, {
|
|
668
|
+
client: this.#client,
|
|
669
|
+
count: opts.count ?? 1,
|
|
670
|
+
...(opts.waitTimeoutMs === undefined ? {} : { waitTimeoutMs: opts.waitTimeoutMs }),
|
|
671
|
+
...(opts.onReady === undefined ? {} : { onReady: opts.onReady }),
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
toJSON() {
|
|
675
|
+
return { sandboxId: this.sandboxId, runId: this.runId, snapshot: this.snapshot };
|
|
676
|
+
}
|
|
677
|
+
[node_util_1.inspect.custom](_depth, opts) {
|
|
678
|
+
return `Sandbox ${(0, node_util_1.inspect)(this.toJSON(), opts)}`;
|
|
679
|
+
}
|
|
680
|
+
async kill() {
|
|
681
|
+
const r = await this.#client.sandboxes.kill(this.sandboxId);
|
|
682
|
+
return r?.already_ended !== true;
|
|
683
|
+
}
|
|
684
|
+
async getInfo() {
|
|
685
|
+
return this.#client.sandboxes.get(this.sandboxId);
|
|
686
|
+
}
|
|
687
|
+
probe(opts = {}) {
|
|
688
|
+
return this.#client.sandboxes.probe(this.sandboxId, opts);
|
|
689
|
+
}
|
|
690
|
+
async isRunning() {
|
|
691
|
+
const rec = await this.#client.sandboxes.get(this.sandboxId);
|
|
692
|
+
return rec.state === "ready" || rec.state === "running";
|
|
693
|
+
}
|
|
694
|
+
async setTimeout(ms) {
|
|
695
|
+
await this.#client.sandboxes.patch(this.sandboxId, { timeoutMs: ms });
|
|
696
|
+
this.timeoutMs = ms;
|
|
697
|
+
}
|
|
698
|
+
async setAutoPause(seconds, mode) {
|
|
699
|
+
await this.#client.sandboxes.patch(this.sandboxId, {
|
|
700
|
+
autoPauseS: seconds,
|
|
701
|
+
...(mode !== undefined ? { autoPauseMode: mode } : {}),
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
async getAutoPause() {
|
|
705
|
+
const rec = await this.#client.sandboxes.get(this.sandboxId);
|
|
706
|
+
return Number(rec.auto_pause_s ?? 0);
|
|
707
|
+
}
|
|
708
|
+
async disableInternet() {
|
|
709
|
+
await this.#client.sandboxes.setNetworkNone(this.sandboxId);
|
|
710
|
+
}
|
|
711
|
+
async getHost(port) {
|
|
712
|
+
const existing = (await (0, core_js_1.withPreviewsFix)(this.#client.sandboxes.ports.list(this.sandboxId), this.sandboxId, port)).find((p) => p.port === port);
|
|
713
|
+
const rec = existing ?? await this.#client.sandboxes.ports.expose(this.sandboxId, port);
|
|
714
|
+
return rec.url.replace(/^[a-z]+:\/\//i, "").replace(/\/$/, "");
|
|
715
|
+
}
|
|
716
|
+
async pause() {
|
|
717
|
+
const r = await this.#client.sandboxes.pause(this.sandboxId);
|
|
718
|
+
return {
|
|
719
|
+
pausedId: r.paused_id,
|
|
720
|
+
...(r.background_execs_killed === undefined ? {} : { backgroundExecsKilled: r.background_execs_killed }),
|
|
721
|
+
...(r.warnings?.length ? { warnings: r.warnings } : {}),
|
|
722
|
+
...(r.already_paused === true ? { alreadyPaused: true } : {}),
|
|
723
|
+
...(r.took_ms === undefined ? {} : { tookMs: r.took_ms }),
|
|
724
|
+
...(r.took_ms === undefined ? {} : { pauseMs: r.took_ms }),
|
|
725
|
+
...(r.bytes === undefined ? {} : { bytes: r.bytes }),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
async resume(opts = {}) {
|
|
729
|
+
const r = await this.#client.sandboxes.resume(this.sandboxId, opts);
|
|
730
|
+
return {
|
|
731
|
+
...(r.took_ms === undefined ? {} : { tookMs: r.took_ms }),
|
|
732
|
+
...(r.took_ms === undefined ? {} : { resumeMs: r.took_ms }),
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
exports.Sandbox = Sandbox;
|