@promigence/sdk 0.1.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +54 -0
  3. package/dist/blake3.d.ts +10 -0
  4. package/dist/blake3.js +111 -0
  5. package/dist/bundle.d.ts +96 -0
  6. package/dist/bundle.js +227 -0
  7. package/dist/ca-guard.d.ts +1 -0
  8. package/dist/ca-guard.js +166 -0
  9. package/dist/caller.d.ts +73 -0
  10. package/dist/caller.js +47 -0
  11. package/dist/cjs/blake3.d.ts +10 -0
  12. package/dist/cjs/blake3.js +116 -0
  13. package/dist/cjs/bundle.d.ts +96 -0
  14. package/dist/cjs/bundle.js +242 -0
  15. package/dist/cjs/ca-guard.d.ts +1 -0
  16. package/dist/cjs/ca-guard.js +174 -0
  17. package/dist/cjs/caller.d.ts +73 -0
  18. package/dist/cjs/caller.js +57 -0
  19. package/dist/cjs/client.d.ts +3015 -0
  20. package/dist/cjs/client.js +2617 -0
  21. package/dist/cjs/command-errors.d.ts +41 -0
  22. package/dist/cjs/command-errors.js +43 -0
  23. package/dist/cjs/context.d.ts +119 -0
  24. package/dist/cjs/context.js +598 -0
  25. package/dist/cjs/core.d.ts +285 -0
  26. package/dist/cjs/core.js +290 -0
  27. package/dist/cjs/credstore.d.ts +90 -0
  28. package/dist/cjs/credstore.js +219 -0
  29. package/dist/cjs/dispatcher.d.ts +58 -0
  30. package/dist/cjs/dispatcher.js +91 -0
  31. package/dist/cjs/github.d.ts +158 -0
  32. package/dist/cjs/github.js +84 -0
  33. package/dist/cjs/gitlab.d.ts +104 -0
  34. package/dist/cjs/gitlab.js +24 -0
  35. package/dist/cjs/handles.d.ts +82 -0
  36. package/dist/cjs/handles.js +139 -0
  37. package/dist/cjs/hint.d.ts +1 -0
  38. package/dist/cjs/hint.js +10 -0
  39. package/dist/cjs/http2.d.ts +206 -0
  40. package/dist/cjs/http2.js +1932 -0
  41. package/dist/cjs/index.d.ts +34 -0
  42. package/dist/cjs/index.js +31 -0
  43. package/dist/cjs/keyed.gen.d.ts +1 -0
  44. package/dist/cjs/keyed.gen.js +10 -0
  45. package/dist/cjs/models.gen.d.ts +6162 -0
  46. package/dist/cjs/models.gen.js +2 -0
  47. package/dist/cjs/package.json +1 -0
  48. package/dist/cjs/proxy.d.ts +40 -0
  49. package/dist/cjs/proxy.js +257 -0
  50. package/dist/cjs/pty.d.ts +171 -0
  51. package/dist/cjs/pty.js +504 -0
  52. package/dist/cjs/sandbox.d.ts +537 -0
  53. package/dist/cjs/sandbox.js +736 -0
  54. package/dist/cjs/ssh.d.ts +25 -0
  55. package/dist/cjs/ssh.js +117 -0
  56. package/dist/cjs/trace.d.ts +33 -0
  57. package/dist/cjs/trace.js +88 -0
  58. package/dist/cjs/transport-worker.d.ts +1 -0
  59. package/dist/cjs/transport-worker.js +468 -0
  60. package/dist/cjs/warm.d.ts +1 -0
  61. package/dist/cjs/warm.js +43 -0
  62. package/dist/client.d.ts +3015 -0
  63. package/dist/client.js +2583 -0
  64. package/dist/command-errors.d.ts +41 -0
  65. package/dist/command-errors.js +36 -0
  66. package/dist/context.d.ts +119 -0
  67. package/dist/context.js +588 -0
  68. package/dist/core.d.ts +285 -0
  69. package/dist/core.js +266 -0
  70. package/dist/credstore.d.ts +90 -0
  71. package/dist/credstore.js +206 -0
  72. package/dist/dispatcher.d.ts +58 -0
  73. package/dist/dispatcher.js +86 -0
  74. package/dist/github.d.ts +158 -0
  75. package/dist/github.js +78 -0
  76. package/dist/gitlab.d.ts +104 -0
  77. package/dist/gitlab.js +20 -0
  78. package/dist/handles.d.ts +82 -0
  79. package/dist/handles.js +135 -0
  80. package/dist/hint.d.ts +1 -0
  81. package/dist/hint.js +7 -0
  82. package/dist/http2.d.ts +206 -0
  83. package/dist/http2.js +1921 -0
  84. package/dist/index.d.ts +34 -0
  85. package/dist/index.js +15 -0
  86. package/dist/keyed.gen.d.ts +1 -0
  87. package/dist/keyed.gen.js +7 -0
  88. package/dist/models.gen.d.ts +6162 -0
  89. package/dist/models.gen.js +1 -0
  90. package/dist/proxy.d.ts +40 -0
  91. package/dist/proxy.js +249 -0
  92. package/dist/pty.d.ts +171 -0
  93. package/dist/pty.js +496 -0
  94. package/dist/sandbox.d.ts +537 -0
  95. package/dist/sandbox.js +725 -0
  96. package/dist/ssh.d.ts +25 -0
  97. package/dist/ssh.js +114 -0
  98. package/dist/trace.d.ts +33 -0
  99. package/dist/trace.js +80 -0
  100. package/dist/transport-worker.d.ts +1 -0
  101. package/dist/transport-worker.js +466 -0
  102. package/dist/warm.d.ts +1 -0
  103. package/dist/warm.js +40 -0
  104. package/package.json +54 -0
@@ -0,0 +1,2617 @@
1
+ "use strict";
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;
4
+ exports.execOutputBytes = execOutputBytes;
5
+ exports.configDir = configDir;
6
+ exports.credentialsPath = credentialsPath;
7
+ exports.credentialsTrustProblem = credentialsTrustProblem;
8
+ exports.credentialsExposure = credentialsExposure;
9
+ exports.storedCredentials = storedCredentials;
10
+ exports.storedCredentialsText = storedCredentialsText;
11
+ exports.readCredentialsWithStore = readCredentialsWithStore;
12
+ exports.readCredentials = readCredentials;
13
+ exports.apiUrlFromCompatUrl = apiUrlFromCompatUrl;
14
+ exports.apiUrlFromCompatDomain = apiUrlFromCompatDomain;
15
+ exports.compatApiKey = compatApiKey;
16
+ exports.keyOrigin = keyOrigin;
17
+ exports.imageRefProblem = imageRefProblem;
18
+ exports.checkImageRef = checkImageRef;
19
+ exports.imageRefNamesVersion = imageRefNamesVersion;
20
+ exports.checkImageVersioned = checkImageVersioned;
21
+ exports.repoUrlCredential = repoUrlCredential;
22
+ exports.withoutPin = withoutPin;
23
+ exports.checkRepoUrl = checkRepoUrl;
24
+ exports.checkDockerfileContext = checkDockerfileContext;
25
+ exports.snapshotCreateBody = snapshotCreateBody;
26
+ exports.errorFor = errorFor;
27
+ exports.isKeyedCreate = isKeyedCreate;
28
+ exports.checkSandboxId = checkSandboxId;
29
+ exports.checkSpendCap = checkSpendCap;
30
+ exports.newIdempotencyKey = newIdempotencyKey;
31
+ exports.serverAppMs = serverAppMs;
32
+ exports.toCommandResult = toCommandResult;
33
+ const node_fs_1 = require("node:fs");
34
+ const node_os_1 = require("node:os");
35
+ const node_util_1 = require("node:util");
36
+ const node_path_1 = require("node:path");
37
+ const core_js_1 = require("./core.js");
38
+ const bundle_js_1 = require("./bundle.js");
39
+ const credstore_js_1 = require("./credstore.js");
40
+ const caller_js_1 = require("./caller.js");
41
+ const pty_js_1 = require("./pty.js");
42
+ const ssh_js_1 = require("./ssh.js");
43
+ const http2_js_1 = require("./http2.js");
44
+ const context_js_1 = require("./context.js");
45
+ const proxy_js_1 = require("./proxy.js");
46
+ const trace_js_1 = require("./trace.js");
47
+ const keyed_gen_js_1 = require("./keyed.gen.js");
48
+ const dispatcher_js_1 = require("./dispatcher.js");
49
+ const ca_guard_js_1 = require("./ca-guard.js");
50
+ const hint_js_1 = require("./hint.js");
51
+ const github_js_1 = require("./github.js");
52
+ const gitlab_js_1 = require("./gitlab.js");
53
+ exports.FILE_TIMEOUT_MS = 15 * 60_000;
54
+ const isWaiting = (s) => s === "queued" || s === "awaiting_capacity";
55
+ exports.isWaiting = isWaiting;
56
+ function b64ToBytes(s) {
57
+ if (!s)
58
+ return new Uint8Array(0);
59
+ const bin = typeof atob === "function"
60
+ ? atob(s)
61
+ : Buffer.from(s, "base64").toString("binary");
62
+ const out = new Uint8Array(bin.length);
63
+ for (let i = 0; i < bin.length; i++)
64
+ out[i] = bin.charCodeAt(i);
65
+ return out;
66
+ }
67
+ function bytesToB64(b) {
68
+ if (typeof btoa !== "function")
69
+ return Buffer.from(b).toString("base64");
70
+ let bin = "";
71
+ for (let i = 0; i < b.length; i += 8192)
72
+ bin += String.fromCharCode(...b.subarray(i, i + 8192));
73
+ return btoa(bin);
74
+ }
75
+ function modeParam(mode) {
76
+ return typeof mode === "number" ? mode.toString(8) : mode;
77
+ }
78
+ function execOutputBytes(r, stream = "stdout") {
79
+ const b64 = r[`${stream}_b64`];
80
+ if (typeof b64 === "string")
81
+ return b64ToBytes(b64);
82
+ return new TextEncoder().encode(r[stream] ?? "");
83
+ }
84
+ function pageOf(r, key, asked) {
85
+ if (Array.isArray(r))
86
+ return { items: r, nextCursor: null, limit: asked };
87
+ const o = (r ?? {});
88
+ const items = Array.isArray(o[key]) ? o[key] : [];
89
+ const c = o["next_cursor"];
90
+ return {
91
+ items,
92
+ nextCursor: typeof c === "string" && c !== "" ? c : null,
93
+ limit: typeof o["limit"] === "number" ? o["limit"] : asked,
94
+ };
95
+ }
96
+ function orgSeg(org) {
97
+ if (!org?.trim())
98
+ throw new core_js_1.InvalidArgumentError("an organization is required", "Pass its id (org_…) or name; me().org_id is this key's.");
99
+ return encodeURIComponent(org.trim());
100
+ }
101
+ function pageQuery(limit, max, cursor) {
102
+ const q = new URLSearchParams({ limit: String(Math.max(1, Math.min(max, Math.floor(limit)))) });
103
+ if (cursor)
104
+ q.set("cursor", cursor);
105
+ return q.toString();
106
+ }
107
+ function expiryFields(o) {
108
+ const body = {};
109
+ if (o.expiresInDays !== undefined)
110
+ body["expires_in_days"] = o.expiresInDays;
111
+ if (o.expiresAt !== undefined)
112
+ body["expires_at"] = o.expiresAt;
113
+ return body;
114
+ }
115
+ exports.NOTICE_HEADER = "promigence-notice";
116
+ exports.MAX_SOURCES = 8;
117
+ function configDir(env = process.env) {
118
+ return (0, node_path_1.join)(env["XDG_CONFIG_HOME"] || (0, node_path_1.join)((0, node_os_1.homedir)(), ".config"), "promigence");
119
+ }
120
+ function credentialsPath(env = process.env) {
121
+ return (0, node_path_1.join)(configDir(env), "credentials");
122
+ }
123
+ function credentialsTrustProblem(path) {
124
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
125
+ const gid = typeof process.getgid === "function" ? process.getgid() : undefined;
126
+ if (uid === undefined)
127
+ return undefined;
128
+ for (const p of [(0, node_path_1.dirname)(path), path]) {
129
+ let st;
130
+ try {
131
+ st = (0, node_fs_1.statSync)(p);
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ if (st.uid !== uid)
137
+ return `${p} belongs to another user (uid ${st.uid})`;
138
+ if ((st.mode & 0o002) !== 0)
139
+ return `${p} is writable by every user`;
140
+ if ((st.mode & 0o020) !== 0 && st.gid !== gid)
141
+ return `${p} is writable by group ${st.gid}`;
142
+ }
143
+ return undefined;
144
+ }
145
+ const credentialsWarned = new Set();
146
+ function credentialsExposure(path) {
147
+ if (typeof process.getuid !== "function")
148
+ return undefined;
149
+ let st;
150
+ try {
151
+ st = (0, node_fs_1.statSync)(path);
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ if ((st.mode & 0o044) === 0)
157
+ return undefined;
158
+ return `${path} is readable by ${(st.mode & 0o004) !== 0 ? "every user" : "its group"} (mode ${(st.mode & 0o777).toString(8)})`;
159
+ }
160
+ function decodeCredentials(buf) {
161
+ if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe)
162
+ return buf.subarray(2).toString("utf16le");
163
+ if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) {
164
+ const le = Buffer.from(buf.subarray(2));
165
+ le.swap16();
166
+ return le.toString("utf16le");
167
+ }
168
+ if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf)
169
+ return buf.subarray(3).toString("utf8");
170
+ return buf.toString("utf8");
171
+ }
172
+ function storedCredentials(env = process.env, platform = process.platform) {
173
+ const path = credentialsPath(env);
174
+ let present = true;
175
+ try {
176
+ (0, node_fs_1.statSync)(path);
177
+ }
178
+ catch (e) {
179
+ present = e.code !== "ENOENT";
180
+ }
181
+ if (!present) {
182
+ const store = (0, credstore_js_1.keychainStore)(env, platform);
183
+ if (store === undefined)
184
+ return undefined;
185
+ const r = (0, credstore_js_1.keychainReadCached)(store, path, env);
186
+ if (r.status === "found")
187
+ return { text: r.text, store, path };
188
+ if (r.status === "absent")
189
+ return undefined;
190
+ if (r.locked) {
191
+ const why = `${(0, credstore_js_1.storeLabel)(store)} is locked`;
192
+ if (!credentialsWarned.has(why)) {
193
+ credentialsWarned.add(why);
194
+ process.emitWarning(`${why}, so the Promigence key stored in it cannot be read (an SSH session cannot unlock it). `
195
+ + (store === "macos-keychain" ? "Unlock it with `security unlock-keychain`, or " : "Unlock it, or ")
196
+ + "set PROMIGENCE_API_KEY for this shell.", { code: "PROMIGENCE_CREDENTIALS" });
197
+ }
198
+ }
199
+ return { unavailable: { store, reason: r.reason, locked: r.locked }, path };
200
+ }
201
+ const untrusted = credentialsTrustProblem(path);
202
+ if (untrusted !== undefined) {
203
+ if (!credentialsWarned.has(untrusted)) {
204
+ credentialsWarned.add(untrusted);
205
+ process.emitWarning(`ignoring the Promigence credentials file: ${untrusted}. Fix the owner or mode (chmod go-w), `
206
+ + "or set XDG_CONFIG_HOME to a directory only you can write.", { code: "PROMIGENCE_CREDENTIALS" });
207
+ }
208
+ return undefined;
209
+ }
210
+ let raw;
211
+ try {
212
+ raw = decodeCredentials((0, node_fs_1.readFileSync)(path));
213
+ }
214
+ catch {
215
+ return undefined;
216
+ }
217
+ const exposed = credentialsExposure(path);
218
+ if (exposed !== undefined && !credentialsWarned.has(exposed)) {
219
+ credentialsWarned.add(exposed);
220
+ process.emitWarning(`the Promigence credentials file holds an API key and ${exposed}. `
221
+ + `Run \`chmod 600 ${path}\` (promigence login writes it that way).`, { code: "PROMIGENCE_CREDENTIALS" });
222
+ }
223
+ return { text: raw, store: "file", path };
224
+ }
225
+ function storedCredentialsText(env = process.env) {
226
+ const s = storedCredentials(env);
227
+ return s === undefined || "unavailable" in s ? undefined : s;
228
+ }
229
+ function parseCredentials(raw) {
230
+ const text = raw.trim();
231
+ if (!text)
232
+ return {};
233
+ if (text.startsWith("{")) {
234
+ try {
235
+ const parsed = JSON.parse(text);
236
+ const out = {};
237
+ const key = parsed["api_key"] ?? parsed["apiKey"];
238
+ const url = parsed["api_url"] ?? parsed["apiUrl"];
239
+ if (typeof key === "string" && key.trim())
240
+ out.api_key = key.trim();
241
+ if (typeof url === "string" && url)
242
+ out.api_url = url;
243
+ return out;
244
+ }
245
+ catch {
246
+ return {};
247
+ }
248
+ }
249
+ const first = text.split("\n").map((l) => l.trim()).find((l) => l && !l.startsWith("#"));
250
+ return first ? { api_key: first } : {};
251
+ }
252
+ function readCredentialsWithStore(env = process.env) {
253
+ const s = storedCredentials(env);
254
+ if (s === undefined)
255
+ return { creds: {}, store: undefined };
256
+ if ("unavailable" in s)
257
+ return { creds: {}, store: undefined, unavailable: s.unavailable };
258
+ return { creds: parseCredentials(s.text), store: s.store };
259
+ }
260
+ function readCredentials(env = process.env) {
261
+ return readCredentialsWithStore(env).creds;
262
+ }
263
+ function storedName(store, env) {
264
+ return store === undefined || store === "file" ? `the credentials file (${credentialsPath(env)})` : (0, credstore_js_1.storeLabel)(store);
265
+ }
266
+ const COMPAT_REFUSED_HOSTS = /(^|\.)e2b(-staging)?\.(app|dev|pro)$/i;
267
+ function apiUrlFromCompatUrl(url) {
268
+ const u = (url ?? "").trim();
269
+ if (!u)
270
+ return undefined;
271
+ let host = "";
272
+ try {
273
+ host = new URL(u.includes("://") ? u : `https://${u}`).hostname;
274
+ }
275
+ catch {
276
+ return undefined;
277
+ }
278
+ return COMPAT_REFUSED_HOSTS.test(host) ? undefined : u;
279
+ }
280
+ function apiUrlFromCompatDomain(domain) {
281
+ const d = (domain ?? "").trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
282
+ if (!d || COMPAT_REFUSED_HOSTS.test(d))
283
+ return undefined;
284
+ return `https://api.${d}/v1`;
285
+ }
286
+ function compatApiKey(key) {
287
+ const k = key?.trim();
288
+ return k && !k.startsWith("e2b_") ? k : undefined;
289
+ }
290
+ function keyOrigin(env = process.env) {
291
+ if (env["PROMIGENCE_API_KEY"]?.trim())
292
+ return "PROMIGENCE_API_KEY";
293
+ const stored = readCredentialsWithStore(env);
294
+ if (stored.creds.api_key)
295
+ return stored.store === "macos-keychain" ? "macOS Keychain" : stored.store === "secret-service" ? "Secret Service keyring" : "credentials file";
296
+ return undefined;
297
+ }
298
+ 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
+ };
304
+ const optKey = opts.apiKey?.trim();
305
+ const envKey = optKey === undefined ? set(env["PROMIGENCE_API_KEY"]?.trim()) : undefined;
306
+ const envUrl = optKey !== undefined && opts.apiUrl !== undefined ? undefined : set(env["PROMIGENCE_API_URL"]);
307
+ const given = optKey !== undefined || envKey !== undefined;
308
+ const { creds, store } = given
309
+ ? { creds: {}, store: undefined }
310
+ : readCredentialsWithStore(env);
311
+ const source = optKey !== undefined ? "opts" : envKey !== undefined ? "env" : creds.api_key !== undefined ? "file" : undefined;
312
+ const ours = optKey ?? envKey ?? creds.api_key;
313
+ const fallback = ours === undefined ? opts.fallbackKey : undefined;
314
+ const fallbackKey = set(fallback?.apiKey.trim());
315
+ const fallbackUrl = fallbackKey === undefined ? undefined : set(fallback?.apiUrl);
316
+ const fromFile = () => trim(creds.api_url ?? fallbackUrl ?? core_js_1.DEFAULT_API_URL);
317
+ const fromEnv = () => (envUrl !== undefined ? trim(envUrl) : fromFile());
318
+ const apiUrl = opts.apiUrl !== undefined ? trim(opts.apiUrl) : fromEnv();
319
+ const paired = source === "file" ? fromFile() : source === "env" ? fromEnv() : apiUrl;
320
+ if (ours !== undefined && paired !== apiUrl) {
321
+ const keyFrom = source === "file" ? (store === undefined || store === "file" ? "the credentials file" : (0, credstore_js_1.storeLabel)(store)) : "PROMIGENCE_API_KEY";
322
+ const urlFrom = opts.apiUrl !== undefined ? "apiUrl" : "PROMIGENCE_API_URL";
323
+ const refusal = new core_js_1.InvalidArgumentError(`the API key from ${keyFrom} belongs to ${paired}; it is not sent to ${apiUrl} (from ${urlFrom})`, source === "file"
324
+ ? `Pass the key with the URL (apiKey beside apiUrl, or PROMIGENCE_API_KEY beside PROMIGENCE_API_URL), or run \`promigence login\` against ${apiUrl}.`
325
+ : "Pass apiKey together with apiUrl, or set PROMIGENCE_API_URL to that URL instead of passing apiUrl.");
326
+ return { apiKey: undefined, apiUrl, refusal };
327
+ }
328
+ const key = ours ?? fallbackKey;
329
+ if (key) {
330
+ const from = source === "opts" ? "the apiKey option" : source === "env" ? "PROMIGENCE_API_KEY"
331
+ : source === "file" ? storedName(store, env) : fallback.from;
332
+ const refusal = keyRefusal(key, from);
333
+ if (refusal)
334
+ return { apiKey: undefined, apiUrl, refusal };
335
+ }
336
+ return { apiKey: key, apiUrl };
337
+ }
338
+ function keyRefusal(key, from) {
339
+ if (/^[\x21-\x7E]+$/.test(key))
340
+ return undefined;
341
+ return new core_js_1.AuthError(`the API key from ${from} is not a valid HTTP header value (it holds a space, a control character or a non-ASCII character)`, `Check ${from} for stray characters inside the key, or run \`promigence login\` to store a fresh one.`);
342
+ }
343
+ function transportDetail(err) {
344
+ const refused = (0, ca_guard_js_1.compromisedKeyCause)(err);
345
+ if (refused !== undefined)
346
+ return refused.message;
347
+ const e = err;
348
+ if (e?.name === "TypeError" && /header/i.test(e.message ?? ""))
349
+ return "a request header holds a character HTTP does not allow";
350
+ return e?.cause?.code ?? e?.message ?? "connection failed";
351
+ }
352
+ function bodyExtra(payload) {
353
+ const e = payload.error;
354
+ return {
355
+ ...(e?.docs ? { docs: e.docs } : {}),
356
+ ...(typeof e?.category === "string" ? { category: e.category } : {}),
357
+ ...(typeof e?.retryable === "boolean" ? { retryable: e.retryable } : {}),
358
+ };
359
+ }
360
+ function codeForStatus(status) {
361
+ if (status === 400 || status === 422)
362
+ return "usage";
363
+ if (status === 401 || status === 403)
364
+ return "auth";
365
+ if (status === 404)
366
+ return "not_found";
367
+ if (status === 409)
368
+ return "cap_exceeded";
369
+ if (status === 402)
370
+ return "cap_exceeded";
371
+ if (status === 429)
372
+ return "rate_limited";
373
+ if (status === 502 || status === 503 || status === 504)
374
+ return "unreachable";
375
+ return "internal";
376
+ }
377
+ function errorCodeOf(text) {
378
+ try {
379
+ const code = JSON.parse(text)?.error?.code;
380
+ return typeof code === "string" ? code : undefined;
381
+ }
382
+ catch {
383
+ return undefined;
384
+ }
385
+ }
386
+ exports.IMAGE_DIGEST_RE = /^[^\s@]+@sha256:[0-9a-f]{64}$/;
387
+ const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
388
+ const IMAGE_TAG_RE = /^[A-Za-z0-9_.-]+$/;
389
+ const IMAGE_REPO_RE = /^[a-z0-9._/-]+$/;
390
+ function imageRefProblem(image) {
391
+ if (typeof image !== "string")
392
+ return "it is not a string";
393
+ const ref = image.trim();
394
+ if (!ref)
395
+ return "it is empty";
396
+ if (ref.length > 1024)
397
+ return "it is longer than 1024 characters";
398
+ if (/[\s\x00-\x1f\x7f]/.test(ref))
399
+ return "it contains whitespace";
400
+ let rest = ref;
401
+ const at = rest.lastIndexOf("@");
402
+ if (at >= 0) {
403
+ const digest = rest.slice(at + 1);
404
+ if (!SHA256_RE.test(digest))
405
+ return `digest ${JSON.stringify(digest)} is not \`sha256:\` followed by 64 lower-case hex characters`;
406
+ rest = rest.slice(0, at);
407
+ }
408
+ const colon = rest.lastIndexOf(":");
409
+ if (colon >= 0 && !rest.slice(colon + 1).includes("/")) {
410
+ const tag = rest.slice(colon + 1);
411
+ if (!tag)
412
+ return "its tag is empty";
413
+ if (!IMAGE_TAG_RE.test(tag))
414
+ return `tag ${JSON.stringify(tag)} is not a valid tag`;
415
+ rest = rest.slice(0, colon);
416
+ }
417
+ const slash = rest.indexOf("/");
418
+ const head = slash >= 0 ? rest.slice(0, slash) : "";
419
+ const repo = slash >= 0 && (head.includes(".") || head.includes(":") || head === "localhost") ? rest.slice(slash + 1) : rest;
420
+ if (!repo)
421
+ return "it names no repository";
422
+ if (!IMAGE_REPO_RE.test(repo)) {
423
+ return `repository ${JSON.stringify(repo)} is not a valid name (lower-case letters, digits, \`.\`, \`_\`, \`-\` and \`/\`)`;
424
+ }
425
+ return undefined;
426
+ }
427
+ function checkImageRef(image) {
428
+ const problem = imageRefProblem(image);
429
+ if (problem !== undefined) {
430
+ throw new core_js_1.InvalidArgumentError(`image ${JSON.stringify(image ?? null)} is not an image reference: ${problem}`, "Pass a tag such as python:3.12 or ghcr.io/org/app:v1 (the API pins it to a digest when " +
431
+ "the snapshot is created), or a reference already pinned as NAME@sha256:<64 hex>.");
432
+ }
433
+ }
434
+ function imageRefNamesVersion(image) {
435
+ const ref = image.trim();
436
+ if (ref.includes("@"))
437
+ return true;
438
+ const colon = ref.lastIndexOf(":");
439
+ return colon >= 0 && !ref.slice(colon + 1).includes("/");
440
+ }
441
+ function checkImageVersioned(image) {
442
+ if (imageRefNamesVersion(image))
443
+ return;
444
+ const ref = image.trim();
445
+ throw new core_js_1.InvalidArgumentError(`image ${JSON.stringify(ref)} names no tag or digest, so it would be pinned to whatever ${ref}:latest is today`, `Name the version: a tag (${ref}:<version>), ${ref}:latest if you mean the moving tag, or a digest (${ref}@sha256:<64 hex>).`);
446
+ }
447
+ exports.CONTEXT_GIT_RE = /^(?:https?:\/\/|ssh:\/\/|git:\/\/|git@)\S+@[0-9a-f]{40}$/i;
448
+ function repoUrlCredential(url) {
449
+ const u = url.trim();
450
+ const sep = u.indexOf("://");
451
+ let auth;
452
+ if (sep >= 0) {
453
+ const rest = u.slice(sep + 3);
454
+ const end = rest.search(/[/?#]/);
455
+ auth = end < 0 ? rest : rest.slice(0, end);
456
+ }
457
+ else {
458
+ const colon = u.indexOf(":");
459
+ if (colon >= 0 && !u.slice(0, colon).includes("/"))
460
+ auth = u.slice(0, colon);
461
+ }
462
+ if (auth !== undefined && (auth.includes("@") || /%40/i.test(auth) || (sep >= 0 && u.includes("@")))) {
463
+ return "userinfo";
464
+ }
465
+ if (u.includes("?"))
466
+ return "query";
467
+ if (u.includes("#"))
468
+ return "fragment";
469
+ return undefined;
470
+ }
471
+ function withoutPin(s) {
472
+ const at = s.lastIndexOf("@");
473
+ return at > 0 && !s.slice(at + 1).includes("/") ? s.slice(0, at) : s;
474
+ }
475
+ exports.REPO_CREDENTIALS_FIX = "Nothing was built or billed. A repository URL never carries a credential. For a private repository, connect its " +
476
+ "host once for your organization (`promigence github connect` or `promigence gitlab connect`) and name it as " +
477
+ "`--repo owner/name` (`--repo gitlab:group/project`); a public one is named by its plain https:// URL, with no " +
478
+ "user name, password, token, query string or fragment in it.";
479
+ function checkRepoUrl(url, field) {
480
+ if (typeof url === "string" && repoUrlCredential(url) !== undefined) {
481
+ throw new core_js_1.InvalidArgumentError(`credentials in repository URLs are not accepted (${field})`, exports.REPO_CREDENTIALS_FIX, "repo_credentials_not_supported");
482
+ }
483
+ }
484
+ function checkDockerfileContext(context) {
485
+ const c = typeof context === "string" ? context.trim() : "";
486
+ if (!c.startsWith(context_js_1.CONTEXT_TAR_PREFIX))
487
+ checkRepoUrl(withoutPin(c), "dockerfile.context");
488
+ if (!c) {
489
+ throw new core_js_1.InvalidArgumentError("snapshots.create dockerfile needs a context", "Pass { dockerfile: { context: 'https://github.com/org/app@<40-char sha>', path: 'Dockerfile' } }, " +
490
+ "or pack a directory with packContext('./app').");
491
+ }
492
+ if (c.startsWith(context_js_1.CONTEXT_TAR_PREFIX) || exports.CONTEXT_GIT_RE.test(c))
493
+ return;
494
+ throw new core_js_1.InvalidArgumentError(`dockerfile.context must be a git URL pinned to a full 40-character sha, got ${JSON.stringify(context)}`, "A branch or a tag is not a pin: pass URL@<40-char sha> (git rev-parse HEAD), or pack a local " +
495
+ "directory with packContext('./app').");
496
+ }
497
+ function sourcesBody(sources) {
498
+ if (sources.length > exports.MAX_SOURCES) {
499
+ throw new core_js_1.InvalidArgumentError(`snapshots.create takes at most ${exports.MAX_SOURCES} extra sources, got ${sources.length}`, "An environment assembled from more repositories than that is cheaper to build once as " +
500
+ "an image and snapshot from there.");
501
+ }
502
+ return sources.map((s, i) => {
503
+ checkRepoUrl(s?.repo, `sources[${i}].repo`);
504
+ if (!s?.repo) {
505
+ throw new core_js_1.InvalidArgumentError(`snapshots.create sources[${i}] needs a repo`, "Pass { repo: 'https://github.com/org/lib', commit: <40-char sha> }.");
506
+ }
507
+ if (!/^[0-9a-f]{40}$/i.test(s.commit ?? "")) {
508
+ throw new core_js_1.InvalidArgumentError(`snapshots.create sources[${i}] needs a full 40-character commit sha, got ${JSON.stringify(s.commit ?? null)}`, "A branch or a short sha is not a pin: pass the full sha (git rev-parse HEAD).");
509
+ }
510
+ const out = { repo: s.repo, commit: s.commit.toLowerCase() };
511
+ if (s.path)
512
+ out["path"] = s.path;
513
+ return out;
514
+ });
515
+ }
516
+ function verifyBody(v) {
517
+ if (v === undefined || v === null)
518
+ return undefined;
519
+ if (typeof v === "string")
520
+ return v ? { cmd: v, allow_fast: false } : undefined;
521
+ if (!v.cmd)
522
+ return undefined;
523
+ const raw = v;
524
+ return { cmd: v.cmd, allow_fast: Boolean(raw.allowFast ?? raw.allow_fast ?? false) };
525
+ }
526
+ function snapshotCreateBody(opts) {
527
+ const o = (opts ?? {});
528
+ const forms = [
529
+ ["image", o.image !== undefined],
530
+ ["dockerfile", o.dockerfile !== undefined],
531
+ ["repo", o.repo !== undefined || o.commit !== undefined],
532
+ ].filter(([, present]) => present).map(([n]) => n);
533
+ if (forms.length === 0) {
534
+ throw new core_js_1.InvalidArgumentError("snapshots.create needs an image, a dockerfile or a repo", "Pass { image: 'registry/name@sha256:…' }, { dockerfile: { context: …, path: 'Dockerfile' } }, " +
535
+ "or { repo: 'https://github.com/org/app', commit: <40-char sha> }.");
536
+ }
537
+ if (forms.length > 1) {
538
+ throw new core_js_1.InvalidArgumentError(`snapshots.create takes one input form, got ${forms.join(" + ")}`, "Pass exactly one of image, dockerfile, or repo/commit.");
539
+ }
540
+ const verify = verifyBody(o.verify);
541
+ let body;
542
+ const github = typeof o.repo === "string"
543
+ && (o.repo.trim().startsWith(github_js_1.GITHUB_REPO_PREFIX) || o.repo.trim().startsWith(gitlab_js_1.GITLAB_REPO_PREFIX));
544
+ if (o.env !== undefined && Object.keys(o.env).length && forms[0] === "repo") {
545
+ throw new core_js_1.InvalidArgumentError("snapshots.create env is for an image or a Dockerfile", "A repository snapshot sets its environment in its setup steps, or pass env on each command.");
546
+ }
547
+ if ((o.user !== undefined || o.setupUser !== undefined) && forms[0] === "repo") {
548
+ throw new core_js_1.InvalidArgumentError("snapshots.create user and setupUser are for an image or a Dockerfile", "A repository snapshot runs as root; name a user on each command instead ({ user: 'app' }).");
549
+ }
550
+ if (o.setupUser !== undefined && o.user === undefined) {
551
+ throw new core_js_1.InvalidArgumentError("snapshots.create setupUser needs user", "Pass { user: 'app', setupUser: 'app' }: start, setup and verify as app too (without setupUser they run as root), every later command as app.");
552
+ }
553
+ if (!github && (o.ref !== undefined || o.from !== undefined || o.track)) {
554
+ throw new core_js_1.InvalidArgumentError("snapshots.create ref, from and track are for a connected repository (repo: 'github:owner/name' or 'gitlab:group/project')", "Pass { repo: 'github:your-org/app', ref: 'main' }, or drop them for this form.");
555
+ }
556
+ if (github) {
557
+ body = (0, github_js_1.githubCreateBody)({
558
+ repo: o.repo, commit: o.commit, ref: o.ref, from: o.from, track: o.track,
559
+ setup: o.setup, verify, tier: o.tier, alias: o.alias,
560
+ others: [
561
+ ["sources", Boolean(o.sources?.length)], ["start", o.start !== undefined],
562
+ ["registrySecret", Boolean(o.registrySecret)],
563
+ ],
564
+ });
565
+ }
566
+ else if (forms[0] === "repo") {
567
+ if (!o.repo)
568
+ throw new core_js_1.InvalidArgumentError("snapshots.create needs a repo", "Pass { repo: 'https://github.com/org/app' }.");
569
+ checkRepoUrl(o.repo, "repo");
570
+ if (!/^[0-9a-f]{40}$/i.test(o.commit ?? "")) {
571
+ throw new core_js_1.InvalidArgumentError(`snapshots.create needs a full 40-character commit sha, got ${JSON.stringify(o.commit ?? null)}`, "A branch or a short sha is not a pin: pass the full sha (git rev-parse HEAD).");
572
+ }
573
+ if (!verify) {
574
+ throw new core_js_1.InvalidArgumentError("snapshots.create needs a verify command", "Pass { verify: { cmd: 'bun typecheck' } } — a snapshot with nothing to verify is not verified.");
575
+ }
576
+ body = { repo: o.repo, commit: o.commit.toLowerCase(), setup: o.setup ?? [], verify };
577
+ if (o.sources?.length)
578
+ body["sources"] = sourcesBody(o.sources);
579
+ if (o.tier !== undefined)
580
+ body["tier"] = o.tier;
581
+ }
582
+ else {
583
+ if (o.sources?.length) {
584
+ throw new core_js_1.InvalidArgumentError("snapshots.create sources is part of the repo form", "An image already carries its tree and may not have git in it: clone the extra " +
585
+ "repository in a setup step, or pass { repo, commit, sources }.");
586
+ }
587
+ body = {};
588
+ if (forms[0] === "image") {
589
+ checkImageRef(o.image);
590
+ body["image"] = o.image;
591
+ }
592
+ else {
593
+ const ctx = o.dockerfile?.context;
594
+ checkDockerfileContext(ctx);
595
+ body["dockerfile"] = { context: ctx, ...(o.dockerfile.path ? { path: o.dockerfile.path } : {}) };
596
+ }
597
+ if (o.setup?.length)
598
+ body["setup"] = [...o.setup];
599
+ if (verify)
600
+ body["verify"] = verify;
601
+ if (o.start !== undefined && o.start !== "" && !(Array.isArray(o.start) && o.start.length === 0)) {
602
+ body["start"] = typeof o.start === "string" ? o.start : [...o.start];
603
+ }
604
+ if (o.tier !== undefined)
605
+ body["tier"] = o.tier;
606
+ if (o.registrySecret)
607
+ body["registry_secret"] = o.registrySecret;
608
+ if (o.env !== undefined && Object.keys(o.env).length)
609
+ body["env"] = { ...o.env };
610
+ if (o.user !== undefined)
611
+ body["user"] = o.user;
612
+ if (o.setupUser !== undefined)
613
+ body["setup_user"] = o.setupUser;
614
+ }
615
+ if (o.alias)
616
+ body["alias"] = o.alias;
617
+ if (o.grade) {
618
+ if (!o.grade.cmd)
619
+ throw new core_js_1.InvalidArgumentError("snapshots.create grade needs a cmd", "Pass { grade: { cmd: 'pytest -q tests/' } }.");
620
+ const g = { cmd: o.grade.cmd };
621
+ if (o.grade.holdoutCmd)
622
+ g["holdout_cmd"] = o.grade.holdoutCmd;
623
+ if (o.grade.timeoutMs !== undefined)
624
+ g["timeout_ms"] = o.grade.timeoutMs;
625
+ if (o.grade.score !== undefined)
626
+ g["score"] = o.grade.score;
627
+ body["grade"] = g;
628
+ }
629
+ if (o.gradeOutput !== undefined) {
630
+ if (o.gradeOutput && !o.grade) {
631
+ throw new core_js_1.InvalidArgumentError("snapshots.create gradeOutput needs a grade", "Pass { grade: { cmd: 'pytest -q tests/' }, gradeOutput: true }.");
632
+ }
633
+ body["grade_output"] = o.gradeOutput;
634
+ }
635
+ return body;
636
+ }
637
+ const ENV_CODES = new Set([
638
+ "fork_failed", "start_timeout", "readiness_failed", "verify_too_slow", "verify_each_failed",
639
+ "toolchain_mismatch", "sandbox_unresponsive", "oom_within_tier", "disk_full_within_tier",
640
+ "sandbox_interrupted", "integrity_mismatch", "egress_unavailable", "service_timeout",
641
+ "resume_unavailable", "sandbox_unreachable", "capacity_busy", "region_at_capacity",
642
+ "builder_unavailable", "builder_busy", "overloaded", "tier_unavailable", "snapshot_unavailable",
643
+ "capacity_limit", "registry_unavailable", "network_policy_unsupported", "secrets_unavailable",
644
+ "secret_binding_unsupported", "idempotency_key_in_use",
645
+ ]);
646
+ const REFUSED_503_CODES = new Set([
647
+ "resume_unavailable", "sandbox_unreachable", "capacity_busy", "region_at_capacity",
648
+ "builder_unavailable", "builder_busy", "overloaded", "tier_unavailable", "registry_unavailable",
649
+ "secrets_unavailable",
650
+ ]);
651
+ function withoutCliHelp(fix) {
652
+ const out = fix.replace(/\s*See `promigence [^`]*--help`\.?\s*$/, "").trim();
653
+ if (out === fix.trim())
654
+ return fix;
655
+ return out === "" ? "Change what the message names, then call again." : out;
656
+ }
657
+ function errorFor(code, message, fix, extra = {}) {
658
+ const attach = (e) => {
659
+ const w = e;
660
+ w.docs = extra.docs ?? `${core_js_1.ERRORS_DOCS_URL}#${code}`;
661
+ if (extra.status !== undefined)
662
+ w.status = extra.status;
663
+ if (extra.requestId !== undefined)
664
+ w.requestId = extra.requestId;
665
+ if (extra.retryAfterMs !== undefined)
666
+ w.retryAfterMs = extra.retryAfterMs;
667
+ if (extra.category !== undefined)
668
+ w.category = extra.category;
669
+ if (extra.retryable !== undefined)
670
+ w.retryable = extra.retryable;
671
+ return e;
672
+ };
673
+ if ((code === "usage" || code === "invalid_argument" || code === "unsupported") && (0, core_js_1.previewsOff)(message)) {
674
+ return attach(new core_js_1.PreviewsUnavailableError());
675
+ }
676
+ switch (code) {
677
+ case "auth": return attach(new core_js_1.AuthError(message, fix));
678
+ case "not_found": return attach(new core_js_1.NotFoundError(message, fix));
679
+ case "unreachable": return attach(new core_js_1.UnreachableError(message, fix));
680
+ case "rate_limited": return attach(new core_js_1.RateLimitError(message, extra.retryAfterMs));
681
+ case "cap_exceeded":
682
+ case "cap_killed":
683
+ case "cap_paused":
684
+ case "credits_paused":
685
+ return attach(new core_js_1.SpendCapError(message, fix, code));
686
+ case "not_verified": return attach(new core_js_1.NotVerifiedError(message, fix));
687
+ case "timeout": return attach(new core_js_1.TimeoutError(message, fix));
688
+ case "usage":
689
+ case "invalid_argument":
690
+ return attach(new core_js_1.InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
691
+ case "not_released": return attach(new core_js_1.NotReleasedError(message));
692
+ case "service_restart": return attach(new core_js_1.ServiceRestartError(message, fix));
693
+ default:
694
+ if (ENV_CODES.has(code) || extra.category === "env")
695
+ return attach(new core_js_1.EnvFailedError(code, message, fix));
696
+ return attach(new core_js_1.ApiError({
697
+ code,
698
+ message,
699
+ fix,
700
+ exitCode: (0, core_js_1.exitForCode)(code),
701
+ ...(extra.docs === undefined ? {} : { docs: extra.docs }),
702
+ ...(extra.status === undefined ? {} : { status: extra.status }),
703
+ ...(extra.requestId === undefined ? {} : { requestId: extra.requestId }),
704
+ }));
705
+ }
706
+ }
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
+ function commandTimeoutMs(v) {
730
+ if (v === undefined)
731
+ return undefined;
732
+ if (!Number.isInteger(v) || v <= 0) {
733
+ throw new core_js_1.InvalidArgumentError(`timeoutMs must be a positive whole number of milliseconds, got ${String(v)}`, "Omit it for the default (15 minutes in the foreground, the sandbox's own timeout in the background), or pass a bound such as 600_000.");
734
+ }
735
+ return v;
736
+ }
737
+ function checkSandboxId(sandboxId, call) {
738
+ if (typeof sandboxId === "string" && sandboxId.trim() !== "")
739
+ return;
740
+ throw new core_js_1.InvalidArgumentError(`${call} needs a sandbox id, got ${JSON.stringify(sandboxId ?? null)}`, "Pass the id a create returned (sandbox.sandboxId, `sbx_…`). An unset environment variable is the usual way to pass an empty one.");
741
+ }
742
+ function checkSpendCap(spendCap) {
743
+ if (typeof spendCap === "number" && Number.isFinite(spendCap) && spendCap >= 0)
744
+ return spendCap;
745
+ throw new core_js_1.InvalidArgumentError(`spend cap must be a number of US dollars, 0 or more, got ${String(spendCap)}`, "Pass spendCap: 1 (or PROMIGENCE_SPEND_CAP=1): the most this run may spend.");
746
+ }
747
+ function newIdempotencyKey() {
748
+ return `idk_${globalThis.crypto.randomUUID()}`;
749
+ }
750
+ function parseRetryAfter(header) {
751
+ if (!header)
752
+ return undefined;
753
+ const secs = Number(header.trim());
754
+ if (Number.isFinite(secs))
755
+ return Math.max(0, secs * 1000);
756
+ const at = Date.parse(header);
757
+ if (!Number.isNaN(at))
758
+ return Math.max(0, at - Date.now());
759
+ return undefined;
760
+ }
761
+ const MAX_RETRY_SLEEP_MS = 10_000;
762
+ const STREAM_IDLE_MS = 90_000;
763
+ const sleep = (ms) => new Promise((r) => { setTimeout(r, ms); });
764
+ const POLLING_TICK_MS = 5;
765
+ const PARKED_TICK_MS = 50;
766
+ async function keepingLoopAwake(everyMs, fn) {
767
+ let tick;
768
+ try {
769
+ tick = setInterval(() => { }, everyMs);
770
+ tick.unref?.();
771
+ return await fn();
772
+ }
773
+ finally {
774
+ if (tick !== undefined)
775
+ clearInterval(tick);
776
+ }
777
+ }
778
+ const WAIT_MS_CAP = 30_000;
779
+ exports.LONG_POLL_MS = 5_000;
780
+ const BUILD_POLL_MS = 10_000;
781
+ const PROGRESS_POLL_MS = 200;
782
+ const WAITED_HEADER = "promigence-waited-ms";
783
+ const seg = (s) => encodeURIComponent(s).replace(/%40/g, "@");
784
+ function waitQuery(waitMs) {
785
+ return waitMs !== undefined && waitMs > 0
786
+ ? `?wait_ms=${Math.min(Math.floor(waitMs), WAIT_MS_CAP)}`
787
+ : "";
788
+ }
789
+ const notReleased = (what) => Promise.reject(new core_js_1.NotReleasedError(what));
790
+ let defaultUserAgent;
791
+ const APP_SPAN = /^\s*app\s*;(?:.*;)?\s*dur=([0-9]+(?:\.[0-9]+)?)/;
792
+ function serverAppMs(header) {
793
+ if (!header)
794
+ return null;
795
+ if (!header.includes(",")) {
796
+ const m = APP_SPAN.exec(header);
797
+ return m ? Number(m[1]) : null;
798
+ }
799
+ for (const part of header.split(",")) {
800
+ const m = APP_SPAN.exec(part);
801
+ if (m)
802
+ return Number(m[1]);
803
+ }
804
+ return null;
805
+ }
806
+ function headerOf(res, name) {
807
+ return res instanceof http2_js_1.H2Response ? res.header(name) : res.headers.get(name);
808
+ }
809
+ class PromigenceClient {
810
+ apiUrl;
811
+ #apiKey;
812
+ #keyRefusal;
813
+ #bearer;
814
+ fetchImpl;
815
+ ownFetch;
816
+ h2;
817
+ requestTimeoutMs;
818
+ maxRetries;
819
+ retryBaseMs;
820
+ userAgent;
821
+ cliErrors;
822
+ caller;
823
+ stats = { requests: 0, retries: 0 };
824
+ lastTiming = null;
825
+ notice;
826
+ onNotice;
827
+ onTiming;
828
+ noticesSeen = new Set();
829
+ constructor(opts = {}) {
830
+ (0, trace_js_1.traceEvent)("client:ctor");
831
+ const resolved = resolveCredentials(opts);
832
+ this.#apiKey = resolved.apiKey;
833
+ this.#keyRefusal = resolved.refusal;
834
+ this.apiUrl = resolved.apiUrl;
835
+ const env = opts.fetch === undefined ? (0, proxy_js_1.transportEnv)() : undefined;
836
+ const proxy = env !== undefined ? (0, proxy_js_1.proxyFor)(this.apiUrl, env) : undefined;
837
+ this.fetchImpl = opts.fetch ?? (proxy ? (0, proxy_js_1.proxyFetch)(proxy) : globalThis.fetch);
838
+ this.ownFetch = opts.fetch === undefined && proxy === undefined;
839
+ this.h2 = env !== undefined && this.apiUrl.startsWith("https:") && (0, http2_js_1.http2Allowed)(opts.http2, env)
840
+ ? new http2_js_1.Http2Transport(this.fetchImpl)
841
+ : undefined;
842
+ this.requestTimeoutMs = opts.requestTimeoutMs ?? 30_000;
843
+ this.maxRetries = opts.maxRetries ?? 3;
844
+ this.retryBaseMs = opts.retryBaseMs ?? 250;
845
+ this.caller = (0, caller_js_1.resolveCaller)(opts.caller);
846
+ this.userAgent = (0, caller_js_1.userAgentWithCaller)(opts.userAgent ?? (defaultUserAgent ??= `promigence/${core_js_1.VERSION} node/${process.versions.node}`), this.caller);
847
+ this.onNotice = opts.onNotice;
848
+ this.onTiming = opts.onTiming;
849
+ this.cliErrors = opts.cliErrors === true;
850
+ (0, trace_js_1.traceEvent)("client:ready");
851
+ }
852
+ observeNotice(raw) {
853
+ if (!raw)
854
+ return;
855
+ let n;
856
+ try {
857
+ n = JSON.parse(raw);
858
+ }
859
+ catch {
860
+ return;
861
+ }
862
+ if (!n || typeof n.code !== "string")
863
+ return;
864
+ this.notice = n;
865
+ const key = `${n.code}|${n.effective_at ?? ""}`;
866
+ if (this.noticesSeen.has(key))
867
+ return;
868
+ this.noticesSeen.add(key);
869
+ if (this.onNotice)
870
+ this.onNotice(n);
871
+ else
872
+ process.emitWarning(`${n.message ?? n.code} ${n.fix ?? ""}`.trim(), { code: "PROMIGENCE_NOTICE" });
873
+ }
874
+ get apiKey() {
875
+ return this.#apiKey;
876
+ }
877
+ get authenticated() {
878
+ return typeof this.#apiKey === "string" && this.#apiKey.length > 0;
879
+ }
880
+ toJSON() {
881
+ return { apiUrl: this.apiUrl, authenticated: this.authenticated };
882
+ }
883
+ [node_util_1.inspect.custom](_depth, opts) {
884
+ return `PromigenceClient ${(0, node_util_1.inspect)(this.toJSON(), opts)}`;
885
+ }
886
+ get credentialsProblem() {
887
+ return this.#keyRefusal;
888
+ }
889
+ preconnect() {
890
+ try {
891
+ if (this.h2 === undefined)
892
+ return;
893
+ (0, http2_js_1.warmTls)();
894
+ this.h2.preconnect(this.apiUrl);
895
+ }
896
+ catch {
897
+ }
898
+ }
899
+ requireKey() {
900
+ if (this.#keyRefusal)
901
+ throw this.#keyRefusal;
902
+ if (!this.#apiKey)
903
+ throw new core_js_1.AuthError();
904
+ return this.#apiKey;
905
+ }
906
+ bearer() {
907
+ const key = this.requireKey();
908
+ return (this.#bearer ??= `Bearer ${key}`);
909
+ }
910
+ refusalBackoff(status, retryAfterMs, code, attempt, replayable, signal) {
911
+ if (attempt >= this.maxRetries || signal?.aborted)
912
+ return undefined;
913
+ const gateway = (status === 502 || status === 504) && replayable && code === undefined;
914
+ if (status !== 429 && status !== 503 && !gateway)
915
+ return undefined;
916
+ if ((retryAfterMs ?? 0) > MAX_RETRY_SLEEP_MS)
917
+ return undefined;
918
+ if (status === 503 && !replayable && retryAfterMs === undefined && !REFUSED_503_CODES.has(code ?? ""))
919
+ return undefined;
920
+ const jitter = Math.random();
921
+ return retryAfterMs !== undefined
922
+ ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
923
+ : Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt) * (0.5 + jitter * 0.5);
924
+ }
925
+ readExtras(opts) {
926
+ const waitMs = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(opts.waitMs, WAIT_MS_CAP) : 0;
927
+ return {
928
+ ...(waitMs > 0 ? { timeoutMs: waitMs + this.requestTimeoutMs } : {}),
929
+ ...(opts.onHeaders ? { onHeaders: opts.onHeaders } : {}),
930
+ ...(opts.signal ? { signal: opts.signal } : {}),
931
+ };
932
+ }
933
+ guardDispatcher() {
934
+ if (!this.ownFetch || (0, dispatcher_js_1.customDispatcher)())
935
+ return undefined;
936
+ return (0, ca_guard_js_1.guardedFetchDispatcher)();
937
+ }
938
+ dispatcherFor(timeoutMs) {
939
+ const guarded = this.guardDispatcher();
940
+ if (guarded !== undefined)
941
+ return (0, dispatcher_js_1.longRequestDispatcher)(timeoutMs, guarded) ?? guarded;
942
+ return (0, dispatcher_js_1.longRequestDispatcher)(timeoutMs);
943
+ }
944
+ async request(opts) {
945
+ const tr = (0, trace_js_1.traceBegin)(opts.method, opts.path);
946
+ const url = `${this.apiUrl}${opts.path}`;
947
+ const headers = {
948
+ accept: "application/json",
949
+ "user-agent": this.userAgent,
950
+ };
951
+ const extra = opts.headers;
952
+ if (extra !== undefined && extra !== null) {
953
+ for (const k of Object.keys(extra)) {
954
+ const name = k.toLowerCase();
955
+ if (name !== "authorization")
956
+ headers[name] = extra[k];
957
+ }
958
+ }
959
+ if (this.caller)
960
+ headers[caller_js_1.CALLER_HEADER] = this.caller;
961
+ if (!opts.anonymous)
962
+ headers["authorization"] = this.bearer();
963
+ if (opts.body !== undefined)
964
+ headers["content-type"] = "application/json";
965
+ const keyedCreate = isKeyedCreate(opts.method, opts.path);
966
+ const idempotencyKey = opts.idempotencyKey ?? (keyedCreate ? newIdempotencyKey() : undefined);
967
+ if (idempotencyKey)
968
+ headers["idempotency-key"] = idempotencyKey;
969
+ 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
+ const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;
979
+ const dispatcher = this.dispatcherFor(timeoutMs);
980
+ let resentDelete = false;
981
+ for (let attempt = 0;; attempt++) {
982
+ let res;
983
+ this.stats.requests++;
984
+ const sentAt = performance.now();
985
+ try {
986
+ if (this.h2) {
987
+ const init = { method: opts.method, headers, timeoutMs };
988
+ if (opts.signal !== undefined)
989
+ init.signal = opts.signal;
990
+ if (opts.body !== undefined)
991
+ init.body = JSON.stringify(opts.body);
992
+ if (dispatcher !== undefined)
993
+ init.dispatcher = dispatcher;
994
+ if (tr !== undefined) {
995
+ init.trace = tr;
996
+ (0, trace_js_1.mark)(tr, "init");
997
+ }
998
+ res = await this.h2.fetch(url, init, replayable);
999
+ }
1000
+ else {
1001
+ const signals = [AbortSignal.timeout(timeoutMs)];
1002
+ if (opts.signal)
1003
+ signals.push(opts.signal);
1004
+ const init = {
1005
+ method: opts.method,
1006
+ headers,
1007
+ signal: signals.length === 1 ? signals[0] : AbortSignal.any(signals),
1008
+ ...(opts.body === undefined ? {} : { body: JSON.stringify(opts.body) }),
1009
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1010
+ };
1011
+ if (tr !== undefined)
1012
+ (0, trace_js_1.mark)(tr, "init");
1013
+ res = await this.fetchImpl(url, init);
1014
+ }
1015
+ if (tr !== undefined)
1016
+ (0, trace_js_1.mark)(tr, "res");
1017
+ this.lastTiming = {
1018
+ method: opts.method,
1019
+ path: opts.path,
1020
+ totalMs: performance.now() - sentAt,
1021
+ ourMs: serverAppMs(headerOf(res, "server-timing")),
1022
+ };
1023
+ this.onTiming?.(this.lastTiming);
1024
+ }
1025
+ catch (err) {
1026
+ const e = err;
1027
+ if (await again(attempt))
1028
+ continue;
1029
+ const cutOff = e?.cause?.code === "UND_ERR_HEADERS_TIMEOUT" || e?.cause?.code === "UND_ERR_BODY_TIMEOUT";
1030
+ const timedOut = e?.name === "TimeoutError" || e?.name === "AbortError" || cutOff;
1031
+ if (!timedOut && replayable && attempt < this.maxRetries && !opts.signal?.aborted) {
1032
+ this.stats.retries++;
1033
+ if (opts.method === "DELETE")
1034
+ resentDelete = true;
1035
+ const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
1036
+ await sleep(backoff * (0.5 + Math.random() * 0.5));
1037
+ continue;
1038
+ }
1039
+ if (opts.signal?.aborted) {
1040
+ throw new core_js_1.TimeoutError(`request ${opts.method} ${opts.path} was aborted by the caller's signal`, "Nothing to fix: the caller aborted it.");
1041
+ }
1042
+ if (e?.name === "TimeoutError" || e?.name === "AbortError") {
1043
+ throw new core_js_1.TimeoutError(`request ${opts.method} ${opts.path} timed out after ${timeoutMs} ms`, "Retry. To allow longer: promigence --request-timeout 120s <command>, " +
1044
+ "PROMIGENCE_REQUEST_TIMEOUT=120s, or requestTimeoutMs in the SDK. A --wait is bounded " +
1045
+ "by --wait-timeout and survives a slow request on its own.");
1046
+ }
1047
+ const code = e?.cause?.code;
1048
+ if (code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") {
1049
+ throw new core_js_1.TimeoutError(`request ${opts.method} ${opts.path} was cut off by the HTTP client (${code}) inside its ${timeoutMs} ms bound`, "Raise headersTimeout and bodyTimeout on the undici dispatcher this process installed " +
1050
+ "to at least the request's bound, or retry.");
1051
+ }
1052
+ throw new core_js_1.UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
1053
+ }
1054
+ const retryAfterMs = parseRetryAfter(headerOf(res, "retry-after"));
1055
+ const gateway = (res.status === 502 || res.status === 504) && replayable;
1056
+ let retryable = (res.status === 429 || res.status === 503 || gateway) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
1057
+ let body;
1058
+ if (retryable && res.status === 503 && !replayable && retryAfterMs === undefined) {
1059
+ body = await res.text().catch(() => "");
1060
+ retryable = REFUSED_503_CODES.has(errorCodeOf(body) ?? "");
1061
+ }
1062
+ if (retryable && gateway) {
1063
+ body = await res.text().catch(() => "");
1064
+ retryable = errorCodeOf(body) === undefined;
1065
+ }
1066
+ if (retryable && attempt < this.maxRetries) {
1067
+ const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
1068
+ const jitter = Math.random();
1069
+ const waitMs = retryAfterMs !== undefined
1070
+ ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
1071
+ : backoff * (0.5 + jitter * 0.5);
1072
+ this.stats.retries++;
1073
+ if (gateway && opts.method === "DELETE")
1074
+ resentDelete = true;
1075
+ if (body === undefined)
1076
+ try {
1077
+ await res.text();
1078
+ }
1079
+ catch { }
1080
+ await sleep(waitMs);
1081
+ continue;
1082
+ }
1083
+ if (tr !== undefined)
1084
+ (0, trace_js_1.mark)(tr, "hdrs");
1085
+ let text;
1086
+ if (body !== undefined)
1087
+ text = body;
1088
+ else if (res.status === 204)
1089
+ text = "";
1090
+ else {
1091
+ try {
1092
+ text = res instanceof http2_js_1.H2Response ? res.textNow() : await res.text();
1093
+ }
1094
+ catch (err) {
1095
+ if (!res.ok)
1096
+ text = "";
1097
+ else {
1098
+ const e = err;
1099
+ const timedOut = e?.name === "TimeoutError" || e?.name === "AbortError";
1100
+ if (!timedOut && replayable && attempt < this.maxRetries && !opts.signal?.aborted) {
1101
+ this.stats.retries++;
1102
+ if (opts.method === "DELETE")
1103
+ resentDelete = true;
1104
+ const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
1105
+ await sleep(backoff * (0.5 + Math.random() * 0.5));
1106
+ continue;
1107
+ }
1108
+ if (timedOut) {
1109
+ throw new core_js_1.TimeoutError(`request ${opts.method} ${opts.path} timed out after ${timeoutMs} ms while its answer was arriving`, "Retry. To allow longer: promigence --request-timeout 120s <command>, " +
1110
+ "PROMIGENCE_REQUEST_TIMEOUT=120s, or requestTimeoutMs in the SDK.");
1111
+ }
1112
+ throw new core_js_1.UnreachableError(`the connection to ${url} was lost while the answer to ${opts.method} ${opts.path} was arriving: ${transportDetail(e)}`);
1113
+ }
1114
+ }
1115
+ }
1116
+ if (tr !== undefined)
1117
+ (0, trace_js_1.mark)(tr, "txt");
1118
+ let payload = {};
1119
+ if (!res.ok) {
1120
+ try {
1121
+ payload = text ? JSON.parse(text) : {};
1122
+ }
1123
+ catch { }
1124
+ }
1125
+ if (res.status === 409 && keyedCreate && payload.error?.code === "idempotency_key_in_use" &&
1126
+ attempt < this.maxRetries && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS) {
1127
+ this.stats.retries++;
1128
+ await sleep((retryAfterMs ?? this.retryBaseMs) + Math.random() * Math.min(250, this.retryBaseMs));
1129
+ continue;
1130
+ }
1131
+ opts.onHeaders?.(res.headers);
1132
+ this.observeNotice(headerOf(res, exports.NOTICE_HEADER));
1133
+ if (res.status === 404 && opts.method === "DELETE" &&
1134
+ (resentDelete || (res instanceof http2_js_1.H2Response && res.replayed))) {
1135
+ if (tr !== undefined)
1136
+ (0, trace_js_1.mark)(tr, "ret");
1137
+ return { ok: true, deleted: true, already_gone: true };
1138
+ }
1139
+ if (!res.ok) {
1140
+ const code = payload.error?.code ?? codeForStatus(res.status);
1141
+ const message = payload.error?.message ?? `HTTP ${res.status} from ${opts.method} ${opts.path}`;
1142
+ const fix = payload.error?.fix ?? (code === "internal"
1143
+ ? "Not your fault. Re-run; if it repeats, include the request_id when reporting."
1144
+ : "See the API response for details.");
1145
+ const extra = { status: res.status, ...bodyExtra(payload), cli: this.cliErrors };
1146
+ const rid = headerOf(res, "request-id") ?? headerOf(res, "x-request-id");
1147
+ if (rid)
1148
+ extra.requestId = rid;
1149
+ if (retryAfterMs !== undefined)
1150
+ extra.retryAfterMs = retryAfterMs;
1151
+ throw errorFor(code, message, fix, extra);
1152
+ }
1153
+ if (!text) {
1154
+ if (tr !== undefined)
1155
+ (0, trace_js_1.mark)(tr, "ret");
1156
+ return undefined;
1157
+ }
1158
+ try {
1159
+ const parsed = JSON.parse(text);
1160
+ if (tr !== undefined)
1161
+ (0, trace_js_1.mark)(tr, "ret");
1162
+ return parsed;
1163
+ }
1164
+ catch {
1165
+ throw new core_js_1.ApiError({
1166
+ code: "internal",
1167
+ message: `${opts.method} ${opts.path} returned non-JSON (${res.status})`,
1168
+ fix: "The API must answer JSON; check PROMIGENCE_API_URL points at /v1.",
1169
+ exitCode: 1,
1170
+ status: res.status,
1171
+ });
1172
+ }
1173
+ }
1174
+ }
1175
+ async bytes(method, path, body, opts = {}) {
1176
+ const url = `${this.apiUrl}${path}`;
1177
+ const headers = {
1178
+ accept: "application/octet-stream, application/json",
1179
+ "user-agent": this.userAgent,
1180
+ authorization: `Bearer ${this.requireKey()}`,
1181
+ };
1182
+ if (this.caller)
1183
+ headers[caller_js_1.CALLER_HEADER] = this.caller;
1184
+ if (body !== undefined)
1185
+ headers["content-type"] = "application/octet-stream";
1186
+ const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
1187
+ if (opts.signal)
1188
+ signals.push(opts.signal);
1189
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1190
+ const dispatcher = this.guardDispatcher();
1191
+ for (let attempt = 0;; attempt++) {
1192
+ let res;
1193
+ this.stats.requests++;
1194
+ try {
1195
+ res = await this.fetchImpl(url, {
1196
+ method,
1197
+ headers,
1198
+ signal,
1199
+ ...(body === undefined ? {} : { body: body }),
1200
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1201
+ });
1202
+ }
1203
+ catch (err) {
1204
+ const e = err;
1205
+ if (e?.name === "TimeoutError" || e?.name === "AbortError") {
1206
+ throw new core_js_1.TimeoutError(`${method} ${path} timed out after ${opts.timeoutMs ?? exports.FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs, or fetch it in ranges.");
1207
+ }
1208
+ throw new core_js_1.UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
1209
+ }
1210
+ const buf = new Uint8Array(await res.arrayBuffer());
1211
+ if (res.ok)
1212
+ return buf;
1213
+ let payload = {};
1214
+ try {
1215
+ payload = JSON.parse(new TextDecoder().decode(buf));
1216
+ }
1217
+ catch { }
1218
+ const retryAfterMs = parseRetryAfter(res.headers.get("retry-after"));
1219
+ const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, method === "GET", opts.signal);
1220
+ if (wait !== undefined) {
1221
+ this.stats.retries++;
1222
+ await sleep(wait);
1223
+ continue;
1224
+ }
1225
+ const code = payload.error?.code ?? codeForStatus(res.status);
1226
+ throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
1227
+ status: res.status,
1228
+ ...bodyExtra(payload),
1229
+ ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
1230
+ });
1231
+ }
1232
+ }
1233
+ async stream(path, onChunk, opts = {}) {
1234
+ const url = `${this.apiUrl}${path}`;
1235
+ const headers = {
1236
+ accept: "application/octet-stream, application/json",
1237
+ "user-agent": this.userAgent,
1238
+ authorization: `Bearer ${this.requireKey()}`,
1239
+ };
1240
+ if (this.caller)
1241
+ headers[caller_js_1.CALLER_HEADER] = this.caller;
1242
+ const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
1243
+ if (opts.signal)
1244
+ signals.push(opts.signal);
1245
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1246
+ const timedOut = () => new core_js_1.TimeoutError(`GET ${path} timed out after ${opts.timeoutMs ?? exports.FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
1247
+ const dispatcher = this.guardDispatcher();
1248
+ for (let attempt = 0;; attempt++) {
1249
+ let res;
1250
+ this.stats.requests++;
1251
+ try {
1252
+ res = await this.fetchImpl(url, { method: "GET", headers, signal, ...(dispatcher === undefined ? {} : { dispatcher }) });
1253
+ }
1254
+ catch (err) {
1255
+ const e = err;
1256
+ if (e?.name === "TimeoutError" || e?.name === "AbortError")
1257
+ throw timedOut();
1258
+ throw new core_js_1.UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
1259
+ }
1260
+ if (res.ok) {
1261
+ let total = 0;
1262
+ if (!res.body)
1263
+ return { bytes: 0 };
1264
+ const reader = res.body.getReader();
1265
+ try {
1266
+ for (;;) {
1267
+ const { done, value } = await reader.read();
1268
+ if (done)
1269
+ break;
1270
+ if (value && value.length) {
1271
+ total += value.length;
1272
+ await onChunk(value);
1273
+ }
1274
+ }
1275
+ }
1276
+ catch (err) {
1277
+ const e = err;
1278
+ if (e?.name === "TimeoutError" || e?.name === "AbortError")
1279
+ throw timedOut();
1280
+ throw new core_js_1.UnreachableError(`the download of ${path} broke off after ${total} bytes: ${transportDetail(err)}`);
1281
+ }
1282
+ finally {
1283
+ reader.releaseLock();
1284
+ }
1285
+ return { bytes: total };
1286
+ }
1287
+ const buf = new Uint8Array(await res.arrayBuffer());
1288
+ let payload = {};
1289
+ try {
1290
+ payload = JSON.parse(new TextDecoder().decode(buf));
1291
+ }
1292
+ catch { }
1293
+ const retryAfterMs = parseRetryAfter(res.headers.get("retry-after"));
1294
+ const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, true, opts.signal);
1295
+ if (wait !== undefined) {
1296
+ this.stats.retries++;
1297
+ await sleep(wait);
1298
+ continue;
1299
+ }
1300
+ const code = payload.error?.code ?? codeForStatus(res.status);
1301
+ throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from GET ${path}`, payload.error?.fix ?? "See the API response for details.", {
1302
+ status: res.status,
1303
+ ...bodyExtra(payload),
1304
+ ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
1305
+ });
1306
+ }
1307
+ }
1308
+ health(opts = {}) {
1309
+ return this.request({
1310
+ method: "GET",
1311
+ path: "/health",
1312
+ anonymous: true,
1313
+ ...(opts.signal ? { signal: opts.signal } : {}),
1314
+ ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
1315
+ });
1316
+ }
1317
+ status(opts = {}) {
1318
+ return this.request({
1319
+ method: "GET",
1320
+ path: "/status",
1321
+ anonymous: true,
1322
+ ...(opts.signal ? { signal: opts.signal } : {}),
1323
+ ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
1324
+ });
1325
+ }
1326
+ async pricing(opts = {}) {
1327
+ if (opts.newAccount === true) {
1328
+ throw new core_js_1.InvalidArgumentError("pricing({ newAccount: true }) is no longer supported: the price list needs a key", "Call pricing() with your API key for your organization's prices. The published list prices are on the pricing page, and `promigence pricing` prints them without a key.");
1329
+ }
1330
+ this.requireKey();
1331
+ return this.request({
1332
+ method: "GET",
1333
+ path: "/pricing",
1334
+ ...(opts.signal ? { signal: opts.signal } : {}),
1335
+ ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
1336
+ });
1337
+ }
1338
+ templates = {
1339
+ list: async (opts = {}) => {
1340
+ const r = await this.request({
1341
+ method: "GET",
1342
+ path: "/templates",
1343
+ anonymous: true,
1344
+ ...(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
+ me(opts = {}) {
1356
+ return this.request({ method: "GET", path: "/me", ...(opts.signal ? { signal: opts.signal } : {}) });
1357
+ }
1358
+ outcomes(opts = {}) {
1359
+ return this.request({ method: "GET", path: "/me/outcomes?period=month", ...(opts.signal ? { signal: opts.signal } : {}) });
1360
+ }
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 ?? [];
1426
+ },
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 },
1430
+ }),
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({
1567
+ method: "GET",
1568
+ path: `/runs/${encodeURIComponent(runId)}/sandboxes${waitQuery(opts.waitMs)}`,
1569
+ ...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 } : {}),
1599
+ },
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({
1614
+ 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);
1632
+ }
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
+ };
1648
+ async waitLoop(opts, maxWaitMs, empty, read, done) {
1649
+ const bound = opts.waitTimeoutMs ?? core_js_1.WAIT_TIMEOUT_MS;
1650
+ const started = Date.now();
1651
+ let longPoll = opts.pollMs === undefined;
1652
+ let unmarkedFast = 0;
1653
+ let value = empty;
1654
+ for (let i = 0;; i++) {
1655
+ const waitMs = longPoll ? Math.max(0, Math.min(bound - (Date.now() - started), maxWaitMs)) : 0;
1656
+ let parked;
1657
+ const t0 = Date.now();
1658
+ const readOpts = {
1659
+ ...(waitMs > 0 ? { waitMs } : {}),
1660
+ onHeaders: (h) => {
1661
+ const v = h.get(WAITED_HEADER);
1662
+ if (v !== null)
1663
+ parked = Number(v);
1664
+ },
1665
+ ...(opts.signal ? { signal: opts.signal } : {}),
1666
+ };
1667
+ let timedOutRead = false;
1668
+ let comeBackMs = 0;
1669
+ try {
1670
+ value = await keepingLoopAwake(waitMs > 0 ? PARKED_TICK_MS : POLLING_TICK_MS, () => read(readOpts));
1671
+ }
1672
+ catch (err) {
1673
+ const elapsedNow = Date.now() - started;
1674
+ const refused = err instanceof core_js_1.RateLimitError ||
1675
+ (err instanceof core_js_1.PromigenceError && err.status === 503 && REFUSED_503_CODES.has(err.code));
1676
+ if (!(err instanceof core_js_1.TimeoutError || refused) || elapsedNow >= bound || opts.signal?.aborted)
1677
+ throw err;
1678
+ timedOutRead = true;
1679
+ if (refused)
1680
+ comeBackMs = err.retryAfterMs ?? 0;
1681
+ }
1682
+ const roundTripMs = Date.now() - t0;
1683
+ if (!timedOutRead && done(value))
1684
+ return { value, timedOut: false, waitedMs: Date.now() - started };
1685
+ const elapsed = Date.now() - started;
1686
+ if (elapsed >= bound || opts.signal?.aborted)
1687
+ return { value, timedOut: true, waitedMs: elapsed };
1688
+ if (timedOutRead) {
1689
+ await sleep(Math.min(Math.max(comeBackMs, opts.pollMs ?? pollInterval(i)), Math.max(1, bound - elapsed)));
1690
+ continue;
1691
+ }
1692
+ if (waitMs > 0 && parked === undefined && roundTripMs < waitMs / 2 && ++unmarkedFast >= 2) {
1693
+ longPoll = false;
1694
+ }
1695
+ const served = parked !== undefined && parked >= Math.min(Math.floor(waitMs), WAIT_MS_CAP);
1696
+ if (served)
1697
+ continue;
1698
+ await sleep(Math.min(opts.pollMs ?? pollInterval(i), Math.max(1, bound - elapsed)));
1699
+ }
1700
+ }
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;
1810
+ }
1811
+ clearTimeout(timer);
1812
+ const text = await r.text().catch(() => "");
1813
+ let payload = {};
1814
+ try {
1815
+ payload = text ? JSON.parse(text) : {};
1816
+ }
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;
1824
+ }
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 (;;) {
1843
+ arm();
1844
+ let chunk;
1845
+ try {
1846
+ chunk = await reader.read();
1847
+ }
1848
+ catch (err) {
1849
+ if (opts.signal?.aborted)
1850
+ throw err;
1851
+ if (silent)
1852
+ throw lost(`was silent for ${STREAM_IDLE_MS / 1000} s`);
1853
+ const e = err;
1854
+ throw lost(`was cut off (${e?.cause?.code ?? e?.message ?? "connection lost"})`);
1855
+ }
1856
+ if (chunk.done)
1857
+ 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 = {};
1883
+ try {
1884
+ parsed = JSON.parse(data);
1885
+ }
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);
1892
+ }
1893
+ else {
1894
+ errOut.push(bytes);
1895
+ on.stderr?.(bytes);
1896
+ }
1897
+ }
1898
+ else if (event === "exit") {
1899
+ exit = { ...(exit ?? {}), ...parsed };
1900
+ }
1901
+ else if (event === "error") {
1902
+ failure = parsed;
1903
+ }
1904
+ }
1905
+ }
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;
1930
+ }
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.");
2015
+ }
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.");
2040
+ }
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 ?? [];
2050
+ },
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.");
2060
+ }
2061
+ return this.request({
2062
+ 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
+ },
2071
+ });
2072
+ },
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) });
2097
+ },
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 }),
2132
+ });
2133
+ },
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);
2147
+ }
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) }),
2167
+ },
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 }),
2176
+ },
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({
2249
+ method: "GET",
2250
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list?${q.join("&")}`,
2251
+ });
2252
+ },
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("&")}`,
2271
+ });
2272
+ },
2273
+ mkdir: (sandboxId, path, opts = {}) => this.request({
2274
+ 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
+ },
2281
+ }),
2282
+ move: (sandboxId, from, to, opts = {}) => this.request({
2283
+ method: "POST",
2284
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/move`,
2285
+ body: { from, to, ...(opts.overwrite === undefined ? {} : { overwrite: opts.overwrite }) },
2286
+ }),
2287
+ copy: (sandboxId, from, to, opts = {}) => this.request({
2288
+ method: "POST",
2289
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/copy`,
2290
+ body: {
2291
+ from, to,
2292
+ ...(opts.recursive === undefined ? {} : { recursive: opts.recursive }),
2293
+ ...(opts.overwrite === undefined ? {} : { overwrite: opts.overwrite }),
2294
+ },
2295
+ }),
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.");
2299
+ }
2300
+ return this.request({
2301
+ method: "POST",
2302
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/chmod`,
2303
+ body: {
2304
+ path,
2305
+ ...(opts.mode === undefined ? {} : { mode: modeParam(opts.mode) }),
2306
+ ...(opts.uid === undefined ? {} : { uid: opts.uid }),
2307
+ ...(opts.gid === undefined ? {} : { gid: opts.gid }),
2308
+ ...(opts.recursive === undefined ? {} : { recursive: opts.recursive }),
2309
+ },
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.");
2350
+ }
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 };
2357
+ },
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) {
2399
+ try {
2400
+ found = await fromSandbox(opts.sandboxId);
2401
+ }
2402
+ catch (err) {
2403
+ if (!(err instanceof core_js_1.NotFoundError))
2404
+ throw err;
2405
+ found = undefined;
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
+ };
2415
+ sandbox(id) {
2416
+ return {
2417
+ id,
2418
+ exec: (opts) => this.sandboxes.exec(id, opts).then(toCommandResult),
2419
+ get: () => this.sandboxes.get(id),
2420
+ kill: () => this.sandboxes.kill(id),
2421
+ pause: () => this.sandboxes.pause(id),
2422
+ resume: (opts) => this.sandboxes.resume(id, opts),
2423
+ patch: (opts) => this.sandboxes.patch(id, opts),
2424
+ files: {
2425
+ get: (path) => this.sandboxes.files.get(id, path),
2426
+ text: (path) => this.sandboxes.files.text(id, path),
2427
+ put: (path, data, opts) => this.sandboxes.files.put(id, path, data, opts ?? {}),
2428
+ stat: (path) => this.sandboxes.files.stat(id, path),
2429
+ delete: (path) => this.sandboxes.files.delete(id, path),
2430
+ },
2431
+ probe: (opts = {}) => this.sandboxes.probe(id, opts),
2432
+ };
2433
+ }
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({
2520
+ 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
+ };
2569
+ }
2570
+ exports.PromigenceClient = PromigenceClient;
2571
+ const POLL_LADDER_MS = [10, 25, 25, 25, 25, 25, 25, 25, 25, 50, 50, 50, 100, 100, 100, 200];
2572
+ function pollInterval(iteration) {
2573
+ const step = POLL_LADDER_MS[iteration];
2574
+ if (step !== undefined)
2575
+ return step;
2576
+ if (iteration < 30)
2577
+ return 500;
2578
+ return 1000;
2579
+ }
2580
+ function sliceOutput(e, sandboxId, runId, range) {
2581
+ const enc = new TextEncoder();
2582
+ const dec = new TextDecoder();
2583
+ const cut = (text) => {
2584
+ const all = enc.encode(text);
2585
+ if (!range)
2586
+ return { text, total: all.length, bytes: all.length };
2587
+ const from = Math.max(0, Math.min(range.from ?? 0, all.length));
2588
+ const to = Math.min(all.length - 1, range.to ?? all.length - 1);
2589
+ const part = to < from ? new Uint8Array(0) : all.subarray(from, to + 1);
2590
+ return { text: dec.decode(part), total: all.length, bytes: part.length };
2591
+ };
2592
+ const out = cut(e.stdout ?? "");
2593
+ const err = cut(e.stderr ?? "");
2594
+ return {
2595
+ exec_id: e.exec_id,
2596
+ ...(sandboxId ?? e.sandbox_id ? { sandbox_id: (sandboxId ?? e.sandbox_id) } : {}),
2597
+ ...(runId ?? e.run_id ? { run_id: (runId ?? e.run_id) } : {}),
2598
+ ...(e.argv ? { argv: e.argv } : {}),
2599
+ exit_code: e.exit_code ?? null,
2600
+ stdout: out.text,
2601
+ stderr: err.text,
2602
+ truncated: e.truncated ?? false,
2603
+ bytes: out.bytes + err.bytes,
2604
+ total_bytes: out.total + err.total,
2605
+ ...(range ? { range: { from: range.from ?? 0, to: range.to ?? Math.max(0, out.total - 1) } } : {}),
2606
+ };
2607
+ }
2608
+ function toCommandResult(r) {
2609
+ const signal = r["signal"];
2610
+ return {
2611
+ exitCode: r.exit_code ?? (typeof signal === "number" && signal > 0 ? 128 + signal : -1),
2612
+ stdout: r.stdout ?? "",
2613
+ stderr: r.stderr ?? "",
2614
+ ...(r.truncated === undefined ? {} : { truncated: r.truncated }),
2615
+ ...(r.exec_id === undefined ? {} : { execId: r.exec_id }),
2616
+ };
2617
+ }