@promigence/sdk 0.1.0-beta.12 → 0.1.0-beta.13
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/dist/caller.js +10 -0
- package/dist/cjs/caller.js +10 -0
- package/dist/cjs/client.d.ts +16 -6
- package/dist/cjs/client.js +1180 -1129
- package/dist/cjs/core.d.ts +1 -1
- package/dist/cjs/core.js +1 -1
- package/dist/cjs/dispatcher.d.ts +2 -0
- package/dist/cjs/dispatcher.js +5 -0
- package/dist/cjs/http2.d.ts +10 -0
- package/dist/cjs/http2.js +143 -51
- package/dist/cjs/keyed.d.ts +6 -0
- package/dist/cjs/keyed.js +59 -0
- package/dist/cjs/proxy.js +11 -0
- package/dist/cjs/sandbox.d.ts +3 -1
- package/dist/cjs/sandbox.js +33 -20
- package/dist/cjs/transport-worker.js +11 -2
- package/dist/client.d.ts +16 -6
- package/dist/client.js +1179 -1127
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/dispatcher.d.ts +2 -0
- package/dist/dispatcher.js +5 -0
- package/dist/http2.d.ts +10 -0
- package/dist/http2.js +142 -51
- package/dist/keyed.d.ts +6 -0
- package/dist/keyed.js +55 -0
- package/dist/proxy.js +11 -0
- package/dist/sandbox.d.ts +3 -1
- package/dist/sandbox.js +33 -20
- package/dist/transport-worker.js +11 -2
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -8,11 +8,11 @@ import { keychainReadCached, keychainStore, storeLabel } from "./credstore.js";
|
|
|
8
8
|
import { CALLER_HEADER, resolveCaller, userAgentWithCaller } from "./caller.js";
|
|
9
9
|
import { connectPty } from "./pty.js";
|
|
10
10
|
import { connectSshTunnel } from "./ssh.js";
|
|
11
|
-
import { H2Response,
|
|
11
|
+
import { H2Response, http2Allowed, sharedTransport, warmTls } from "./http2.js";
|
|
12
12
|
import { CONTEXT_TAR_PREFIX } from "./context.js";
|
|
13
13
|
import { proxyFetch, proxyFor, transportEnv } from "./proxy.js";
|
|
14
14
|
import { mark, traceBegin, traceEvent } from "./trace.js";
|
|
15
|
-
import {
|
|
15
|
+
import { isKeyedCreate } from "./keyed.js";
|
|
16
16
|
import { customDispatcher, longRequestDispatcher } from "./dispatcher.js";
|
|
17
17
|
import { compromisedKeyCause, guardedFetchDispatcher } from "./ca-guard.js";
|
|
18
18
|
import { hintArg } from "./hint.js";
|
|
@@ -262,15 +262,34 @@ export function keyOrigin(env = process.env) {
|
|
|
262
262
|
return stored.store === "macos-keychain" ? "macOS Keychain" : stored.store === "secret-service" ? "Secret Service keyring" : "credentials file";
|
|
263
263
|
return undefined;
|
|
264
264
|
}
|
|
265
|
+
function nonEmpty(v) {
|
|
266
|
+
return v ? v : undefined;
|
|
267
|
+
}
|
|
268
|
+
function withV1(u) {
|
|
269
|
+
const t = u.replace(/\/+$/, "");
|
|
270
|
+
return /^https?:\/\/[^/?#]+$/i.test(t) ? `${t}/v1` : t;
|
|
271
|
+
}
|
|
272
|
+
let credsLastInputs;
|
|
273
|
+
let credsLast;
|
|
265
274
|
function resolveCredentials(opts, env = process.env) {
|
|
266
|
-
const set =
|
|
267
|
-
const trim =
|
|
268
|
-
const t = u.replace(/\/+$/, "");
|
|
269
|
-
return /^https?:\/\/[^/?#]+$/i.test(t) ? `${t}/v1` : t;
|
|
270
|
-
};
|
|
275
|
+
const set = nonEmpty;
|
|
276
|
+
const trim = withV1;
|
|
271
277
|
const optKey = opts.apiKey?.trim();
|
|
272
278
|
const envKey = optKey === undefined ? set(env["PROMIGENCE_API_KEY"]?.trim()) : undefined;
|
|
273
279
|
const envUrl = optKey !== undefined && opts.apiUrl !== undefined ? undefined : set(env["PROMIGENCE_API_URL"]);
|
|
280
|
+
const inputs = optKey === undefined && envKey === undefined
|
|
281
|
+
? undefined
|
|
282
|
+
: `${optKey ?? "\0"}\n${envKey ?? "\0"}\n${envUrl ?? "\0"}\n${opts.apiUrl ?? "\0"}`;
|
|
283
|
+
if (inputs !== undefined && inputs === credsLastInputs && credsLast !== undefined)
|
|
284
|
+
return { apiKey: credsLast.apiKey, apiUrl: credsLast.apiUrl };
|
|
285
|
+
const worked = resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl);
|
|
286
|
+
if (inputs !== undefined && worked.refusal === undefined) {
|
|
287
|
+
credsLastInputs = inputs;
|
|
288
|
+
credsLast = { apiKey: worked.apiKey, apiUrl: worked.apiUrl };
|
|
289
|
+
}
|
|
290
|
+
return worked;
|
|
291
|
+
}
|
|
292
|
+
function resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl) {
|
|
274
293
|
const given = optKey !== undefined || envKey !== undefined;
|
|
275
294
|
const { creds, store } = given
|
|
276
295
|
? { creds: {}, store: undefined }
|
|
@@ -671,28 +690,7 @@ export function errorFor(code, message, fix, extra = {}) {
|
|
|
671
690
|
}));
|
|
672
691
|
}
|
|
673
692
|
}
|
|
674
|
-
|
|
675
|
-
const KEYED_METHODS = new Set(KEYED_CREATES.map(([m]) => m));
|
|
676
|
-
export function isKeyedCreate(method, path) {
|
|
677
|
-
if (!KEYED_METHODS.has(method))
|
|
678
|
-
return false;
|
|
679
|
-
const segs = (path.split("?")[0] ?? "").split("/").filter(Boolean);
|
|
680
|
-
for (const [m, pat] of KEYED_PATTERNS) {
|
|
681
|
-
if (m !== method || pat.length !== segs.length)
|
|
682
|
-
continue;
|
|
683
|
-
let all = true;
|
|
684
|
-
for (let i = 0; i < pat.length; i++) {
|
|
685
|
-
const s = pat[i];
|
|
686
|
-
if (s.startsWith("{") ? (segs[i] ?? "").length === 0 : s !== segs[i]) {
|
|
687
|
-
all = false;
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
if (all)
|
|
692
|
-
return true;
|
|
693
|
-
}
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
693
|
+
export { isKeyedCreate };
|
|
696
694
|
function commandTimeoutMs(v) {
|
|
697
695
|
if (v === undefined)
|
|
698
696
|
return undefined;
|
|
@@ -773,6 +771,12 @@ export function serverAppMs(header) {
|
|
|
773
771
|
function headerOf(res, name) {
|
|
774
772
|
return res instanceof H2Response ? res.header(name) : res.headers.get(name);
|
|
775
773
|
}
|
|
774
|
+
function ownFetchDispatcherFor(timeoutMs) {
|
|
775
|
+
const guarded = customDispatcher() ? undefined : guardedFetchDispatcher();
|
|
776
|
+
if (guarded !== undefined)
|
|
777
|
+
return longRequestDispatcher(timeoutMs, guarded) ?? guarded;
|
|
778
|
+
return longRequestDispatcher(timeoutMs);
|
|
779
|
+
}
|
|
776
780
|
export class PromigenceClient {
|
|
777
781
|
apiUrl;
|
|
778
782
|
#apiKey;
|
|
@@ -804,7 +808,7 @@ export class PromigenceClient {
|
|
|
804
808
|
this.fetchImpl = opts.fetch ?? (proxy ? proxyFetch(proxy) : globalThis.fetch);
|
|
805
809
|
this.ownFetch = opts.fetch === undefined && proxy === undefined;
|
|
806
810
|
this.h2 = env !== undefined && this.apiUrl.startsWith("https:") && http2Allowed(opts.http2, env)
|
|
807
|
-
?
|
|
811
|
+
? sharedTransport(this.fetchImpl)
|
|
808
812
|
: undefined;
|
|
809
813
|
this.requestTimeoutMs = opts.requestTimeoutMs ?? 30_000;
|
|
810
814
|
this.maxRetries = opts.maxRetries ?? 3;
|
|
@@ -903,11 +907,18 @@ export class PromigenceClient {
|
|
|
903
907
|
return guardedFetchDispatcher();
|
|
904
908
|
}
|
|
905
909
|
dispatcherFor(timeoutMs) {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return longRequestDispatcher(timeoutMs, guarded) ?? guarded;
|
|
910
|
+
if (this.ownFetch)
|
|
911
|
+
return ownFetchDispatcherFor(timeoutMs);
|
|
909
912
|
return longRequestDispatcher(timeoutMs);
|
|
910
913
|
}
|
|
914
|
+
async againKeyed(attempt, signal) {
|
|
915
|
+
if (attempt >= this.maxRetries || signal?.aborted)
|
|
916
|
+
return false;
|
|
917
|
+
this.stats.retries++;
|
|
918
|
+
const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
|
|
919
|
+
await sleep(backoff * (0.5 + Math.random() * 0.5));
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
911
922
|
async request(opts) {
|
|
912
923
|
const tr = traceBegin(opts.method, opts.path);
|
|
913
924
|
const url = `${this.apiUrl}${opts.path}`;
|
|
@@ -934,16 +945,8 @@ export class PromigenceClient {
|
|
|
934
945
|
if (idempotencyKey)
|
|
935
946
|
headers["idempotency-key"] = idempotencyKey;
|
|
936
947
|
const replayable = opts.method === "GET" || opts.method === "DELETE" || (keyedCreate && Boolean(idempotencyKey));
|
|
937
|
-
const again = async (attempt) => {
|
|
938
|
-
if (!keyedCreate || !idempotencyKey || attempt >= this.maxRetries || opts.signal?.aborted)
|
|
939
|
-
return false;
|
|
940
|
-
this.stats.retries++;
|
|
941
|
-
const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
|
|
942
|
-
await sleep(backoff * (0.5 + Math.random() * 0.5));
|
|
943
|
-
return true;
|
|
944
|
-
};
|
|
945
948
|
const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;
|
|
946
|
-
|
|
949
|
+
let dispatcher;
|
|
947
950
|
let resentDelete = false;
|
|
948
951
|
for (let attempt = 0;; attempt++) {
|
|
949
952
|
let res;
|
|
@@ -956,8 +959,7 @@ export class PromigenceClient {
|
|
|
956
959
|
init.signal = opts.signal;
|
|
957
960
|
if (opts.body !== undefined)
|
|
958
961
|
init.body = JSON.stringify(opts.body);
|
|
959
|
-
|
|
960
|
-
init.dispatcher = dispatcher;
|
|
962
|
+
init.dispatcherFor = ownFetchDispatcherFor;
|
|
961
963
|
if (tr !== undefined) {
|
|
962
964
|
init.trace = tr;
|
|
963
965
|
mark(tr, "init");
|
|
@@ -973,7 +975,7 @@ export class PromigenceClient {
|
|
|
973
975
|
headers,
|
|
974
976
|
signal: signals.length === 1 ? signals[0] : AbortSignal.any(signals),
|
|
975
977
|
...(opts.body === undefined ? {} : { body: JSON.stringify(opts.body) }),
|
|
976
|
-
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
978
|
+
...((dispatcher ??= this.dispatcherFor(timeoutMs)) === undefined ? {} : { dispatcher }),
|
|
977
979
|
};
|
|
978
980
|
if (tr !== undefined)
|
|
979
981
|
mark(tr, "init");
|
|
@@ -991,7 +993,7 @@ export class PromigenceClient {
|
|
|
991
993
|
}
|
|
992
994
|
catch (err) {
|
|
993
995
|
const e = err;
|
|
994
|
-
if (await
|
|
996
|
+
if (keyedCreate && idempotencyKey && await this.againKeyed(attempt, opts.signal))
|
|
995
997
|
continue;
|
|
996
998
|
const cutOff = e?.cause?.code === "UND_ERR_HEADERS_TIMEOUT" || e?.cause?.code === "UND_ERR_BODY_TIMEOUT";
|
|
997
999
|
const timedOut = e?.name === "TimeoutError" || e?.name === "AbortError" || cutOff;
|
|
@@ -1302,316 +1304,332 @@ export class PromigenceClient {
|
|
|
1302
1304
|
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
1303
1305
|
});
|
|
1304
1306
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1307
|
+
group_templates() {
|
|
1308
|
+
return {
|
|
1309
|
+
list: async (opts = {}) => {
|
|
1310
|
+
const r = await this.request({
|
|
1311
|
+
method: "GET",
|
|
1312
|
+
path: "/templates",
|
|
1313
|
+
anonymous: true,
|
|
1314
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1315
|
+
});
|
|
1316
|
+
return r?.templates ?? [];
|
|
1317
|
+
},
|
|
1318
|
+
get: (name, opts = {}) => this.request({
|
|
1308
1319
|
method: "GET",
|
|
1309
|
-
path:
|
|
1320
|
+
path: `/templates/${seg(name)}`,
|
|
1310
1321
|
anonymous: true,
|
|
1311
1322
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1312
|
-
})
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
get: (name, opts = {}) => this.request({
|
|
1316
|
-
method: "GET",
|
|
1317
|
-
path: `/templates/${seg(name)}`,
|
|
1318
|
-
anonymous: true,
|
|
1319
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1320
|
-
}),
|
|
1321
|
-
};
|
|
1323
|
+
}),
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1322
1326
|
me(opts = {}) {
|
|
1323
1327
|
return this.request({ method: "GET", path: "/me", ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
1324
1328
|
}
|
|
1325
1329
|
outcomes(opts = {}) {
|
|
1326
1330
|
return this.request({ method: "GET", path: "/me/outcomes?period=month", ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
1327
1331
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
invites: async (org) => {
|
|
1385
|
-
const r = await this.request({ method: "GET", path: `/orgs/${orgSeg(org)}/invites` });
|
|
1386
|
-
return r?.invites ?? [];
|
|
1387
|
-
},
|
|
1388
|
-
leave: (org) => this.request({ method: "DELETE", path: `/orgs/${orgSeg(org)}/members/me` }),
|
|
1389
|
-
projects: {
|
|
1390
|
-
list: async (org) => {
|
|
1391
|
-
const r = await this.request({ method: "GET", path: `/orgs/${orgSeg(org)}/projects` });
|
|
1392
|
-
return r?.projects ?? [];
|
|
1332
|
+
group_billing() {
|
|
1333
|
+
return {
|
|
1334
|
+
usage: () => this.request({ method: "GET", path: "/billing" }),
|
|
1335
|
+
ledger: async (limit = 100) => (await this.billing.ledgerPage({ limit })).items,
|
|
1336
|
+
ledgerPage: async (opts = {}) => {
|
|
1337
|
+
const limit = opts.limit ?? 100;
|
|
1338
|
+
const r = await this.request({ method: "GET", path: `/me/ledger?${pageQuery(limit, 1000, opts.cursor)}` });
|
|
1339
|
+
return pageOf(r, "entries", limit);
|
|
1340
|
+
},
|
|
1341
|
+
historyPage: async (opts = {}) => {
|
|
1342
|
+
const limit = opts.limit ?? 50;
|
|
1343
|
+
const q = new URLSearchParams(pageQuery(limit, 100, opts.cursor));
|
|
1344
|
+
if (opts.kind !== undefined && opts.kind !== "all")
|
|
1345
|
+
q.set("kind", opts.kind);
|
|
1346
|
+
const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
|
|
1347
|
+
return pageOf(r, "entries", limit);
|
|
1348
|
+
},
|
|
1349
|
+
checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
|
|
1350
|
+
portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
|
|
1351
|
+
referrals: () => this.request({ method: "GET", path: "/referrals" }),
|
|
1352
|
+
applyReferral: (code) => this.request({ method: "POST", path: "/referrals/apply", body: { code } }),
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
group_keys() {
|
|
1356
|
+
return {
|
|
1357
|
+
list: async (opts = {}) => {
|
|
1358
|
+
const r = await this.request({ method: "GET", path: `/keys${opts.all ? "?all=1" : ""}` });
|
|
1359
|
+
return r?.keys ?? [];
|
|
1360
|
+
},
|
|
1361
|
+
create: (opts = {}) => {
|
|
1362
|
+
const body = { ...expiryFields(opts) };
|
|
1363
|
+
if (opts.name !== undefined)
|
|
1364
|
+
body["name"] = opts.name.slice(0, 64);
|
|
1365
|
+
return this.request({ method: "POST", path: "/keys", body });
|
|
1366
|
+
},
|
|
1367
|
+
revoke: (keyId) => {
|
|
1368
|
+
if (!keyId?.trim())
|
|
1369
|
+
throw new InvalidArgumentError("keys.revoke needs a key id", "Pass a key_id from keys.list().");
|
|
1370
|
+
return this.request({ method: "DELETE", path: `/keys/${encodeURIComponent(keyId)}` });
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
group_orgs() {
|
|
1375
|
+
return {
|
|
1376
|
+
list: async () => {
|
|
1377
|
+
const r = await this.request({ method: "GET", path: "/orgs" });
|
|
1378
|
+
return r?.orgs ?? [];
|
|
1379
|
+
},
|
|
1380
|
+
setDefault: (org) => this.request({ method: "POST", path: `/orgs/${orgSeg(org)}/default`, body: {} }),
|
|
1381
|
+
createKey: (org, opts = {}) => {
|
|
1382
|
+
const body = { ...expiryFields(opts) };
|
|
1383
|
+
if (opts.name !== undefined)
|
|
1384
|
+
body["name"] = opts.name.slice(0, 64);
|
|
1385
|
+
if (opts.project !== undefined)
|
|
1386
|
+
body["project"] = opts.project;
|
|
1387
|
+
return this.request({ method: "POST", path: `/orgs/${orgSeg(org)}/keys`, body });
|
|
1393
1388
|
},
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1389
|
+
members: async (org) => {
|
|
1390
|
+
const r = await this.request({ method: "GET", path: `/orgs/${orgSeg(org)}/members` });
|
|
1391
|
+
return r?.members ?? [];
|
|
1392
|
+
},
|
|
1393
|
+
invites: async (org) => {
|
|
1394
|
+
const r = await this.request({ method: "GET", path: `/orgs/${orgSeg(org)}/invites` });
|
|
1395
|
+
return r?.invites ?? [];
|
|
1396
|
+
},
|
|
1397
|
+
leave: (org) => this.request({ method: "DELETE", path: `/orgs/${orgSeg(org)}/members/me` }),
|
|
1398
|
+
projects: {
|
|
1399
|
+
list: async (org) => {
|
|
1400
|
+
const r = await this.request({ method: "GET", path: `/orgs/${orgSeg(org)}/projects` });
|
|
1401
|
+
return r?.projects ?? [];
|
|
1402
|
+
},
|
|
1403
|
+
create: (org, name) => this.request({ method: "POST", path: `/orgs/${orgSeg(org)}/projects`, body: { name } }),
|
|
1404
|
+
rename: (org, project, name) => this.request({
|
|
1405
|
+
method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: { name },
|
|
1406
|
+
}),
|
|
1407
|
+
},
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
group_credits() {
|
|
1411
|
+
return {
|
|
1412
|
+
offer: () => this.request({ method: "GET", path: "/credits/offer" }),
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
group_github() {
|
|
1416
|
+
return {
|
|
1417
|
+
status: () => this.request({ method: "GET", path: "/integrations/github" }),
|
|
1418
|
+
connect: () => this.request({ method: "POST", path: "/integrations/github/connect", body: {} }),
|
|
1419
|
+
ticket: () => this.request({ method: "POST", path: "/integrations/github/requests", body: {} }),
|
|
1420
|
+
request: (requestId) => this.request({
|
|
1421
|
+
method: "GET",
|
|
1422
|
+
path: `/integrations/github/requests/${encodeURIComponent(requestId)}`,
|
|
1397
1423
|
}),
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
if (waitMs > 0)
|
|
1509
|
-
req.timeoutMs = waitMs + this.requestTimeoutMs;
|
|
1510
|
-
if (opts.idempotencyKey)
|
|
1511
|
-
req.idempotencyKey = opts.idempotencyKey;
|
|
1512
|
-
if (opts.signal)
|
|
1513
|
-
req.signal = opts.signal;
|
|
1514
|
-
return this.request(req);
|
|
1515
|
-
},
|
|
1516
|
-
get: (runId, opts = {}) => this.request({
|
|
1517
|
-
method: "GET",
|
|
1518
|
-
path: `/runs/${encodeURIComponent(runId)}${waitQuery(opts.waitMs)}`,
|
|
1519
|
-
...this.readExtras(opts),
|
|
1520
|
-
}),
|
|
1521
|
-
list: async (limit = 20) => (await this.runs.listPage({ limit })).items,
|
|
1522
|
-
listPage: async (opts = {}) => {
|
|
1523
|
-
const limit = opts.limit ?? 20;
|
|
1524
|
-
const r = await this.request({ method: "GET", path: `/runs?${pageQuery(limit, 500, opts.cursor)}` });
|
|
1525
|
-
return pageOf(r, "runs", limit);
|
|
1526
|
-
},
|
|
1527
|
-
status: (runId, opts = {}) => this.runs.get(runId, opts),
|
|
1528
|
-
sandboxes: async (runId, opts = {}) => {
|
|
1529
|
-
const r = await this.runs.sandboxesWithCapacity(runId, opts);
|
|
1530
|
-
return r.sandboxes;
|
|
1531
|
-
},
|
|
1532
|
-
sandboxesWithCapacity: async (runId, opts = {}) => {
|
|
1533
|
-
const r = await this.request({
|
|
1424
|
+
disconnect: (installationId) => this.request({
|
|
1425
|
+
method: "DELETE",
|
|
1426
|
+
path: `/integrations/github/installations/${encodeURIComponent(String(installationId))}`,
|
|
1427
|
+
}),
|
|
1428
|
+
repos: (opts = {}) => {
|
|
1429
|
+
const q = new URLSearchParams();
|
|
1430
|
+
if (opts.page !== undefined)
|
|
1431
|
+
q.set("page", String(Math.max(1, Math.floor(opts.page))));
|
|
1432
|
+
if (opts.perPage !== undefined)
|
|
1433
|
+
q.set("per_page", String(Math.min(100, Math.max(1, Math.floor(opts.perPage)))));
|
|
1434
|
+
const qs = q.toString();
|
|
1435
|
+
return this.request({ method: "GET", path: `/integrations/github/repos${qs ? `?${qs}` : ""}` });
|
|
1436
|
+
},
|
|
1437
|
+
untrack: (trackId) => this.request({ method: "DELETE", path: `/integrations/github/tracks/${encodeURIComponent(trackId)}` }),
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
group_gitlab() {
|
|
1441
|
+
return {
|
|
1442
|
+
status: () => this.request({ method: "GET", path: "/integrations/gitlab" }),
|
|
1443
|
+
connect: () => this.request({ method: "POST", path: "/integrations/gitlab/connect", body: {} }),
|
|
1444
|
+
ticket: () => this.request({ method: "POST", path: "/integrations/gitlab/requests", body: {} }),
|
|
1445
|
+
request: (requestId) => this.request({
|
|
1446
|
+
method: "GET",
|
|
1447
|
+
path: `/integrations/gitlab/requests/${encodeURIComponent(requestId)}`,
|
|
1448
|
+
}),
|
|
1449
|
+
disconnect: (connectionId) => this.request({
|
|
1450
|
+
method: "DELETE",
|
|
1451
|
+
path: `/integrations/gitlab/connections/${encodeURIComponent(connectionId)}`,
|
|
1452
|
+
}),
|
|
1453
|
+
repos: (opts = {}) => {
|
|
1454
|
+
const q = new URLSearchParams();
|
|
1455
|
+
if (opts.page !== undefined)
|
|
1456
|
+
q.set("page", String(Math.max(1, Math.floor(opts.page))));
|
|
1457
|
+
if (opts.perPage !== undefined)
|
|
1458
|
+
q.set("per_page", String(Math.min(100, Math.max(1, Math.floor(opts.perPage)))));
|
|
1459
|
+
if (opts.connectionId !== undefined)
|
|
1460
|
+
q.set("connection_id", opts.connectionId);
|
|
1461
|
+
const qs = q.toString();
|
|
1462
|
+
return this.request({ method: "GET", path: `/integrations/gitlab/repos${qs ? `?${qs}` : ""}` });
|
|
1463
|
+
},
|
|
1464
|
+
untrack: (trackId) => this.request({ method: "DELETE", path: `/integrations/gitlab/tracks/${encodeURIComponent(trackId)}` }),
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
group_runs() {
|
|
1468
|
+
return {
|
|
1469
|
+
start: (opts) => {
|
|
1470
|
+
if (!opts.snapshot)
|
|
1471
|
+
throw new InvalidArgumentError("runs.start needs a snapshot", "Pass { snapshot: 'snap_… | alias | alias@commit7' }.");
|
|
1472
|
+
if (!Number.isInteger(opts.count) || opts.count < 1)
|
|
1473
|
+
throw new InvalidArgumentError("runs.start needs count >= 1", "Pass { count: 1 } or more.");
|
|
1474
|
+
const body = { snapshot: opts.snapshot, count: opts.count };
|
|
1475
|
+
if (opts.timeoutMs !== undefined)
|
|
1476
|
+
body["timeout_ms"] = opts.timeoutMs;
|
|
1477
|
+
if (opts.spendCap !== undefined)
|
|
1478
|
+
body["spend_cap"] = checkSpendCap(opts.spendCap);
|
|
1479
|
+
if (opts.allowPartial !== undefined)
|
|
1480
|
+
body["allow_partial"] = opts.allowPartial;
|
|
1481
|
+
if (opts.verifyEach !== undefined)
|
|
1482
|
+
body["verify_each"] = opts.verifyEach;
|
|
1483
|
+
if (opts.network !== undefined)
|
|
1484
|
+
body["network"] = opts.network;
|
|
1485
|
+
if (opts.optimizeFor !== undefined)
|
|
1486
|
+
body["optimize_for"] = opts.optimizeFor;
|
|
1487
|
+
if (opts.secrets?.length)
|
|
1488
|
+
body["secrets"] = opts.secrets;
|
|
1489
|
+
if (opts.labels && Object.keys(opts.labels).length)
|
|
1490
|
+
body["labels"] = opts.labels;
|
|
1491
|
+
if (opts.exec) {
|
|
1492
|
+
const e = {};
|
|
1493
|
+
if (opts.exec.argv)
|
|
1494
|
+
e["argv"] = opts.exec.argv;
|
|
1495
|
+
if (opts.exec.sh)
|
|
1496
|
+
e["sh"] = opts.exec.sh;
|
|
1497
|
+
if (opts.exec.cwd)
|
|
1498
|
+
e["cwd"] = opts.exec.cwd;
|
|
1499
|
+
if (opts.exec.env)
|
|
1500
|
+
e["env"] = opts.exec.env;
|
|
1501
|
+
const t = commandTimeoutMs(opts.exec.timeoutMs);
|
|
1502
|
+
if (t !== undefined)
|
|
1503
|
+
e["timeout_ms"] = t;
|
|
1504
|
+
if (opts.exec.maxOutputBytes !== undefined)
|
|
1505
|
+
e["max_output_bytes"] = opts.exec.maxOutputBytes;
|
|
1506
|
+
e["kill_on_exit"] = opts.exec.killOnExit ?? true;
|
|
1507
|
+
body["exec"] = e;
|
|
1508
|
+
}
|
|
1509
|
+
if (opts.grade) {
|
|
1510
|
+
if (!opts.grade.cmd)
|
|
1511
|
+
throw new InvalidArgumentError("runs.start grade needs a cmd", "Pass { grade: { cmd: 'pytest -q tests/' } }.");
|
|
1512
|
+
const g = { cmd: opts.grade.cmd };
|
|
1513
|
+
if (opts.grade.holdoutCmd)
|
|
1514
|
+
g["holdout_cmd"] = opts.grade.holdoutCmd;
|
|
1515
|
+
if (opts.grade.timeoutMs !== undefined)
|
|
1516
|
+
g["timeout_ms"] = opts.grade.timeoutMs;
|
|
1517
|
+
if (opts.grade.score !== undefined)
|
|
1518
|
+
g["score"] = opts.grade.score;
|
|
1519
|
+
body["grade"] = g;
|
|
1520
|
+
}
|
|
1521
|
+
if (opts.gradeOutput !== undefined)
|
|
1522
|
+
body["grade_output"] = opts.gradeOutput;
|
|
1523
|
+
const waitMs = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(opts.waitMs, WAIT_MS_CAP) : 0;
|
|
1524
|
+
const req = { method: "POST", path: `/runs${waitQuery(waitMs)}`, body };
|
|
1525
|
+
if (waitMs > 0)
|
|
1526
|
+
req.timeoutMs = waitMs + this.requestTimeoutMs;
|
|
1527
|
+
if (opts.idempotencyKey)
|
|
1528
|
+
req.idempotencyKey = opts.idempotencyKey;
|
|
1529
|
+
if (opts.signal)
|
|
1530
|
+
req.signal = opts.signal;
|
|
1531
|
+
return this.request(req);
|
|
1532
|
+
},
|
|
1533
|
+
get: (runId, opts = {}) => this.request({
|
|
1534
1534
|
method: "GET",
|
|
1535
|
-
path: `/runs/${encodeURIComponent(runId)}
|
|
1535
|
+
path: `/runs/${encodeURIComponent(runId)}${waitQuery(opts.waitMs)}`,
|
|
1536
1536
|
...this.readExtras(opts),
|
|
1537
|
-
})
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
report: (runId) => this.request({ method: "GET", path: `/runs/${encodeURIComponent(runId)}/report` }),
|
|
1544
|
-
cancel: (runId) => this.request({ method: "POST", path: `/runs/${encodeURIComponent(runId)}/cancel`, body: {} }),
|
|
1545
|
-
kill: (runId) => this.request({ method: "DELETE", path: `/runs/${encodeURIComponent(runId)}` }),
|
|
1546
|
-
quote: (opts) => {
|
|
1547
|
-
if (!opts.snapshot)
|
|
1548
|
-
throw new InvalidArgumentError("runs.quote needs a snapshot", "Pass { snapshot: 'snap_… | alias | alias@commit7' }.");
|
|
1549
|
-
if (!Number.isInteger(opts.count) || opts.count < 1)
|
|
1550
|
-
throw new InvalidArgumentError("runs.quote needs count >= 1", "Pass { count: 1 } or more.");
|
|
1551
|
-
const body = { snapshot: opts.snapshot, count: opts.count };
|
|
1552
|
-
if (opts.timeoutMs !== undefined)
|
|
1553
|
-
body["timeout_ms"] = opts.timeoutMs;
|
|
1554
|
-
if (opts.tier !== undefined)
|
|
1555
|
-
body["tier"] = opts.tier;
|
|
1556
|
-
if (opts.spendCap !== undefined)
|
|
1557
|
-
body["spend_cap"] = checkSpendCap(opts.spendCap);
|
|
1558
|
-
return this.request({ method: "POST", path: "/runs/quote", body });
|
|
1559
|
-
},
|
|
1560
|
-
replay: (runId, sandboxIds, opts = {}) => this.request({
|
|
1561
|
-
method: "POST",
|
|
1562
|
-
path: `/runs/${encodeURIComponent(runId)}/replay`,
|
|
1563
|
-
body: {
|
|
1564
|
-
...(sandboxIds?.length ? { sandbox_ids: sandboxIds } : {}),
|
|
1565
|
-
...(opts.env && Object.keys(opts.env).length ? { env: opts.env } : {}),
|
|
1537
|
+
}),
|
|
1538
|
+
list: async (limit = 20) => (await this.runs.listPage({ limit })).items,
|
|
1539
|
+
listPage: async (opts = {}) => {
|
|
1540
|
+
const limit = opts.limit ?? 20;
|
|
1541
|
+
const r = await this.request({ method: "GET", path: `/runs?${pageQuery(limit, 500, opts.cursor)}` });
|
|
1542
|
+
return pageOf(r, "runs", limit);
|
|
1566
1543
|
},
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1544
|
+
status: (runId, opts = {}) => this.runs.get(runId, opts),
|
|
1545
|
+
sandboxes: async (runId, opts = {}) => {
|
|
1546
|
+
const r = await this.runs.sandboxesWithCapacity(runId, opts);
|
|
1547
|
+
return r.sandboxes;
|
|
1548
|
+
},
|
|
1549
|
+
sandboxesWithCapacity: async (runId, opts = {}) => {
|
|
1550
|
+
const r = await this.request({
|
|
1551
|
+
method: "GET",
|
|
1552
|
+
path: `/runs/${encodeURIComponent(runId)}/sandboxes${waitQuery(opts.waitMs)}`,
|
|
1553
|
+
...this.readExtras(opts),
|
|
1554
|
+
});
|
|
1555
|
+
if (Array.isArray(r))
|
|
1556
|
+
return { sandboxes: r };
|
|
1557
|
+
const sandboxes = r?.sandboxes ?? [];
|
|
1558
|
+
return r?.capacity ? { sandboxes, capacity: r.capacity } : { sandboxes };
|
|
1559
|
+
},
|
|
1560
|
+
report: (runId) => this.request({ method: "GET", path: `/runs/${encodeURIComponent(runId)}/report` }),
|
|
1561
|
+
cancel: (runId) => this.request({ method: "POST", path: `/runs/${encodeURIComponent(runId)}/cancel`, body: {} }),
|
|
1562
|
+
kill: (runId) => this.request({ method: "DELETE", path: `/runs/${encodeURIComponent(runId)}` }),
|
|
1563
|
+
quote: (opts) => {
|
|
1564
|
+
if (!opts.snapshot)
|
|
1565
|
+
throw new InvalidArgumentError("runs.quote needs a snapshot", "Pass { snapshot: 'snap_… | alias | alias@commit7' }.");
|
|
1566
|
+
if (!Number.isInteger(opts.count) || opts.count < 1)
|
|
1567
|
+
throw new InvalidArgumentError("runs.quote needs count >= 1", "Pass { count: 1 } or more.");
|
|
1568
|
+
const body = { snapshot: opts.snapshot, count: opts.count };
|
|
1569
|
+
if (opts.timeoutMs !== undefined)
|
|
1570
|
+
body["timeout_ms"] = opts.timeoutMs;
|
|
1571
|
+
if (opts.tier !== undefined)
|
|
1572
|
+
body["tier"] = opts.tier;
|
|
1573
|
+
if (opts.spendCap !== undefined)
|
|
1574
|
+
body["spend_cap"] = checkSpendCap(opts.spendCap);
|
|
1575
|
+
return this.request({ method: "POST", path: "/runs/quote", body });
|
|
1576
|
+
},
|
|
1577
|
+
replay: (runId, sandboxIds, opts = {}) => this.request({
|
|
1581
1578
|
method: "POST",
|
|
1582
|
-
path: `/runs/${encodeURIComponent(runId)}/
|
|
1583
|
-
body
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1579
|
+
path: `/runs/${encodeURIComponent(runId)}/replay`,
|
|
1580
|
+
body: {
|
|
1581
|
+
...(sandboxIds?.length ? { sandbox_ids: sandboxIds } : {}),
|
|
1582
|
+
...(opts.env && Object.keys(opts.env).length ? { env: opts.env } : {}),
|
|
1583
|
+
},
|
|
1584
|
+
}),
|
|
1585
|
+
captures: (runId) => this.request({
|
|
1586
|
+
method: "GET",
|
|
1587
|
+
path: `/runs/${encodeURIComponent(runId)}/captures`,
|
|
1588
|
+
}),
|
|
1589
|
+
open: (runId, opts = {}) => {
|
|
1590
|
+
const body = {};
|
|
1591
|
+
if (opts.sandboxId !== undefined)
|
|
1592
|
+
body["sandbox_id"] = opts.sandboxId;
|
|
1593
|
+
if (opts.spendCap !== undefined)
|
|
1594
|
+
body["spend_cap"] = opts.spendCap;
|
|
1595
|
+
if (opts.timeoutMs !== undefined)
|
|
1596
|
+
body["timeout_ms"] = opts.timeoutMs;
|
|
1597
|
+
return this.request({
|
|
1598
|
+
method: "POST",
|
|
1599
|
+
path: `/runs/${encodeURIComponent(runId)}/open`,
|
|
1600
|
+
body,
|
|
1601
|
+
});
|
|
1602
|
+
},
|
|
1603
|
+
waitReady: async (runId, opts = {}) => {
|
|
1604
|
+
const seen = new Set();
|
|
1605
|
+
const progress = opts.onReady !== undefined || opts.onCapacity !== undefined;
|
|
1606
|
+
const r = await this.waitLoop(opts, progress ? PROGRESS_POLL_MS : LONG_POLL_MS, [], async (read) => {
|
|
1607
|
+
const { sandboxes, capacity } = await this.runs.sandboxesWithCapacity(runId, read);
|
|
1608
|
+
if (capacity)
|
|
1609
|
+
opts.onCapacity?.(capacity);
|
|
1610
|
+
return sandboxes;
|
|
1611
|
+
}, (sandboxes) => {
|
|
1612
|
+
for (const s of sandboxes) {
|
|
1613
|
+
if (!isWaiting(s.state) && !seen.has(s.sandbox_id)) {
|
|
1614
|
+
seen.add(s.sandbox_id);
|
|
1615
|
+
opts.onReady?.(s);
|
|
1616
|
+
}
|
|
1599
1617
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1618
|
+
return sandboxes.length > 0 && sandboxes.every((s) => !isWaiting(s.state));
|
|
1619
|
+
});
|
|
1620
|
+
return { sandboxes: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
|
|
1621
|
+
},
|
|
1622
|
+
waitDone: async (runId, opts = {}) => {
|
|
1623
|
+
const r = await this.waitLoop(opts, LONG_POLL_MS, { run_id: runId, state: "running" }, (read) => this.runs.get(runId, read), (run) => {
|
|
1624
|
+
const terminal = run.state === "completed" || run.state === "cap_killed" || run.state === "aborted";
|
|
1625
|
+
const allDone = Array.isArray(run.sandboxes) && run.sandboxes.length > 0 &&
|
|
1626
|
+
run.sandboxes.every((s) => s.state === "ended" || s.command_outcome != null);
|
|
1627
|
+
return terminal || allDone;
|
|
1628
|
+
});
|
|
1629
|
+
return { run: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
|
|
1630
|
+
},
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1615
1633
|
async waitLoop(opts, maxWaitMs, empty, read, done) {
|
|
1616
1634
|
const bound = opts.waitTimeoutMs ?? WAIT_TIMEOUT_MS;
|
|
1617
1635
|
const started = Date.now();
|
|
@@ -1665,720 +1683,722 @@ export class PromigenceClient {
|
|
|
1665
1683
|
await sleep(Math.min(opts.pollMs ?? pollInterval(i), Math.max(1, bound - elapsed)));
|
|
1666
1684
|
}
|
|
1667
1685
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
const body = {};
|
|
1674
|
-
if (opts.argv?.length)
|
|
1675
|
-
body["argv"] = opts.argv;
|
|
1676
|
-
if (opts.sh)
|
|
1677
|
-
body["sh"] = opts.sh;
|
|
1678
|
-
if (opts.cwd)
|
|
1679
|
-
body["cwd"] = opts.cwd;
|
|
1680
|
-
if (opts.env)
|
|
1681
|
-
body["env"] = opts.env;
|
|
1682
|
-
if (opts.user)
|
|
1683
|
-
body["user"] = opts.user;
|
|
1684
|
-
if (opts.secrets?.length)
|
|
1685
|
-
body["secrets"] = opts.secrets;
|
|
1686
|
-
if (opts.terminal)
|
|
1687
|
-
body["terminal"] = true;
|
|
1688
|
-
if (opts.killOnExit !== undefined)
|
|
1689
|
-
body["kill_on_exit"] = opts.killOnExit;
|
|
1690
|
-
const timeoutMs = commandTimeoutMs(opts.timeoutMs) ?? 15 * 60_000;
|
|
1691
|
-
body["timeout_ms"] = timeoutMs;
|
|
1692
|
-
body["max_output_bytes"] = opts.maxOutputBytes ?? MAX_OUTPUT_BYTES_DEFAULT;
|
|
1693
|
-
if (opts.encoding === "base64")
|
|
1694
|
-
body["encoding"] = "base64";
|
|
1695
|
-
const req = {
|
|
1696
|
-
method: "POST",
|
|
1697
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1698
|
-
body,
|
|
1699
|
-
timeoutMs: timeoutMs + 30_000,
|
|
1700
|
-
};
|
|
1701
|
-
if (opts.idempotencyKey)
|
|
1702
|
-
req.idempotencyKey = opts.idempotencyKey;
|
|
1703
|
-
if (opts.signal)
|
|
1704
|
-
req.signal = opts.signal;
|
|
1705
|
-
return this.request(req);
|
|
1706
|
-
},
|
|
1707
|
-
execStream: async (sandboxId, opts, on = {}) => {
|
|
1708
|
-
if (!opts.argv?.length && !opts.sh) {
|
|
1709
|
-
throw new InvalidArgumentError("exec needs argv or sh", "Pass { argv: ['bun','typecheck'] } or { sh: 'bun typecheck' }.");
|
|
1710
|
-
}
|
|
1711
|
-
if (opts.idempotencyKey) {
|
|
1712
|
-
throw new InvalidArgumentError("a streamed exec cannot take an idempotencyKey (its output cannot be replayed)", "Use sandboxes.exec (buffered) or sandboxes.execBackground with the key, or stream without one.");
|
|
1713
|
-
}
|
|
1714
|
-
const body = {};
|
|
1715
|
-
if (opts.argv?.length)
|
|
1716
|
-
body["argv"] = opts.argv;
|
|
1717
|
-
if (opts.sh)
|
|
1718
|
-
body["sh"] = opts.sh;
|
|
1719
|
-
if (opts.cwd)
|
|
1720
|
-
body["cwd"] = opts.cwd;
|
|
1721
|
-
if (opts.env)
|
|
1722
|
-
body["env"] = opts.env;
|
|
1723
|
-
if (opts.user)
|
|
1724
|
-
body["user"] = opts.user;
|
|
1725
|
-
if (opts.secrets?.length)
|
|
1726
|
-
body["secrets"] = opts.secrets;
|
|
1727
|
-
if (opts.terminal)
|
|
1728
|
-
body["terminal"] = true;
|
|
1729
|
-
if (opts.killOnExit !== undefined)
|
|
1730
|
-
body["kill_on_exit"] = opts.killOnExit;
|
|
1731
|
-
body["timeout_ms"] = commandTimeoutMs(opts.timeoutMs) ?? 15 * 60_000;
|
|
1732
|
-
body["max_output_bytes"] = opts.maxOutputBytes ?? MAX_OUTPUT_BYTES_DEFAULT;
|
|
1733
|
-
const url = `${this.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/execs`;
|
|
1734
|
-
const idle = new AbortController();
|
|
1735
|
-
let silent = false;
|
|
1736
|
-
let reader;
|
|
1737
|
-
let timer;
|
|
1738
|
-
const arm = () => {
|
|
1739
|
-
clearTimeout(timer);
|
|
1740
|
-
timer = setTimeout(() => {
|
|
1741
|
-
silent = true;
|
|
1742
|
-
idle.abort();
|
|
1743
|
-
reader?.cancel().catch(() => { });
|
|
1744
|
-
}, STREAM_IDLE_MS);
|
|
1745
|
-
};
|
|
1746
|
-
let stream;
|
|
1747
|
-
const bearer = `Bearer ${this.requireKey()}`;
|
|
1748
|
-
const dispatcher = this.guardDispatcher();
|
|
1749
|
-
for (let attempt = 0; stream === undefined; attempt++) {
|
|
1750
|
-
let r;
|
|
1751
|
-
this.stats.requests++;
|
|
1752
|
-
arm();
|
|
1753
|
-
try {
|
|
1754
|
-
r = await this.fetchImpl(url, {
|
|
1755
|
-
method: "POST",
|
|
1756
|
-
headers: {
|
|
1757
|
-
accept: "text/event-stream",
|
|
1758
|
-
"content-type": "application/json",
|
|
1759
|
-
"user-agent": this.userAgent,
|
|
1760
|
-
authorization: bearer,
|
|
1761
|
-
...(this.caller ? { [CALLER_HEADER]: this.caller } : {}),
|
|
1762
|
-
},
|
|
1763
|
-
body: JSON.stringify(body),
|
|
1764
|
-
signal: opts.signal ? AbortSignal.any([opts.signal, idle.signal]) : idle.signal,
|
|
1765
|
-
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
catch (err) {
|
|
1769
|
-
clearTimeout(timer);
|
|
1770
|
-
const e = err;
|
|
1771
|
-
const why = silent ? `no answer in ${STREAM_IDLE_MS / 1000} s` : transportDetail(e);
|
|
1772
|
-
throw new UnreachableError(`cannot reach ${url}: ${why}`);
|
|
1773
|
-
}
|
|
1774
|
-
if (r.ok && r.body) {
|
|
1775
|
-
stream = r.body;
|
|
1776
|
-
break;
|
|
1686
|
+
group_sandboxes() {
|
|
1687
|
+
return {
|
|
1688
|
+
exec: (sandboxId, opts) => {
|
|
1689
|
+
if (!opts.argv?.length && !opts.sh) {
|
|
1690
|
+
throw new InvalidArgumentError("exec needs argv or sh", "Pass { argv: ['bun','typecheck'] } or { sh: 'bun typecheck' }.");
|
|
1777
1691
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1692
|
+
const body = {};
|
|
1693
|
+
if (opts.argv?.length)
|
|
1694
|
+
body["argv"] = opts.argv;
|
|
1695
|
+
if (opts.sh)
|
|
1696
|
+
body["sh"] = opts.sh;
|
|
1697
|
+
if (opts.cwd)
|
|
1698
|
+
body["cwd"] = opts.cwd;
|
|
1699
|
+
if (opts.env)
|
|
1700
|
+
body["env"] = opts.env;
|
|
1701
|
+
if (opts.user)
|
|
1702
|
+
body["user"] = opts.user;
|
|
1703
|
+
if (opts.secrets?.length)
|
|
1704
|
+
body["secrets"] = opts.secrets;
|
|
1705
|
+
if (opts.terminal)
|
|
1706
|
+
body["terminal"] = true;
|
|
1707
|
+
if (opts.killOnExit !== undefined)
|
|
1708
|
+
body["kill_on_exit"] = opts.killOnExit;
|
|
1709
|
+
const timeoutMs = commandTimeoutMs(opts.timeoutMs) ?? 15 * 60_000;
|
|
1710
|
+
body["timeout_ms"] = timeoutMs;
|
|
1711
|
+
body["max_output_bytes"] = opts.maxOutputBytes ?? MAX_OUTPUT_BYTES_DEFAULT;
|
|
1712
|
+
if (opts.encoding === "base64")
|
|
1713
|
+
body["encoding"] = "base64";
|
|
1714
|
+
const req = {
|
|
1715
|
+
method: "POST",
|
|
1716
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1717
|
+
body,
|
|
1718
|
+
timeoutMs: timeoutMs + 30_000,
|
|
1719
|
+
};
|
|
1720
|
+
if (opts.idempotencyKey)
|
|
1721
|
+
req.idempotencyKey = opts.idempotencyKey;
|
|
1722
|
+
if (opts.signal)
|
|
1723
|
+
req.signal = opts.signal;
|
|
1724
|
+
return this.request(req);
|
|
1725
|
+
},
|
|
1726
|
+
execStream: async (sandboxId, opts, on = {}) => {
|
|
1727
|
+
if (!opts.argv?.length && !opts.sh) {
|
|
1728
|
+
throw new InvalidArgumentError("exec needs argv or sh", "Pass { argv: ['bun','typecheck'] } or { sh: 'bun typecheck' }.");
|
|
1783
1729
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
const wait = this.refusalBackoff(r.status, retryAfterMs, payload.error?.code, attempt, false, opts.signal);
|
|
1787
|
-
if (wait !== undefined) {
|
|
1788
|
-
this.stats.retries++;
|
|
1789
|
-
await sleep(wait);
|
|
1790
|
-
continue;
|
|
1730
|
+
if (opts.idempotencyKey) {
|
|
1731
|
+
throw new InvalidArgumentError("a streamed exec cannot take an idempotencyKey (its output cannot be replayed)", "Use sandboxes.exec (buffered) or sandboxes.execBackground with the key, or stream without one.");
|
|
1791
1732
|
}
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1733
|
+
const body = {};
|
|
1734
|
+
if (opts.argv?.length)
|
|
1735
|
+
body["argv"] = opts.argv;
|
|
1736
|
+
if (opts.sh)
|
|
1737
|
+
body["sh"] = opts.sh;
|
|
1738
|
+
if (opts.cwd)
|
|
1739
|
+
body["cwd"] = opts.cwd;
|
|
1740
|
+
if (opts.env)
|
|
1741
|
+
body["env"] = opts.env;
|
|
1742
|
+
if (opts.user)
|
|
1743
|
+
body["user"] = opts.user;
|
|
1744
|
+
if (opts.secrets?.length)
|
|
1745
|
+
body["secrets"] = opts.secrets;
|
|
1746
|
+
if (opts.terminal)
|
|
1747
|
+
body["terminal"] = true;
|
|
1748
|
+
if (opts.killOnExit !== undefined)
|
|
1749
|
+
body["kill_on_exit"] = opts.killOnExit;
|
|
1750
|
+
body["timeout_ms"] = commandTimeoutMs(opts.timeoutMs) ?? 15 * 60_000;
|
|
1751
|
+
body["max_output_bytes"] = opts.maxOutputBytes ?? MAX_OUTPUT_BYTES_DEFAULT;
|
|
1752
|
+
const url = `${this.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/execs`;
|
|
1753
|
+
const idle = new AbortController();
|
|
1754
|
+
let silent = false;
|
|
1755
|
+
let reader;
|
|
1756
|
+
let timer;
|
|
1757
|
+
const arm = () => {
|
|
1758
|
+
clearTimeout(timer);
|
|
1759
|
+
timer = setTimeout(() => {
|
|
1760
|
+
silent = true;
|
|
1761
|
+
idle.abort();
|
|
1762
|
+
reader?.cancel().catch(() => { });
|
|
1763
|
+
}, STREAM_IDLE_MS);
|
|
1764
|
+
};
|
|
1765
|
+
let stream;
|
|
1766
|
+
const bearer = `Bearer ${this.requireKey()}`;
|
|
1767
|
+
const dispatcher = this.guardDispatcher();
|
|
1768
|
+
for (let attempt = 0; stream === undefined; attempt++) {
|
|
1769
|
+
let r;
|
|
1770
|
+
this.stats.requests++;
|
|
1810
1771
|
arm();
|
|
1811
|
-
let chunk;
|
|
1812
1772
|
try {
|
|
1813
|
-
|
|
1773
|
+
r = await this.fetchImpl(url, {
|
|
1774
|
+
method: "POST",
|
|
1775
|
+
headers: {
|
|
1776
|
+
accept: "text/event-stream",
|
|
1777
|
+
"content-type": "application/json",
|
|
1778
|
+
"user-agent": this.userAgent,
|
|
1779
|
+
authorization: bearer,
|
|
1780
|
+
...(this.caller ? { [CALLER_HEADER]: this.caller } : {}),
|
|
1781
|
+
},
|
|
1782
|
+
body: JSON.stringify(body),
|
|
1783
|
+
signal: opts.signal ? AbortSignal.any([opts.signal, idle.signal]) : idle.signal,
|
|
1784
|
+
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1785
|
+
});
|
|
1814
1786
|
}
|
|
1815
1787
|
catch (err) {
|
|
1816
|
-
|
|
1817
|
-
throw err;
|
|
1818
|
-
if (silent)
|
|
1819
|
-
throw lost(`was silent for ${STREAM_IDLE_MS / 1000} s`);
|
|
1788
|
+
clearTimeout(timer);
|
|
1820
1789
|
const e = err;
|
|
1821
|
-
|
|
1790
|
+
const why = silent ? `no answer in ${STREAM_IDLE_MS / 1000} s` : transportDetail(e);
|
|
1791
|
+
throw new UnreachableError(`cannot reach ${url}: ${why}`);
|
|
1822
1792
|
}
|
|
1823
|
-
if (
|
|
1793
|
+
if (r.ok && r.body) {
|
|
1794
|
+
stream = r.body;
|
|
1824
1795
|
break;
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1796
|
+
}
|
|
1797
|
+
clearTimeout(timer);
|
|
1798
|
+
const text = await r.text().catch(() => "");
|
|
1799
|
+
let payload = {};
|
|
1800
|
+
try {
|
|
1801
|
+
payload = text ? JSON.parse(text) : {};
|
|
1802
|
+
}
|
|
1803
|
+
catch { }
|
|
1804
|
+
const retryAfterMs = parseRetryAfter(r.headers.get("retry-after"));
|
|
1805
|
+
const wait = this.refusalBackoff(r.status, retryAfterMs, payload.error?.code, attempt, false, opts.signal);
|
|
1806
|
+
if (wait !== undefined) {
|
|
1807
|
+
this.stats.retries++;
|
|
1808
|
+
await sleep(wait);
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
const code = payload.error?.code ?? codeForStatus(r.status);
|
|
1812
|
+
throw errorFor(code, payload.error?.message ?? `HTTP ${r.status} from POST /sandboxes/${sandboxId}/execs`, payload.error?.fix ?? "See the API response for details.", {
|
|
1813
|
+
status: r.status,
|
|
1814
|
+
...bodyExtra(payload),
|
|
1815
|
+
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
const out = [];
|
|
1819
|
+
const errOut = [];
|
|
1820
|
+
let exit;
|
|
1821
|
+
let failure;
|
|
1822
|
+
const dec = new TextDecoder();
|
|
1823
|
+
let buf = "";
|
|
1824
|
+
const lost = (how) => new UnreachableError(`the exec stream for sandbox ${sandboxId} ${how}`, "The connection dropped before the command finished; its result is unknown. " +
|
|
1825
|
+
`Read it by id (\`promigence exec output <exec_id> --sandbox ${hintArg(sandboxId)}\`) or re-run.`);
|
|
1826
|
+
reader = stream.getReader();
|
|
1827
|
+
try {
|
|
1828
|
+
for (;;) {
|
|
1829
|
+
arm();
|
|
1830
|
+
let chunk;
|
|
1850
1831
|
try {
|
|
1851
|
-
|
|
1832
|
+
chunk = await reader.read();
|
|
1852
1833
|
}
|
|
1853
|
-
catch {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
if (
|
|
1857
|
-
|
|
1858
|
-
|
|
1834
|
+
catch (err) {
|
|
1835
|
+
if (opts.signal?.aborted)
|
|
1836
|
+
throw err;
|
|
1837
|
+
if (silent)
|
|
1838
|
+
throw lost(`was silent for ${STREAM_IDLE_MS / 1000} s`);
|
|
1839
|
+
const e = err;
|
|
1840
|
+
throw lost(`was cut off (${e?.cause?.code ?? e?.message ?? "connection lost"})`);
|
|
1841
|
+
}
|
|
1842
|
+
if (chunk.done)
|
|
1843
|
+
break;
|
|
1844
|
+
const value = chunk.value;
|
|
1845
|
+
buf += dec.decode(value, { stream: true });
|
|
1846
|
+
let sep;
|
|
1847
|
+
while ((sep = buf.indexOf("\n\n")) >= 0) {
|
|
1848
|
+
const block = buf.slice(0, sep);
|
|
1849
|
+
buf = buf.slice(sep + 2);
|
|
1850
|
+
let event = "";
|
|
1851
|
+
let data = "";
|
|
1852
|
+
for (const line of block.split("\n")) {
|
|
1853
|
+
if (line.startsWith("event:"))
|
|
1854
|
+
event = line.slice(6).trim();
|
|
1855
|
+
else if (line.startsWith("data:"))
|
|
1856
|
+
data = line.slice(5).replace(/^ /, "");
|
|
1859
1857
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1858
|
+
if (!event)
|
|
1859
|
+
continue;
|
|
1860
|
+
if (event === "heartbeat") {
|
|
1861
|
+
on.heartbeat?.();
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (event === "reconnect") {
|
|
1865
|
+
reader.cancel().catch(() => { });
|
|
1866
|
+
throw new ServiceRestartError(`a service restart stopped the command on sandbox ${sandboxId} while its output was streaming`, "Not billed: the time the command ran is credited back. The sandbox kept running; the command may have done part of its work before it stopped: run it again if that is safe.");
|
|
1867
|
+
}
|
|
1868
|
+
let parsed = {};
|
|
1869
|
+
try {
|
|
1870
|
+
parsed = JSON.parse(data);
|
|
1871
|
+
}
|
|
1872
|
+
catch { }
|
|
1873
|
+
if (event === "stdout" || event === "stderr") {
|
|
1874
|
+
const bytes = b64ToBytes(String(parsed["data"] ?? ""));
|
|
1875
|
+
if (event === "stdout") {
|
|
1876
|
+
out.push(bytes);
|
|
1877
|
+
on.stdout?.(bytes);
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
errOut.push(bytes);
|
|
1881
|
+
on.stderr?.(bytes);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
else if (event === "exit") {
|
|
1885
|
+
exit = { ...(exit ?? {}), ...parsed };
|
|
1886
|
+
}
|
|
1887
|
+
else if (event === "error") {
|
|
1888
|
+
failure = parsed;
|
|
1863
1889
|
}
|
|
1864
|
-
}
|
|
1865
|
-
else if (event === "exit") {
|
|
1866
|
-
exit = { ...(exit ?? {}), ...parsed };
|
|
1867
|
-
}
|
|
1868
|
-
else if (event === "error") {
|
|
1869
|
-
failure = parsed;
|
|
1870
1890
|
}
|
|
1871
1891
|
}
|
|
1872
1892
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
clearTimeout(timer);
|
|
1876
|
-
}
|
|
1877
|
-
if (failure) {
|
|
1878
|
-
throw errorFor(failure.code ?? "internal", failure.message ?? "the exec stream failed", failure.fix ?? "Re-run; if it repeats, include the exec_id.", { status: 200, ...bodyExtra({ error: failure }) });
|
|
1879
|
-
}
|
|
1880
|
-
if (!exit && silent)
|
|
1881
|
-
throw lost(`was silent for ${STREAM_IDLE_MS / 1000} s`);
|
|
1882
|
-
if (!exit) {
|
|
1883
|
-
throw lost("ended without an exit event");
|
|
1884
|
-
}
|
|
1885
|
-
const rawCode = exit["exit_code"];
|
|
1886
|
-
const signal = exit["signal"];
|
|
1887
|
-
const exitCode = typeof rawCode === "number" && Number.isFinite(rawCode)
|
|
1888
|
-
? rawCode
|
|
1889
|
-
: typeof signal === "number" && signal > 0 ? 128 + signal : null;
|
|
1890
|
-
const join = (parts) => {
|
|
1891
|
-
const n = parts.reduce((a, p) => a + p.length, 0);
|
|
1892
|
-
const all = new Uint8Array(n);
|
|
1893
|
-
let at = 0;
|
|
1894
|
-
for (const p of parts) {
|
|
1895
|
-
all.set(p, at);
|
|
1896
|
-
at += p.length;
|
|
1893
|
+
finally {
|
|
1894
|
+
clearTimeout(timer);
|
|
1897
1895
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
return {
|
|
1901
|
-
exec_id: String(exit["exec_id"] ?? ""),
|
|
1902
|
-
sandbox_id: sandboxId,
|
|
1903
|
-
exit_code: exitCode,
|
|
1904
|
-
stdout: join(out),
|
|
1905
|
-
stderr: join(errOut),
|
|
1906
|
-
truncated: Boolean(exit["truncated"] ?? false),
|
|
1907
|
-
...(exit["timed_out"] === true ? { timed_out: true } : {}),
|
|
1908
|
-
...(exit["oom_killed"] === true ? { oom_killed: true } : {}),
|
|
1909
|
-
...(typeof exit["reason"] === "string" ? { reason: exit["reason"] } : {}),
|
|
1910
|
-
...(typeof signal === "number" ? { signal } : {}),
|
|
1911
|
-
};
|
|
1912
|
-
},
|
|
1913
|
-
execBackground: (sandboxId, opts) => {
|
|
1914
|
-
if (!opts.argv?.length && !opts.sh) {
|
|
1915
|
-
throw new InvalidArgumentError("exec needs argv or sh", "Pass { argv: ['npm','run','dev'] } or { sh: 'npm run dev' }.");
|
|
1916
|
-
}
|
|
1917
|
-
const body = { background: true };
|
|
1918
|
-
if (opts.argv?.length)
|
|
1919
|
-
body["argv"] = opts.argv;
|
|
1920
|
-
if (opts.sh)
|
|
1921
|
-
body["sh"] = opts.sh;
|
|
1922
|
-
if (opts.cwd)
|
|
1923
|
-
body["cwd"] = opts.cwd;
|
|
1924
|
-
if (opts.env)
|
|
1925
|
-
body["env"] = opts.env;
|
|
1926
|
-
if (opts.user)
|
|
1927
|
-
body["user"] = opts.user;
|
|
1928
|
-
if (opts.secrets?.length)
|
|
1929
|
-
body["secrets"] = opts.secrets;
|
|
1930
|
-
if (opts.stdin)
|
|
1931
|
-
body["stdin"] = true;
|
|
1932
|
-
if (opts.follow)
|
|
1933
|
-
body["follow"] = true;
|
|
1934
|
-
const timeoutMs = commandTimeoutMs(opts.timeoutMs);
|
|
1935
|
-
if (timeoutMs !== undefined)
|
|
1936
|
-
body["timeout_ms"] = timeoutMs;
|
|
1937
|
-
if (opts.maxOutputBytes !== undefined)
|
|
1938
|
-
body["max_output_bytes"] = opts.maxOutputBytes;
|
|
1939
|
-
return this.request({
|
|
1940
|
-
method: "POST",
|
|
1941
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1942
|
-
body,
|
|
1943
|
-
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
1944
|
-
});
|
|
1945
|
-
},
|
|
1946
|
-
execs: {
|
|
1947
|
-
list: async (sandboxId) => {
|
|
1948
|
-
const r = await this.request({
|
|
1949
|
-
method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1950
|
-
});
|
|
1951
|
-
return r.execs ?? [];
|
|
1952
|
-
},
|
|
1953
|
-
get: (sandboxId, execId, opts = {}) => {
|
|
1954
|
-
const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
|
|
1955
|
-
return this.request({
|
|
1956
|
-
method: "GET",
|
|
1957
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}${wait ? `?wait_ms=${wait}` : ""}`,
|
|
1958
|
-
...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
|
|
1959
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1960
|
-
});
|
|
1961
|
-
},
|
|
1962
|
-
read: async (sandboxId, execId, stream, opts = {}) => {
|
|
1963
|
-
const q = [`stream=${stream}`, `offset=${Math.max(0, Math.floor(opts.offset ?? 0))}`];
|
|
1964
|
-
if (opts.len !== undefined)
|
|
1965
|
-
q.push(`len=${Math.max(0, Math.floor(opts.len))}`);
|
|
1966
|
-
const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
|
|
1967
|
-
if (wait)
|
|
1968
|
-
q.push(`wait_ms=${wait}`);
|
|
1969
|
-
if (wait && opts.untilOutput)
|
|
1970
|
-
q.push("until=output");
|
|
1971
|
-
const r = await this.request({
|
|
1972
|
-
method: "GET",
|
|
1973
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}?${q.join("&")}`,
|
|
1974
|
-
...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
|
|
1975
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1976
|
-
});
|
|
1977
|
-
return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), skipped: r.skipped ?? 0, record: r };
|
|
1978
|
-
},
|
|
1979
|
-
stdin: (sandboxId, execId, opts = {}) => {
|
|
1980
|
-
if (opts.data === undefined && !opts.eof) {
|
|
1981
|
-
throw new InvalidArgumentError("execs.stdin needs data, eof, or both", "Pass { data: 'y\\n' } to write, { eof: true } to close, or both to do each in one call.");
|
|
1896
|
+
if (failure) {
|
|
1897
|
+
throw errorFor(failure.code ?? "internal", failure.message ?? "the exec stream failed", failure.fix ?? "Re-run; if it repeats, include the exec_id.", { status: 200, ...bodyExtra({ error: failure }) });
|
|
1982
1898
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
body: {
|
|
1988
|
-
...(bytes === undefined ? {} : { data: bytesToB64(bytes) }),
|
|
1989
|
-
...(opts.eof ? { eof: true } : {}),
|
|
1990
|
-
},
|
|
1991
|
-
});
|
|
1992
|
-
},
|
|
1993
|
-
signal: (sandboxId, execId, signal = "SIGTERM") => this.request({
|
|
1994
|
-
method: "POST",
|
|
1995
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}/signal`,
|
|
1996
|
-
body: { signal },
|
|
1997
|
-
}),
|
|
1998
|
-
forget: (sandboxId, execId) => this.request({
|
|
1999
|
-
method: "DELETE",
|
|
2000
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}`,
|
|
2001
|
-
}),
|
|
2002
|
-
},
|
|
2003
|
-
ports: {
|
|
2004
|
-
expose: (sandboxId, port) => {
|
|
2005
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2006
|
-
throw new InvalidArgumentError(`port must be an integer 1..65535, got ${port}`, "Pass the port your process listens on inside the sandbox, e.g. 3000.");
|
|
1899
|
+
if (!exit && silent)
|
|
1900
|
+
throw lost(`was silent for ${STREAM_IDLE_MS / 1000} s`);
|
|
1901
|
+
if (!exit) {
|
|
1902
|
+
throw lost("ended without an exit event");
|
|
2007
1903
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
1904
|
+
const rawCode = exit["exit_code"];
|
|
1905
|
+
const signal = exit["signal"];
|
|
1906
|
+
const exitCode = typeof rawCode === "number" && Number.isFinite(rawCode)
|
|
1907
|
+
? rawCode
|
|
1908
|
+
: typeof signal === "number" && signal > 0 ? 128 + signal : null;
|
|
1909
|
+
const join = (parts) => {
|
|
1910
|
+
const n = parts.reduce((a, p) => a + p.length, 0);
|
|
1911
|
+
const all = new Uint8Array(n);
|
|
1912
|
+
let at = 0;
|
|
1913
|
+
for (const p of parts) {
|
|
1914
|
+
all.set(p, at);
|
|
1915
|
+
at += p.length;
|
|
1916
|
+
}
|
|
1917
|
+
return new TextDecoder().decode(all);
|
|
1918
|
+
};
|
|
1919
|
+
return {
|
|
1920
|
+
exec_id: String(exit["exec_id"] ?? ""),
|
|
1921
|
+
sandbox_id: sandboxId,
|
|
1922
|
+
exit_code: exitCode,
|
|
1923
|
+
stdout: join(out),
|
|
1924
|
+
stderr: join(errOut),
|
|
1925
|
+
truncated: Boolean(exit["truncated"] ?? false),
|
|
1926
|
+
...(exit["timed_out"] === true ? { timed_out: true } : {}),
|
|
1927
|
+
...(exit["oom_killed"] === true ? { oom_killed: true } : {}),
|
|
1928
|
+
...(typeof exit["reason"] === "string" ? { reason: exit["reason"] } : {}),
|
|
1929
|
+
...(typeof signal === "number" ? { signal } : {}),
|
|
1930
|
+
};
|
|
2017
1931
|
},
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
}),
|
|
2022
|
-
},
|
|
2023
|
-
ssh: {
|
|
2024
|
-
open: (sandboxId, opts) => {
|
|
2025
|
-
if (!opts.publicKey?.trim()) {
|
|
2026
|
-
throw new InvalidArgumentError("publicKey is required", "Pass the contents of an ssh-ed25519 .pub file, or use `promigence sandbox ssh <id>`, which makes one.");
|
|
1932
|
+
execBackground: (sandboxId, opts) => {
|
|
1933
|
+
if (!opts.argv?.length && !opts.sh) {
|
|
1934
|
+
throw new InvalidArgumentError("exec needs argv or sh", "Pass { argv: ['npm','run','dev'] } or { sh: 'npm run dev' }.");
|
|
2027
1935
|
}
|
|
1936
|
+
const body = { background: true };
|
|
1937
|
+
if (opts.argv?.length)
|
|
1938
|
+
body["argv"] = opts.argv;
|
|
1939
|
+
if (opts.sh)
|
|
1940
|
+
body["sh"] = opts.sh;
|
|
1941
|
+
if (opts.cwd)
|
|
1942
|
+
body["cwd"] = opts.cwd;
|
|
1943
|
+
if (opts.env)
|
|
1944
|
+
body["env"] = opts.env;
|
|
1945
|
+
if (opts.user)
|
|
1946
|
+
body["user"] = opts.user;
|
|
1947
|
+
if (opts.secrets?.length)
|
|
1948
|
+
body["secrets"] = opts.secrets;
|
|
1949
|
+
if (opts.stdin)
|
|
1950
|
+
body["stdin"] = true;
|
|
1951
|
+
if (opts.follow)
|
|
1952
|
+
body["follow"] = true;
|
|
1953
|
+
const timeoutMs = commandTimeoutMs(opts.timeoutMs);
|
|
1954
|
+
if (timeoutMs !== undefined)
|
|
1955
|
+
body["timeout_ms"] = timeoutMs;
|
|
1956
|
+
if (opts.maxOutputBytes !== undefined)
|
|
1957
|
+
body["max_output_bytes"] = opts.maxOutputBytes;
|
|
2028
1958
|
return this.request({
|
|
2029
1959
|
method: "POST",
|
|
2030
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/
|
|
2031
|
-
body
|
|
2032
|
-
|
|
2033
|
-
...(opts.ttlS === undefined ? {} : { ttl_s: Math.floor(opts.ttlS) }),
|
|
2034
|
-
...(opts.idleTimeoutS === undefined
|
|
2035
|
-
? {}
|
|
2036
|
-
: { idle_timeout_s: Math.floor(opts.idleTimeoutS) }),
|
|
2037
|
-
},
|
|
1960
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1961
|
+
body,
|
|
1962
|
+
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
2038
1963
|
});
|
|
2039
1964
|
},
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ssh/${encodeURIComponent(keyIdOrFingerprint)}`,
|
|
2047
|
-
}),
|
|
2048
|
-
connect: (sandboxId, opts) => connectSshTunnel({
|
|
2049
|
-
url: `${this.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/ssh/connect`,
|
|
2050
|
-
token: opts.token ?? this.requireKey(),
|
|
2051
|
-
userAgent: this.userAgent,
|
|
2052
|
-
...(this.caller ? { caller: this.caller } : {}),
|
|
2053
|
-
onData: opts.onData,
|
|
2054
|
-
onClose: opts.onClose,
|
|
2055
|
-
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
2056
|
-
refuse: (status, text) => {
|
|
2057
|
-
let payload = {};
|
|
2058
|
-
try {
|
|
2059
|
-
payload = text ? JSON.parse(text) : {};
|
|
2060
|
-
}
|
|
2061
|
-
catch { }
|
|
2062
|
-
const code = payload.error?.code ?? (status === 0 ? "unreachable" : codeForStatus(status));
|
|
2063
|
-
return errorFor(code, payload.error?.message ?? `HTTP ${status} from the SSH tunnel`, payload.error?.fix ?? "See the API response for details.", { status, ...bodyExtra(payload) });
|
|
1965
|
+
execs: {
|
|
1966
|
+
list: async (sandboxId) => {
|
|
1967
|
+
const r = await this.request({
|
|
1968
|
+
method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs`,
|
|
1969
|
+
});
|
|
1970
|
+
return r.execs ?? [];
|
|
2064
1971
|
},
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
q.push(`rows=${Math.floor(opts.rows)}`);
|
|
2073
|
-
const path = `/sandboxes/${encodeURIComponent(sandboxId)}/pty${q.length ? `?${q.join("&")}` : ""}`;
|
|
2074
|
-
const open = () => connectPty({
|
|
2075
|
-
url: `${this.apiUrl}${path}`,
|
|
2076
|
-
apiKey: this.requireKey(),
|
|
2077
|
-
userAgent: this.userAgent,
|
|
2078
|
-
...(this.caller ? { caller: this.caller } : {}),
|
|
2079
|
-
...(opts.cmd ? { command: opts.cmd } : {}),
|
|
2080
|
-
...(opts.secrets?.length ? { secrets: opts.secrets } : {}),
|
|
2081
|
-
...(opts.onData ? { onData: opts.onData } : {}),
|
|
2082
|
-
...(opts.onExit ? { onExit: opts.onExit } : {}),
|
|
2083
|
-
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
2084
|
-
...(opts.resume === undefined ? {} : { resume: opts.resume }),
|
|
2085
|
-
...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
|
|
2086
|
-
...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
|
|
2087
|
-
refuse: (status, text, retryAfter) => {
|
|
2088
|
-
let payload = {};
|
|
2089
|
-
try {
|
|
2090
|
-
payload = text ? JSON.parse(text) : {};
|
|
2091
|
-
}
|
|
2092
|
-
catch { }
|
|
2093
|
-
const code = payload.error?.code ?? (status === 0 ? "unreachable" : codeForStatus(status));
|
|
2094
|
-
const retryAfterMs = parseRetryAfter(retryAfter ?? null);
|
|
2095
|
-
return errorFor(code, payload.error?.message ?? `HTTP ${status} from GET ${path}`, payload.error?.fix ?? "See the API response for details.", {
|
|
2096
|
-
status,
|
|
2097
|
-
...bodyExtra(payload),
|
|
2098
|
-
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
1972
|
+
get: (sandboxId, execId, opts = {}) => {
|
|
1973
|
+
const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
|
|
1974
|
+
return this.request({
|
|
1975
|
+
method: "GET",
|
|
1976
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}${wait ? `?wait_ms=${wait}` : ""}`,
|
|
1977
|
+
...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
|
|
1978
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
2099
1979
|
});
|
|
2100
1980
|
},
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
1981
|
+
read: async (sandboxId, execId, stream, opts = {}) => {
|
|
1982
|
+
const q = [`stream=${stream}`, `offset=${Math.max(0, Math.floor(opts.offset ?? 0))}`];
|
|
1983
|
+
if (opts.len !== undefined)
|
|
1984
|
+
q.push(`len=${Math.max(0, Math.floor(opts.len))}`);
|
|
1985
|
+
const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
|
|
1986
|
+
if (wait)
|
|
1987
|
+
q.push(`wait_ms=${wait}`);
|
|
1988
|
+
if (wait && opts.untilOutput)
|
|
1989
|
+
q.push("until=output");
|
|
1990
|
+
const r = await this.request({
|
|
1991
|
+
method: "GET",
|
|
1992
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}?${q.join("&")}`,
|
|
1993
|
+
...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
|
|
1994
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
1995
|
+
});
|
|
1996
|
+
return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), skipped: r.skipped ?? 0, record: r };
|
|
1997
|
+
},
|
|
1998
|
+
stdin: (sandboxId, execId, opts = {}) => {
|
|
1999
|
+
if (opts.data === undefined && !opts.eof) {
|
|
2000
|
+
throw new InvalidArgumentError("execs.stdin needs data, eof, or both", "Pass { data: 'y\\n' } to write, { eof: true } to close, or both to do each in one call.");
|
|
2114
2001
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2002
|
+
const bytes = typeof opts.data === "string" ? new TextEncoder().encode(opts.data) : opts.data;
|
|
2003
|
+
return this.request({
|
|
2004
|
+
method: "POST",
|
|
2005
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}/stdin`,
|
|
2006
|
+
body: {
|
|
2007
|
+
...(bytes === undefined ? {} : { data: bytesToB64(bytes) }),
|
|
2008
|
+
...(opts.eof ? { eof: true } : {}),
|
|
2009
|
+
},
|
|
2010
|
+
});
|
|
2011
|
+
},
|
|
2012
|
+
signal: (sandboxId, execId, signal = "SIGTERM") => this.request({
|
|
2013
|
+
method: "POST",
|
|
2014
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}/signal`,
|
|
2015
|
+
body: { signal },
|
|
2016
|
+
}),
|
|
2017
|
+
forget: (sandboxId, execId) => this.request({
|
|
2018
|
+
method: "DELETE",
|
|
2019
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}`,
|
|
2020
|
+
}),
|
|
2134
2021
|
},
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2022
|
+
ports: {
|
|
2023
|
+
expose: (sandboxId, port) => {
|
|
2024
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2025
|
+
throw new InvalidArgumentError(`port must be an integer 1..65535, got ${port}`, "Pass the port your process listens on inside the sandbox, e.g. 3000.");
|
|
2026
|
+
}
|
|
2027
|
+
return withPreviewsFix(this.request({
|
|
2028
|
+
method: "POST", path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports`, body: { port },
|
|
2029
|
+
}), sandboxId, port);
|
|
2030
|
+
},
|
|
2031
|
+
list: async (sandboxId) => {
|
|
2032
|
+
const r = await withPreviewsFix(this.request({
|
|
2033
|
+
method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports`,
|
|
2034
|
+
}), sandboxId);
|
|
2035
|
+
return r.ports ?? [];
|
|
2036
|
+
},
|
|
2037
|
+
revoke: (sandboxId, tokenOrPort) => this.request({
|
|
2038
|
+
method: "DELETE",
|
|
2039
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/${encodeURIComponent(String(tokenOrPort))}`,
|
|
2040
|
+
}),
|
|
2143
2041
|
},
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
},
|
|
2163
|
-
probe: (sandboxId, opts = {}) => this.request({
|
|
2164
|
-
method: "POST",
|
|
2165
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/probe`,
|
|
2166
|
-
body: opts.paths?.length ? { paths: opts.paths } : {},
|
|
2167
|
-
}),
|
|
2168
|
-
kill: (sandboxId) => this.request({ method: "DELETE", path: `/sandboxes/${encodeURIComponent(sandboxId)}` }),
|
|
2169
|
-
network: (sandboxId) => this.request({ method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}/network` }),
|
|
2170
|
-
setNetworkNone: (sandboxId) => this.request({
|
|
2171
|
-
method: "POST",
|
|
2172
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/network`,
|
|
2173
|
-
body: { network: "none" },
|
|
2174
|
-
}),
|
|
2175
|
-
patch: (sandboxId, opts) => {
|
|
2176
|
-
const body = {};
|
|
2177
|
-
if (opts.timeoutMs !== undefined) {
|
|
2178
|
-
if (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs <= 0) {
|
|
2179
|
-
throw new InvalidArgumentError("sandboxes.patch needs a positive timeoutMs", "Pass { timeoutMs: 1_800_000 }.");
|
|
2180
|
-
}
|
|
2181
|
-
body["timeout_ms"] = opts.timeoutMs;
|
|
2182
|
-
}
|
|
2183
|
-
if (opts.autoPauseS !== undefined) {
|
|
2184
|
-
if (!Number.isInteger(opts.autoPauseS) || opts.autoPauseS < 0) {
|
|
2185
|
-
throw new InvalidArgumentError("sandboxes.patch needs a non-negative whole number of seconds for autoPauseS", "Pass { autoPauseS: 900 } to pause it after 15 idle minutes, or { autoPauseS: 0 } to turn that off.");
|
|
2186
|
-
}
|
|
2187
|
-
body["auto_pause_s"] = opts.autoPauseS;
|
|
2188
|
-
}
|
|
2189
|
-
if (opts.autoPauseMode !== undefined) {
|
|
2190
|
-
if (opts.autoPauseMode !== "requests" && opts.autoPauseMode !== "activity") {
|
|
2191
|
-
throw new InvalidArgumentError(`sandboxes.patch: autoPauseMode must be "requests" or "activity", got ${JSON.stringify(opts.autoPauseMode)}`, 'Pass { autoPauseMode: "activity" } for an agent that runs inside the sandbox.');
|
|
2192
|
-
}
|
|
2193
|
-
body["auto_pause_mode"] = opts.autoPauseMode;
|
|
2194
|
-
}
|
|
2195
|
-
if (Object.keys(body).length === 0) {
|
|
2196
|
-
throw new InvalidArgumentError("sandboxes.patch was given nothing to change", "Pass { timeoutMs } to extend it, { autoPauseS } to set its idle rule, or both.");
|
|
2197
|
-
}
|
|
2198
|
-
return this.request({
|
|
2199
|
-
method: "PATCH",
|
|
2200
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}`,
|
|
2201
|
-
body,
|
|
2202
|
-
});
|
|
2203
|
-
},
|
|
2204
|
-
files: {
|
|
2205
|
-
list: (sandboxId, path = "/", opts = {}) => {
|
|
2206
|
-
const q = [`path=${encodeURIComponent(path)}`];
|
|
2207
|
-
if (opts.recursive)
|
|
2208
|
-
q.push("recursive=1");
|
|
2209
|
-
if (opts.depth !== undefined)
|
|
2210
|
-
q.push(`depth=${Math.max(0, Math.floor(opts.depth))}`);
|
|
2211
|
-
if (opts.limit !== undefined)
|
|
2212
|
-
q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
|
|
2213
|
-
if (opts.hidden === false)
|
|
2214
|
-
q.push("hidden=0");
|
|
2215
|
-
return this.request({
|
|
2042
|
+
ssh: {
|
|
2043
|
+
open: (sandboxId, opts) => {
|
|
2044
|
+
if (!opts.publicKey?.trim()) {
|
|
2045
|
+
throw new InvalidArgumentError("publicKey is required", "Pass the contents of an ssh-ed25519 .pub file, or use `promigence sandbox ssh <id>`, which makes one.");
|
|
2046
|
+
}
|
|
2047
|
+
return this.request({
|
|
2048
|
+
method: "POST",
|
|
2049
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ssh`,
|
|
2050
|
+
body: {
|
|
2051
|
+
public_key: opts.publicKey.trim(),
|
|
2052
|
+
...(opts.ttlS === undefined ? {} : { ttl_s: Math.floor(opts.ttlS) }),
|
|
2053
|
+
...(opts.idleTimeoutS === undefined
|
|
2054
|
+
? {}
|
|
2055
|
+
: { idle_timeout_s: Math.floor(opts.idleTimeoutS) }),
|
|
2056
|
+
},
|
|
2057
|
+
});
|
|
2058
|
+
},
|
|
2059
|
+
list: (sandboxId) => this.request({
|
|
2216
2060
|
method: "GET",
|
|
2217
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/
|
|
2218
|
-
})
|
|
2061
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ssh`,
|
|
2062
|
+
}),
|
|
2063
|
+
revoke: (sandboxId, keyIdOrFingerprint) => this.request({
|
|
2064
|
+
method: "DELETE",
|
|
2065
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ssh/${encodeURIComponent(keyIdOrFingerprint)}`,
|
|
2066
|
+
}),
|
|
2067
|
+
connect: (sandboxId, opts) => connectSshTunnel({
|
|
2068
|
+
url: `${this.apiUrl}/sandboxes/${encodeURIComponent(sandboxId)}/ssh/connect`,
|
|
2069
|
+
token: opts.token ?? this.requireKey(),
|
|
2070
|
+
userAgent: this.userAgent,
|
|
2071
|
+
...(this.caller ? { caller: this.caller } : {}),
|
|
2072
|
+
onData: opts.onData,
|
|
2073
|
+
onClose: opts.onClose,
|
|
2074
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
2075
|
+
refuse: (status, text) => {
|
|
2076
|
+
let payload = {};
|
|
2077
|
+
try {
|
|
2078
|
+
payload = text ? JSON.parse(text) : {};
|
|
2079
|
+
}
|
|
2080
|
+
catch { }
|
|
2081
|
+
const code = payload.error?.code ?? (status === 0 ? "unreachable" : codeForStatus(status));
|
|
2082
|
+
return errorFor(code, payload.error?.message ?? `HTTP ${status} from the SSH tunnel`, payload.error?.fix ?? "See the API response for details.", { status, ...bodyExtra(payload) });
|
|
2083
|
+
},
|
|
2084
|
+
}),
|
|
2219
2085
|
},
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2086
|
+
pty: (sandboxId, opts = {}) => {
|
|
2087
|
+
const q = [];
|
|
2088
|
+
if (opts.cols !== undefined)
|
|
2089
|
+
q.push(`cols=${Math.floor(opts.cols)}`);
|
|
2090
|
+
if (opts.rows !== undefined)
|
|
2091
|
+
q.push(`rows=${Math.floor(opts.rows)}`);
|
|
2092
|
+
const path = `/sandboxes/${encodeURIComponent(sandboxId)}/pty${q.length ? `?${q.join("&")}` : ""}`;
|
|
2093
|
+
const open = () => connectPty({
|
|
2094
|
+
url: `${this.apiUrl}${path}`,
|
|
2095
|
+
apiKey: this.requireKey(),
|
|
2096
|
+
userAgent: this.userAgent,
|
|
2097
|
+
...(this.caller ? { caller: this.caller } : {}),
|
|
2098
|
+
...(opts.cmd ? { command: opts.cmd } : {}),
|
|
2099
|
+
...(opts.secrets?.length ? { secrets: opts.secrets } : {}),
|
|
2100
|
+
...(opts.onData ? { onData: opts.onData } : {}),
|
|
2101
|
+
...(opts.onExit ? { onExit: opts.onExit } : {}),
|
|
2102
|
+
...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
|
|
2103
|
+
...(opts.resume === undefined ? {} : { resume: opts.resume }),
|
|
2104
|
+
...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
|
|
2105
|
+
...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
|
|
2106
|
+
refuse: (status, text, retryAfter) => {
|
|
2107
|
+
let payload = {};
|
|
2108
|
+
try {
|
|
2109
|
+
payload = text ? JSON.parse(text) : {};
|
|
2110
|
+
}
|
|
2111
|
+
catch { }
|
|
2112
|
+
const code = payload.error?.code ?? (status === 0 ? "unreachable" : codeForStatus(status));
|
|
2113
|
+
const retryAfterMs = parseRetryAfter(retryAfter ?? null);
|
|
2114
|
+
return errorFor(code, payload.error?.message ?? `HTTP ${status} from GET ${path}`, payload.error?.fix ?? "See the API response for details.", {
|
|
2115
|
+
status,
|
|
2116
|
+
...bodyExtra(payload),
|
|
2117
|
+
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
2118
|
+
});
|
|
2119
|
+
},
|
|
2238
2120
|
});
|
|
2121
|
+
return (async () => {
|
|
2122
|
+
for (let attempt = 0;; attempt++) {
|
|
2123
|
+
try {
|
|
2124
|
+
return await open();
|
|
2125
|
+
}
|
|
2126
|
+
catch (err) {
|
|
2127
|
+
const e = err instanceof PromigenceError ? err : undefined;
|
|
2128
|
+
const wait = e ? this.refusalBackoff(e.status, e.retryAfterMs, e.code, attempt, false) : undefined;
|
|
2129
|
+
if (wait === undefined)
|
|
2130
|
+
throw err;
|
|
2131
|
+
this.stats.retries++;
|
|
2132
|
+
await sleep(wait);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
})();
|
|
2239
2136
|
},
|
|
2240
|
-
|
|
2137
|
+
get: (sandboxId) => {
|
|
2138
|
+
checkSandboxId(sandboxId, "sandboxes.get");
|
|
2139
|
+
return this.request({ method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}` });
|
|
2140
|
+
},
|
|
2141
|
+
pause: (sandboxId) => this.request({
|
|
2241
2142
|
method: "POST",
|
|
2242
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/
|
|
2243
|
-
body: {
|
|
2244
|
-
|
|
2245
|
-
...(opts.mode === undefined ? {} : { mode: modeParam(opts.mode) }),
|
|
2246
|
-
...(opts.parents === undefined ? {} : { parents: opts.parents }),
|
|
2247
|
-
},
|
|
2143
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/pause`,
|
|
2144
|
+
body: {},
|
|
2145
|
+
timeoutMs: 10 * 60_000,
|
|
2248
2146
|
}),
|
|
2249
|
-
|
|
2147
|
+
resume: (sandboxId, opts = {}) => this.request({
|
|
2250
2148
|
method: "POST",
|
|
2251
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/
|
|
2252
|
-
body: {
|
|
2149
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/resume`,
|
|
2150
|
+
body: {
|
|
2151
|
+
...(opts.optimizeFor === undefined ? {} : { optimize_for: opts.optimizeFor }),
|
|
2152
|
+
...(opts.spendCap === undefined ? {} : { spend_cap: checkSpendCap(opts.spendCap) }),
|
|
2153
|
+
},
|
|
2253
2154
|
}),
|
|
2254
|
-
|
|
2155
|
+
snapshot: (sandboxId, opts = {}) => this.request({
|
|
2255
2156
|
method: "POST",
|
|
2256
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/
|
|
2157
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/snapshot`,
|
|
2257
2158
|
body: {
|
|
2258
|
-
|
|
2259
|
-
...(opts.
|
|
2260
|
-
...(opts.
|
|
2159
|
+
...(opts.alias === undefined ? {} : { alias: opts.alias }),
|
|
2160
|
+
...(opts.force === undefined ? {} : { force: opts.force }),
|
|
2161
|
+
...(opts.moveAlias === undefined ? {} : { move_alias: opts.moveAlias }),
|
|
2261
2162
|
},
|
|
2163
|
+
timeoutMs: 10 * 60_000,
|
|
2164
|
+
}),
|
|
2165
|
+
list: async (opts = 50) => (await this.sandboxes.listPage(opts)).items,
|
|
2166
|
+
listPage: async (opts = 50) => {
|
|
2167
|
+
const o = typeof opts === "number" ? { limit: opts } : opts;
|
|
2168
|
+
const q = new URLSearchParams({ limit: String(Math.max(1, Math.trunc(o.limit ?? 50))) });
|
|
2169
|
+
const pairs = Object.entries(o.labels ?? {});
|
|
2170
|
+
if (pairs.length)
|
|
2171
|
+
q.set("labels", pairs.map(([k, v]) => `${k}=${v}`).join(","));
|
|
2172
|
+
if (o.state?.length)
|
|
2173
|
+
q.set("state", o.state.join(","));
|
|
2174
|
+
if (o.cursor)
|
|
2175
|
+
q.set("cursor", o.cursor);
|
|
2176
|
+
const r = await this.request({
|
|
2177
|
+
method: "GET",
|
|
2178
|
+
path: `/sandboxes?${q.toString()}`,
|
|
2179
|
+
});
|
|
2180
|
+
return { items: r.sandboxes ?? [], nextCursor: r.next_cursor ?? null, limit: r.limit ?? (o.limit ?? 50) };
|
|
2181
|
+
},
|
|
2182
|
+
probe: (sandboxId, opts = {}) => this.request({
|
|
2183
|
+
method: "POST",
|
|
2184
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/probe`,
|
|
2185
|
+
body: opts.paths?.length ? { paths: opts.paths } : {},
|
|
2186
|
+
}),
|
|
2187
|
+
kill: (sandboxId) => this.request({ method: "DELETE", path: `/sandboxes/${encodeURIComponent(sandboxId)}` }),
|
|
2188
|
+
network: (sandboxId) => this.request({ method: "GET", path: `/sandboxes/${encodeURIComponent(sandboxId)}/network` }),
|
|
2189
|
+
setNetworkNone: (sandboxId) => this.request({
|
|
2190
|
+
method: "POST",
|
|
2191
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/network`,
|
|
2192
|
+
body: { network: "none" },
|
|
2262
2193
|
}),
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2194
|
+
patch: (sandboxId, opts) => {
|
|
2195
|
+
const body = {};
|
|
2196
|
+
if (opts.timeoutMs !== undefined) {
|
|
2197
|
+
if (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs <= 0) {
|
|
2198
|
+
throw new InvalidArgumentError("sandboxes.patch needs a positive timeoutMs", "Pass { timeoutMs: 1_800_000 }.");
|
|
2199
|
+
}
|
|
2200
|
+
body["timeout_ms"] = opts.timeoutMs;
|
|
2201
|
+
}
|
|
2202
|
+
if (opts.autoPauseS !== undefined) {
|
|
2203
|
+
if (!Number.isInteger(opts.autoPauseS) || opts.autoPauseS < 0) {
|
|
2204
|
+
throw new InvalidArgumentError("sandboxes.patch needs a non-negative whole number of seconds for autoPauseS", "Pass { autoPauseS: 900 } to pause it after 15 idle minutes, or { autoPauseS: 0 } to turn that off.");
|
|
2205
|
+
}
|
|
2206
|
+
body["auto_pause_s"] = opts.autoPauseS;
|
|
2207
|
+
}
|
|
2208
|
+
if (opts.autoPauseMode !== undefined) {
|
|
2209
|
+
if (opts.autoPauseMode !== "requests" && opts.autoPauseMode !== "activity") {
|
|
2210
|
+
throw new InvalidArgumentError(`sandboxes.patch: autoPauseMode must be "requests" or "activity", got ${JSON.stringify(opts.autoPauseMode)}`, 'Pass { autoPauseMode: "activity" } for an agent that runs inside the sandbox.');
|
|
2211
|
+
}
|
|
2212
|
+
body["auto_pause_mode"] = opts.autoPauseMode;
|
|
2213
|
+
}
|
|
2214
|
+
if (Object.keys(body).length === 0) {
|
|
2215
|
+
throw new InvalidArgumentError("sandboxes.patch was given nothing to change", "Pass { timeoutMs } to extend it, { autoPauseS } to set its idle rule, or both.");
|
|
2266
2216
|
}
|
|
2267
2217
|
return this.request({
|
|
2218
|
+
method: "PATCH",
|
|
2219
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}`,
|
|
2220
|
+
body,
|
|
2221
|
+
});
|
|
2222
|
+
},
|
|
2223
|
+
files: {
|
|
2224
|
+
list: (sandboxId, path = "/", opts = {}) => {
|
|
2225
|
+
const q = [`path=${encodeURIComponent(path)}`];
|
|
2226
|
+
if (opts.recursive)
|
|
2227
|
+
q.push("recursive=1");
|
|
2228
|
+
if (opts.depth !== undefined)
|
|
2229
|
+
q.push(`depth=${Math.max(0, Math.floor(opts.depth))}`);
|
|
2230
|
+
if (opts.limit !== undefined)
|
|
2231
|
+
q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
|
|
2232
|
+
if (opts.hidden === false)
|
|
2233
|
+
q.push("hidden=0");
|
|
2234
|
+
return this.request({
|
|
2235
|
+
method: "GET",
|
|
2236
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list?${q.join("&")}`,
|
|
2237
|
+
});
|
|
2238
|
+
},
|
|
2239
|
+
search: (sandboxId, path, opts = {}) => {
|
|
2240
|
+
if (!opts.pattern && !opts.contains) {
|
|
2241
|
+
throw new InvalidArgumentError("files.search needs `pattern`, `contains`, or both", "Pass { pattern: '*.py' } for a name glob, { contains: 'TODO' } for text inside files.");
|
|
2242
|
+
}
|
|
2243
|
+
const q = [`path=${encodeURIComponent(path)}`];
|
|
2244
|
+
if (opts.pattern)
|
|
2245
|
+
q.push(`pattern=${encodeURIComponent(opts.pattern)}`);
|
|
2246
|
+
if (opts.contains)
|
|
2247
|
+
q.push(`contains=${encodeURIComponent(opts.contains)}`);
|
|
2248
|
+
if (opts.ignoreCase)
|
|
2249
|
+
q.push("ignore_case=1");
|
|
2250
|
+
if (opts.limit !== undefined)
|
|
2251
|
+
q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
|
|
2252
|
+
if (opts.hidden === false)
|
|
2253
|
+
q.push("hidden=0");
|
|
2254
|
+
return this.request({
|
|
2255
|
+
method: "GET",
|
|
2256
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search?${q.join("&")}`,
|
|
2257
|
+
});
|
|
2258
|
+
},
|
|
2259
|
+
mkdir: (sandboxId, path, opts = {}) => this.request({
|
|
2268
2260
|
method: "POST",
|
|
2269
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/
|
|
2261
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/mkdir`,
|
|
2270
2262
|
body: {
|
|
2271
2263
|
path,
|
|
2272
2264
|
...(opts.mode === undefined ? {} : { mode: modeParam(opts.mode) }),
|
|
2273
|
-
...(opts.
|
|
2274
|
-
|
|
2265
|
+
...(opts.parents === undefined ? {} : { parents: opts.parents }),
|
|
2266
|
+
},
|
|
2267
|
+
}),
|
|
2268
|
+
move: (sandboxId, from, to, opts = {}) => this.request({
|
|
2269
|
+
method: "POST",
|
|
2270
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/move`,
|
|
2271
|
+
body: { from, to, ...(opts.overwrite === undefined ? {} : { overwrite: opts.overwrite }) },
|
|
2272
|
+
}),
|
|
2273
|
+
copy: (sandboxId, from, to, opts = {}) => this.request({
|
|
2274
|
+
method: "POST",
|
|
2275
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/copy`,
|
|
2276
|
+
body: {
|
|
2277
|
+
from, to,
|
|
2275
2278
|
...(opts.recursive === undefined ? {} : { recursive: opts.recursive }),
|
|
2279
|
+
...(opts.overwrite === undefined ? {} : { overwrite: opts.overwrite }),
|
|
2276
2280
|
},
|
|
2277
|
-
})
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/archive`,
|
|
2282
|
-
body: {
|
|
2283
|
-
path,
|
|
2284
|
-
...(opts.out === undefined ? {} : { out: opts.out }),
|
|
2285
|
-
...(opts.exclude?.length ? { exclude: opts.exclude } : {}),
|
|
2286
|
-
},
|
|
2287
|
-
}),
|
|
2288
|
-
extract: (sandboxId, archive, into, opts = {}) => this.request({
|
|
2289
|
-
method: "POST",
|
|
2290
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/extract`,
|
|
2291
|
-
body: { archive, into, ...(opts.strip === undefined ? {} : { strip: Math.max(0, Math.floor(opts.strip)) }) },
|
|
2292
|
-
}),
|
|
2293
|
-
stat: (sandboxId, path) => this.request({
|
|
2294
|
-
method: "GET",
|
|
2295
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat?path=${encodeURIComponent(path)}`,
|
|
2296
|
-
}),
|
|
2297
|
-
get: (sandboxId, path, opts = {}) => this.bytes("GET", `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, undefined, opts),
|
|
2298
|
-
download: (sandboxId, path, onChunk, opts = {}) => this.stream(`/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, onChunk, opts),
|
|
2299
|
-
text: async (sandboxId, path, opts = {}) => new TextDecoder().decode(await this.sandboxes.files.get(sandboxId, path, opts)),
|
|
2300
|
-
put: async (sandboxId, path, data, opts = {}) => {
|
|
2301
|
-
const body = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2302
|
-
return this.sandboxes.files.putFrom(sandboxId, path, {
|
|
2303
|
-
size: body.length,
|
|
2304
|
-
read: (offset, len) => body.subarray(offset, offset + len),
|
|
2305
|
-
}, opts);
|
|
2306
|
-
},
|
|
2307
|
-
putFrom: async (sandboxId, path, source, opts = {}) => {
|
|
2308
|
-
const mode = opts.mode === undefined ? "" : `&mode=${opts.mode.toString(8)}`;
|
|
2309
|
-
const base = `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}${mode}`;
|
|
2310
|
-
let sent = 0;
|
|
2311
|
-
let chunks = 0;
|
|
2312
|
-
let last;
|
|
2313
|
-
do {
|
|
2314
|
-
const slice = await source.read(sent, Math.min(FILE_PUT_CHUNK_BYTES, source.size - sent));
|
|
2315
|
-
if (slice.length === 0 && sent < source.size) {
|
|
2316
|
-
throw new InvalidArgumentError(`the source of ${path} ended at ${sent} of ${source.size} bytes`, "The file shrank while it was being sent; send it again once nothing is writing it.");
|
|
2281
|
+
}),
|
|
2282
|
+
chmod: (sandboxId, path, opts) => {
|
|
2283
|
+
if (opts.mode === undefined && opts.uid === undefined && opts.gid === undefined) {
|
|
2284
|
+
throw new InvalidArgumentError("files.chmod needs one of mode, uid or gid", "Pass { mode: 0o755 }, { uid: 1000 }, { gid: 1000 }, or any combination.");
|
|
2317
2285
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2286
|
+
return this.request({
|
|
2287
|
+
method: "POST",
|
|
2288
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/chmod`,
|
|
2289
|
+
body: {
|
|
2290
|
+
path,
|
|
2291
|
+
...(opts.mode === undefined ? {} : { mode: modeParam(opts.mode) }),
|
|
2292
|
+
...(opts.uid === undefined ? {} : { uid: opts.uid }),
|
|
2293
|
+
...(opts.gid === undefined ? {} : { gid: opts.gid }),
|
|
2294
|
+
...(opts.recursive === undefined ? {} : { recursive: opts.recursive }),
|
|
2295
|
+
},
|
|
2296
|
+
});
|
|
2297
|
+
},
|
|
2298
|
+
archive: (sandboxId, path, opts = {}) => this.request({
|
|
2299
|
+
method: "POST",
|
|
2300
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/archive`,
|
|
2301
|
+
body: {
|
|
2302
|
+
path,
|
|
2303
|
+
...(opts.out === undefined ? {} : { out: opts.out }),
|
|
2304
|
+
...(opts.exclude?.length ? { exclude: opts.exclude } : {}),
|
|
2305
|
+
},
|
|
2306
|
+
}),
|
|
2307
|
+
extract: (sandboxId, archive, into, opts = {}) => this.request({
|
|
2308
|
+
method: "POST",
|
|
2309
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/extract`,
|
|
2310
|
+
body: { archive, into, ...(opts.strip === undefined ? {} : { strip: Math.max(0, Math.floor(opts.strip)) }) },
|
|
2311
|
+
}),
|
|
2312
|
+
stat: (sandboxId, path) => this.request({
|
|
2313
|
+
method: "GET",
|
|
2314
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat?path=${encodeURIComponent(path)}`,
|
|
2315
|
+
}),
|
|
2316
|
+
get: (sandboxId, path, opts = {}) => this.bytes("GET", `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, undefined, opts),
|
|
2317
|
+
download: (sandboxId, path, onChunk, opts = {}) => this.stream(`/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, onChunk, opts),
|
|
2318
|
+
text: async (sandboxId, path, opts = {}) => new TextDecoder().decode(await this.sandboxes.files.get(sandboxId, path, opts)),
|
|
2319
|
+
put: async (sandboxId, path, data, opts = {}) => {
|
|
2320
|
+
const body = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
2321
|
+
return this.sandboxes.files.putFrom(sandboxId, path, {
|
|
2322
|
+
size: body.length,
|
|
2323
|
+
read: (offset, len) => body.subarray(offset, offset + len),
|
|
2324
|
+
}, opts);
|
|
2325
|
+
},
|
|
2326
|
+
putFrom: async (sandboxId, path, source, opts = {}) => {
|
|
2327
|
+
const mode = opts.mode === undefined ? "" : `&mode=${opts.mode.toString(8)}`;
|
|
2328
|
+
const base = `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}${mode}`;
|
|
2329
|
+
let sent = 0;
|
|
2330
|
+
let chunks = 0;
|
|
2331
|
+
let last;
|
|
2332
|
+
do {
|
|
2333
|
+
const slice = await source.read(sent, Math.min(FILE_PUT_CHUNK_BYTES, source.size - sent));
|
|
2334
|
+
if (slice.length === 0 && sent < source.size) {
|
|
2335
|
+
throw new InvalidArgumentError(`the source of ${path} ended at ${sent} of ${source.size} bytes`, "The file shrank while it was being sent; send it again once nothing is writing it.");
|
|
2336
|
+
}
|
|
2337
|
+
const raw = await this.bytes("PUT", sent === 0 ? base : `${base}&offset=${sent}`, slice, opts);
|
|
2338
|
+
last = JSON.parse(new TextDecoder().decode(raw));
|
|
2339
|
+
sent += slice.length;
|
|
2340
|
+
chunks += 1;
|
|
2341
|
+
} while (sent < source.size);
|
|
2342
|
+
return { ...last, bytes: source.size, chunks };
|
|
2343
|
+
},
|
|
2344
|
+
delete: (sandboxId, path, opts = {}) => this.request({
|
|
2345
|
+
method: "DELETE",
|
|
2346
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`
|
|
2347
|
+
+ (opts.recursive ? "&recursive=1" : ""),
|
|
2348
|
+
}),
|
|
2324
2349
|
},
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
runId ??= rec.run_id;
|
|
2342
|
-
return hit;
|
|
2343
|
-
};
|
|
2344
|
-
let found;
|
|
2345
|
-
let sandboxId = opts.sandboxId;
|
|
2346
|
-
let routeMissing = false;
|
|
2347
|
-
try {
|
|
2348
|
-
found = await this.request({ method: "GET", path: `/execs/${encodeURIComponent(execId)}` });
|
|
2349
|
-
}
|
|
2350
|
-
catch (err) {
|
|
2351
|
-
if (!(err instanceof NotFoundError))
|
|
2352
|
-
throw err;
|
|
2353
|
-
found = undefined;
|
|
2354
|
-
routeMissing = true;
|
|
2355
|
-
}
|
|
2356
|
-
if (found && opts.runId && found.run_id && found.run_id !== opts.runId)
|
|
2357
|
-
found = undefined;
|
|
2358
|
-
let otherSandbox = false;
|
|
2359
|
-
if (found && opts.sandboxId && found.sandbox_id && found.sandbox_id !== opts.sandboxId) {
|
|
2360
|
-
found = undefined;
|
|
2361
|
-
otherSandbox = true;
|
|
2362
|
-
}
|
|
2363
|
-
if (found)
|
|
2364
|
-
runId ??= found.run_id;
|
|
2365
|
-
if (!found && (routeMissing || otherSandbox) && opts.sandboxId) {
|
|
2350
|
+
output: async (execId, opts = {}) => {
|
|
2351
|
+
if (!execId)
|
|
2352
|
+
throw new InvalidArgumentError("exec output needs an exec_id", "Pass the `exec_id` the exec envelope printed.");
|
|
2353
|
+
let runId = opts.runId;
|
|
2354
|
+
const fromSandbox = async (sid) => {
|
|
2355
|
+
const rec = await this.request({
|
|
2356
|
+
method: "GET", path: `/sandboxes/${encodeURIComponent(sid)}`,
|
|
2357
|
+
});
|
|
2358
|
+
const hit = (rec?.execs ?? []).find((e) => e.exec_id === execId);
|
|
2359
|
+
if (hit && rec?.run_id)
|
|
2360
|
+
runId ??= rec.run_id;
|
|
2361
|
+
return hit;
|
|
2362
|
+
};
|
|
2363
|
+
let found;
|
|
2364
|
+
let sandboxId = opts.sandboxId;
|
|
2365
|
+
let routeMissing = false;
|
|
2366
2366
|
try {
|
|
2367
|
-
found = await
|
|
2367
|
+
found = await this.request({ method: "GET", path: `/execs/${encodeURIComponent(execId)}` });
|
|
2368
2368
|
}
|
|
2369
2369
|
catch (err) {
|
|
2370
2370
|
if (!(err instanceof NotFoundError))
|
|
2371
2371
|
throw err;
|
|
2372
2372
|
found = undefined;
|
|
2373
|
+
routeMissing = true;
|
|
2373
2374
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2375
|
+
if (found && opts.runId && found.run_id && found.run_id !== opts.runId)
|
|
2376
|
+
found = undefined;
|
|
2377
|
+
let otherSandbox = false;
|
|
2378
|
+
if (found && opts.sandboxId && found.sandbox_id && found.sandbox_id !== opts.sandboxId) {
|
|
2379
|
+
found = undefined;
|
|
2380
|
+
otherSandbox = true;
|
|
2381
|
+
}
|
|
2382
|
+
if (found)
|
|
2383
|
+
runId ??= found.run_id;
|
|
2384
|
+
if (!found && (routeMissing || otherSandbox) && opts.sandboxId) {
|
|
2385
|
+
try {
|
|
2386
|
+
found = await fromSandbox(opts.sandboxId);
|
|
2387
|
+
}
|
|
2388
|
+
catch (err) {
|
|
2389
|
+
if (!(err instanceof NotFoundError))
|
|
2390
|
+
throw err;
|
|
2391
|
+
found = undefined;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
sandboxId ??= found?.sandbox_id;
|
|
2395
|
+
if (!found) {
|
|
2396
|
+
throw new NotFoundError(`exec ${execId} not found${sandboxId ? ` in sandbox ${sandboxId}` : opts.runId ? ` in run ${opts.runId}` : ""}`, "Check the `exec_id` from the exec envelope, and that the sandbox it ran in still exists (`promigence run report <run_id>`).");
|
|
2397
|
+
}
|
|
2398
|
+
return sliceOutput(found, sandboxId, runId, opts.range);
|
|
2399
|
+
},
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2382
2402
|
sandbox(id) {
|
|
2383
2403
|
return {
|
|
2384
2404
|
id,
|
|
@@ -2398,142 +2418,174 @@ export class PromigenceClient {
|
|
|
2398
2418
|
probe: (opts = {}) => this.sandboxes.probe(id, opts),
|
|
2399
2419
|
};
|
|
2400
2420
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
path: `/snapshots/builds/${encodeURIComponent(buildId)}/build-log?tail=${Math.max(1, Math.floor(tail))}`,
|
|
2460
|
-
});
|
|
2461
|
-
return r?.log ?? [];
|
|
2462
|
-
},
|
|
2463
|
-
builds: async (limit = 20) => (await this.snapshots.buildsPage({ limit })).items,
|
|
2464
|
-
buildsPage: async (opts = {}) => {
|
|
2465
|
-
const limit = opts.limit ?? 20;
|
|
2466
|
-
const r = await this.request({ method: "GET", path: `/snapshots/builds?${pageQuery(limit, 500, opts.cursor)}` });
|
|
2467
|
-
return pageOf(r, "builds", limit);
|
|
2468
|
-
},
|
|
2469
|
-
get: (idOrAlias) => this.request({ method: "GET", path: `/snapshots/${seg(idOrAlias)}` }),
|
|
2470
|
-
list: async (opts = {}) => (await this.snapshots.listPage(opts)).items,
|
|
2471
|
-
listPage: async (opts = {}) => {
|
|
2472
|
-
const q = new URLSearchParams();
|
|
2473
|
-
if (opts.limit !== undefined)
|
|
2474
|
-
q.set("limit", String(Math.max(1, Math.trunc(opts.limit))));
|
|
2475
|
-
if (opts.cursor)
|
|
2476
|
-
q.set("cursor", opts.cursor);
|
|
2477
|
-
const qs = q.toString();
|
|
2478
|
-
const r = await this.request({
|
|
2479
|
-
method: "GET", path: `/snapshots${qs ? `?${qs}` : ""}`, ...(this.authenticated ? {} : { anonymous: true }),
|
|
2480
|
-
});
|
|
2481
|
-
if (Array.isArray(r))
|
|
2482
|
-
return { items: r, nextCursor: null, limit: opts.limit ?? r.length };
|
|
2483
|
-
return { items: r?.snapshots ?? [], nextCursor: r?.next_cursor ?? null, limit: r?.limit ?? (opts.limit ?? 0) };
|
|
2484
|
-
},
|
|
2485
|
-
public: async (limit = 20) => {
|
|
2486
|
-
const r = await this.request({
|
|
2421
|
+
group_secrets() {
|
|
2422
|
+
return {
|
|
2423
|
+
set: (name, value, opts = {}) => {
|
|
2424
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name ?? "")) {
|
|
2425
|
+
throw new InvalidArgumentError(`secrets.set: ${JSON.stringify(name ?? null)} is not usable as an environment variable`, "Use letters, digits and underscores, not starting with a digit: OPENAI_API_KEY.");
|
|
2426
|
+
}
|
|
2427
|
+
if (!value) {
|
|
2428
|
+
throw new InvalidArgumentError(`secrets.set: ${name} has an empty value`, `To remove it, call secrets.delete(${JSON.stringify(name)}).`);
|
|
2429
|
+
}
|
|
2430
|
+
const body = { name, value };
|
|
2431
|
+
if (opts.hosts !== undefined)
|
|
2432
|
+
body.hosts = opts.hosts;
|
|
2433
|
+
if (opts.headers !== undefined)
|
|
2434
|
+
body.headers = opts.headers;
|
|
2435
|
+
if (opts.raw !== undefined)
|
|
2436
|
+
body.raw = opts.raw;
|
|
2437
|
+
if (opts.scanResponses !== undefined)
|
|
2438
|
+
body.scan_responses = opts.scanResponses;
|
|
2439
|
+
return this.request({ method: "POST", path: "/secrets", body });
|
|
2440
|
+
},
|
|
2441
|
+
list: () => this.request({ method: "GET", path: "/secrets" }),
|
|
2442
|
+
delete: (name) => this.request({
|
|
2443
|
+
method: "DELETE",
|
|
2444
|
+
path: `/secrets/${encodeURIComponent(name)}`,
|
|
2445
|
+
}),
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
group_images() {
|
|
2449
|
+
return {
|
|
2450
|
+
resolve: (image, opts = {}) => {
|
|
2451
|
+
if (!image?.trim()) {
|
|
2452
|
+
throw new InvalidArgumentError("images.resolve needs an image reference", "Pass a name like 'python:3.12' or 'ghcr.io/org/app:v1'.");
|
|
2453
|
+
}
|
|
2454
|
+
const body = { image: image.trim() };
|
|
2455
|
+
if (opts.registrySecret)
|
|
2456
|
+
body["registry_secret"] = opts.registrySecret;
|
|
2457
|
+
return this.request({ method: "POST", path: "/images/resolve", body });
|
|
2458
|
+
},
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
group_captures() {
|
|
2462
|
+
return {
|
|
2463
|
+
delete: (snapshotId) => this.request({
|
|
2464
|
+
method: "DELETE",
|
|
2465
|
+
path: `/captures/${encodeURIComponent(snapshotId)}`,
|
|
2466
|
+
}),
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
group_snapshots() {
|
|
2470
|
+
return {
|
|
2471
|
+
create: (opts) => {
|
|
2472
|
+
const body = snapshotCreateBody(opts);
|
|
2473
|
+
return this.request({
|
|
2474
|
+
method: "POST", path: "/snapshots", body,
|
|
2475
|
+
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
2476
|
+
});
|
|
2477
|
+
},
|
|
2478
|
+
status: (buildId, opts = {}) => this.request({
|
|
2487
2479
|
method: "GET",
|
|
2488
|
-
path: `/snapshots
|
|
2489
|
-
|
|
2490
|
-
})
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2480
|
+
path: `/snapshots/builds/${encodeURIComponent(buildId)}${waitQuery(opts.waitMs)}`,
|
|
2481
|
+
...this.readExtras(opts),
|
|
2482
|
+
}),
|
|
2483
|
+
buildLog: async (buildId, tail = 200) => {
|
|
2484
|
+
const r = await this.request({
|
|
2485
|
+
method: "GET",
|
|
2486
|
+
path: `/snapshots/builds/${encodeURIComponent(buildId)}/build-log?tail=${Math.max(1, Math.floor(tail))}`,
|
|
2487
|
+
});
|
|
2488
|
+
return r?.log ?? [];
|
|
2489
|
+
},
|
|
2490
|
+
builds: async (limit = 20) => (await this.snapshots.buildsPage({ limit })).items,
|
|
2491
|
+
buildsPage: async (opts = {}) => {
|
|
2492
|
+
const limit = opts.limit ?? 20;
|
|
2493
|
+
const r = await this.request({ method: "GET", path: `/snapshots/builds?${pageQuery(limit, 500, opts.cursor)}` });
|
|
2494
|
+
return pageOf(r, "builds", limit);
|
|
2495
|
+
},
|
|
2496
|
+
get: (idOrAlias) => this.request({ method: "GET", path: `/snapshots/${seg(idOrAlias)}` }),
|
|
2497
|
+
list: async (opts = {}) => (await this.snapshots.listPage(opts)).items,
|
|
2498
|
+
listPage: async (opts = {}) => {
|
|
2499
|
+
const q = new URLSearchParams();
|
|
2500
|
+
if (opts.limit !== undefined)
|
|
2501
|
+
q.set("limit", String(Math.max(1, Math.trunc(opts.limit))));
|
|
2502
|
+
if (opts.cursor)
|
|
2503
|
+
q.set("cursor", opts.cursor);
|
|
2504
|
+
const qs = q.toString();
|
|
2505
|
+
const r = await this.request({
|
|
2506
|
+
method: "GET", path: `/snapshots${qs ? `?${qs}` : ""}`, ...(this.authenticated ? {} : { anonymous: true }),
|
|
2507
|
+
});
|
|
2508
|
+
if (Array.isArray(r))
|
|
2509
|
+
return { items: r, nextCursor: null, limit: opts.limit ?? r.length };
|
|
2510
|
+
return { items: r?.snapshots ?? [], nextCursor: r?.next_cursor ?? null, limit: r?.limit ?? (opts.limit ?? 0) };
|
|
2511
|
+
},
|
|
2512
|
+
public: async (limit = 20) => {
|
|
2513
|
+
const r = await this.request({
|
|
2514
|
+
method: "GET",
|
|
2515
|
+
path: `/snapshots?limit=${Math.max(1, Math.floor(limit))}`,
|
|
2516
|
+
anonymous: true,
|
|
2517
|
+
});
|
|
2518
|
+
const all = Array.isArray(r) ? r : (r?.snapshots ?? []);
|
|
2519
|
+
return all.filter((s) => s.visibility === undefined || s.visibility === "public").slice(0, limit);
|
|
2520
|
+
},
|
|
2521
|
+
delete: (idOrAlias) => {
|
|
2522
|
+
if (!idOrAlias)
|
|
2523
|
+
throw new InvalidArgumentError("snapshots.delete needs a snapshot", "Pass a snap_ id, an alias, or alias@commit7.");
|
|
2524
|
+
return this.request({ method: "DELETE", path: `/snapshots/${seg(idOrAlias)}` });
|
|
2525
|
+
},
|
|
2526
|
+
alias: (idOrAlias, alias) => this.request({
|
|
2527
|
+
method: "PUT",
|
|
2528
|
+
path: `/snapshots/${seg(idOrAlias)}/alias`,
|
|
2529
|
+
body: { alias },
|
|
2530
|
+
}),
|
|
2531
|
+
waitBuild: async (buildId, opts = {}) => {
|
|
2532
|
+
const r = await this.waitLoop(opts, BUILD_POLL_MS, { build_id: buildId, state: "running" }, (read) => this.snapshots.status(buildId, read), (b) => b.state !== "running" && b.state !== "queued");
|
|
2533
|
+
return { build: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
|
|
2534
|
+
},
|
|
2535
|
+
verify: (id, opts = {}) => {
|
|
2536
|
+
if (!id)
|
|
2537
|
+
throw new InvalidArgumentError("snapshots.verify needs a snapshot", "Pass a snap_ id, an alias, or alias@commit7.");
|
|
2538
|
+
const body = {};
|
|
2539
|
+
if (opts.timeoutMs !== undefined)
|
|
2540
|
+
body["timeout_ms"] = opts.timeoutMs;
|
|
2541
|
+
if (opts.spendCap !== undefined)
|
|
2542
|
+
body["spend_cap"] = opts.spendCap;
|
|
2543
|
+
return this.request({
|
|
2544
|
+
method: "POST",
|
|
2545
|
+
path: `/snapshots/${encodeURIComponent(id)}/verify`,
|
|
2546
|
+
body,
|
|
2547
|
+
});
|
|
2548
|
+
},
|
|
2549
|
+
manifest: (idOrAlias) => this.request({ method: "GET", path: `/snapshots/${seg(idOrAlias)}/manifest` }),
|
|
2550
|
+
bundle: async (idOrAlias, opts = {}) => {
|
|
2551
|
+
const [record, m] = await Promise.all([
|
|
2552
|
+
this.snapshots.get(idOrAlias).catch(() => undefined),
|
|
2553
|
+
this.snapshots.manifest(idOrAlias),
|
|
2554
|
+
]);
|
|
2555
|
+
if (!m?.manifest || typeof m.manifest !== "object") {
|
|
2556
|
+
throw new NotFoundError(`snapshot ${idOrAlias} has no manifest to export`, "Only a verified snapshot has one: check 'promigence snapshot inspect' first.");
|
|
2557
|
+
}
|
|
2558
|
+
const alias = opts.alias ?? record?.alias ?? undefined;
|
|
2559
|
+
const bundle = buildBundle(m.manifest, { ...(alias ? { alias } : {}) });
|
|
2560
|
+
return { bundle, served_by: "client", ...(record ? { record } : {}) };
|
|
2561
|
+
},
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
const API_GROUPS = ["templates", "billing", "keys", "orgs", "credits", "github", "gitlab", "runs", "sandboxes", "secrets", "images", "captures", "snapshots"];
|
|
2566
|
+
function installLazyGroups(proto) {
|
|
2567
|
+
for (const name of API_GROUPS) {
|
|
2568
|
+
const build = `group_${name}`;
|
|
2569
|
+
const own = (self, value) => {
|
|
2570
|
+
Object.defineProperty(self, name, { value, writable: true, enumerable: true, configurable: true });
|
|
2571
|
+
};
|
|
2572
|
+
Object.defineProperty(proto, name, {
|
|
2573
|
+
configurable: true,
|
|
2574
|
+
enumerable: false,
|
|
2575
|
+
get() {
|
|
2576
|
+
const value = this[build].call(this);
|
|
2577
|
+
own(this, value);
|
|
2578
|
+
return value;
|
|
2579
|
+
},
|
|
2580
|
+
set(value) {
|
|
2581
|
+
own(this, value);
|
|
2582
|
+
},
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2536
2585
|
}
|
|
2586
|
+
installLazyGroups(PromigenceClient.prototype);
|
|
2587
|
+
const lazyGroupTypesMatch = [true, true, true, true, true, true, true, true, true, true, true, true, true];
|
|
2588
|
+
void lazyGroupTypesMatch;
|
|
2537
2589
|
const POLL_LADDER_MS = [10, 25, 25, 25, 25, 25, 25, 25, 25, 50, 50, 50, 100, 100, 100, 200];
|
|
2538
2590
|
function pollInterval(iteration) {
|
|
2539
2591
|
const step = POLL_LADDER_MS[iteration];
|