@promigence/sdk 0.1.0-beta.12 → 0.1.0-beta.14

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