@promigence/sdk 0.1.0-beta.14 → 0.1.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/caller.js +1 -1
- package/dist/cjs/caller.js +1 -1
- package/dist/cjs/client.d.ts +134 -23
- package/dist/cjs/client.js +202 -80
- package/dist/cjs/command-errors.d.ts +1 -0
- package/dist/cjs/command-errors.js +2 -0
- package/dist/cjs/context.d.ts +19 -4
- package/dist/cjs/context.js +77 -5
- package/dist/cjs/core.d.ts +32 -4
- package/dist/cjs/core.js +30 -5
- package/dist/cjs/http2.d.ts +8 -0
- package/dist/cjs/http2.js +22 -1
- package/dist/cjs/models.gen.d.ts +663 -43
- package/dist/cjs/pty.d.ts +11 -14
- package/dist/cjs/pty.js +15 -12
- package/dist/cjs/sandbox.d.ts +26 -8
- package/dist/cjs/sandbox.js +28 -4
- package/dist/cjs/transport-worker.js +10 -1
- package/dist/client.d.ts +134 -23
- package/dist/client.js +201 -77
- package/dist/command-errors.d.ts +1 -0
- package/dist/command-errors.js +3 -1
- package/dist/context.d.ts +19 -4
- package/dist/context.js +74 -4
- package/dist/core.d.ts +32 -4
- package/dist/core.js +26 -4
- package/dist/http2.d.ts +8 -0
- package/dist/http2.js +22 -1
- package/dist/models.gen.d.ts +663 -43
- package/dist/pty.d.ts +11 -14
- package/dist/pty.js +13 -11
- package/dist/sandbox.d.ts +26 -8
- package/dist/sandbox.js +28 -4
- package/dist/transport-worker.js +10 -1
- package/package.json +1 -1
package/dist/cjs/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PromigenceClient = exports.LONG_POLL_MS = exports.isKeyedCreate = exports.REPO_CREDENTIALS_FIX = exports.CONTEXT_GIT_RE = exports.IMAGE_DIGEST_RE = exports.MAX_SOURCES = exports.NOTICE_HEADER = exports.isWaiting = exports.FILE_TIMEOUT_MS = void 0;
|
|
3
|
+
exports.PromigenceClient = exports.RUN_SETTLE_MS = exports.LONG_POLL_MS = exports.isKeyedCreate = exports.REPO_CREDENTIALS_FIX = exports.CONTEXT_GIT_RE = exports.IMAGE_DIGEST_RE = exports.NO_KEY_FIX = exports.NO_KEY_MESSAGE = exports.MAX_SOURCES = exports.NOTICE_HEADER = exports.OUTPUT_RETENTION_MAX_DAYS = exports.isWaiting = exports.FILE_TIMEOUT_MS = void 0;
|
|
4
4
|
exports.execOutputBytes = execOutputBytes;
|
|
5
5
|
exports.configDir = configDir;
|
|
6
6
|
exports.credentialsPath = credentialsPath;
|
|
@@ -10,9 +10,6 @@ exports.storedCredentials = storedCredentials;
|
|
|
10
10
|
exports.storedCredentialsText = storedCredentialsText;
|
|
11
11
|
exports.readCredentialsWithStore = readCredentialsWithStore;
|
|
12
12
|
exports.readCredentials = readCredentials;
|
|
13
|
-
exports.apiUrlFromCompatUrl = apiUrlFromCompatUrl;
|
|
14
|
-
exports.apiUrlFromCompatDomain = apiUrlFromCompatDomain;
|
|
15
|
-
exports.compatApiKey = compatApiKey;
|
|
16
13
|
exports.keyOrigin = keyOrigin;
|
|
17
14
|
exports.imageRefProblem = imageRefProblem;
|
|
18
15
|
exports.checkImageRef = checkImageRef;
|
|
@@ -27,6 +24,7 @@ exports.errorFor = errorFor;
|
|
|
27
24
|
exports.checkSandboxId = checkSandboxId;
|
|
28
25
|
exports.checkSpendCap = checkSpendCap;
|
|
29
26
|
exports.newIdempotencyKey = newIdempotencyKey;
|
|
27
|
+
exports.runWorkDone = runWorkDone;
|
|
30
28
|
exports.serverAppMs = serverAppMs;
|
|
31
29
|
exports.toCommandResult = toCommandResult;
|
|
32
30
|
const node_fs_1 = require("node:fs");
|
|
@@ -75,6 +73,26 @@ function bytesToB64(b) {
|
|
|
75
73
|
function modeParam(mode) {
|
|
76
74
|
return typeof mode === "number" ? mode.toString(8) : mode;
|
|
77
75
|
}
|
|
76
|
+
function jsonBody(value) {
|
|
77
|
+
return { data: new TextEncoder().encode(JSON.stringify(value)), contentType: "application/json" };
|
|
78
|
+
}
|
|
79
|
+
function fileForm(fields, data) {
|
|
80
|
+
const boundary = `promigence-${globalThis.crypto.randomUUID().replace(/-/g, "")}`;
|
|
81
|
+
let head = "";
|
|
82
|
+
for (const [name, value] of fields) {
|
|
83
|
+
head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
|
|
84
|
+
}
|
|
85
|
+
head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\n`
|
|
86
|
+
+ "Content-Type: application/octet-stream\r\n\r\n";
|
|
87
|
+
const enc = new TextEncoder();
|
|
88
|
+
const pre = enc.encode(head);
|
|
89
|
+
const post = enc.encode(`\r\n--${boundary}--\r\n`);
|
|
90
|
+
const out = new Uint8Array(pre.length + data.length + post.length);
|
|
91
|
+
out.set(pre, 0);
|
|
92
|
+
out.set(data, pre.length);
|
|
93
|
+
out.set(post, pre.length + data.length);
|
|
94
|
+
return { data: out, contentType: `multipart/form-data; boundary=${boundary}` };
|
|
95
|
+
}
|
|
78
96
|
function execOutputBytes(r, stream = "stdout") {
|
|
79
97
|
const b64 = r[`${stream}_b64`];
|
|
80
98
|
if (typeof b64 === "string")
|
|
@@ -104,6 +122,32 @@ function pageQuery(limit, max, cursor) {
|
|
|
104
122
|
q.set("cursor", cursor);
|
|
105
123
|
return q.toString();
|
|
106
124
|
}
|
|
125
|
+
exports.OUTPUT_RETENTION_MAX_DAYS = 30;
|
|
126
|
+
function projectUpdateBody(u) {
|
|
127
|
+
const body = {};
|
|
128
|
+
if (u.name !== undefined)
|
|
129
|
+
body["name"] = u.name;
|
|
130
|
+
if (u.outputRetentionDays !== undefined) {
|
|
131
|
+
const d = u.outputRetentionDays;
|
|
132
|
+
if (d !== null && (!Number.isInteger(d) || d < 1 || d > exports.OUTPUT_RETENTION_MAX_DAYS)) {
|
|
133
|
+
throw new core_js_1.InvalidArgumentError(`outputRetentionDays must be a whole number from 1 to ${exports.OUTPUT_RETENTION_MAX_DAYS}, or null, not ${String(d)}`, "Pass null for the organization's setting.");
|
|
134
|
+
}
|
|
135
|
+
body["output_retention_days"] = d;
|
|
136
|
+
}
|
|
137
|
+
if (u.failureCaptures !== undefined) {
|
|
138
|
+
if (u.failureCaptures !== null && typeof u.failureCaptures !== "boolean") {
|
|
139
|
+
throw new core_js_1.InvalidArgumentError("failureCaptures must be true, false or null", "Pass null for the organization's setting.");
|
|
140
|
+
}
|
|
141
|
+
if (Object.keys(body).length) {
|
|
142
|
+
throw new core_js_1.InvalidArgumentError("failureCaptures is set on its own", "Change it in a call of its own, without name or outputRetentionDays.");
|
|
143
|
+
}
|
|
144
|
+
body["failure_captures"] = u.failureCaptures;
|
|
145
|
+
}
|
|
146
|
+
if (!Object.keys(body).length) {
|
|
147
|
+
throw new core_js_1.InvalidArgumentError("projects.update needs something to change", "Pass name, outputRetentionDays or failureCaptures.");
|
|
148
|
+
}
|
|
149
|
+
return body;
|
|
150
|
+
}
|
|
107
151
|
function expiryFields(o) {
|
|
108
152
|
const body = {};
|
|
109
153
|
if (o.expiresInDays !== undefined)
|
|
@@ -263,30 +307,6 @@ function readCredentials(env = process.env) {
|
|
|
263
307
|
function storedName(store, env) {
|
|
264
308
|
return store === undefined || store === "file" ? `the credentials file (${credentialsPath(env)})` : (0, credstore_js_1.storeLabel)(store);
|
|
265
309
|
}
|
|
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
310
|
function keyOrigin(env = process.env) {
|
|
291
311
|
if (env["PROMIGENCE_API_KEY"]?.trim())
|
|
292
312
|
return "PROMIGENCE_API_KEY";
|
|
@@ -313,12 +333,13 @@ function resolveCredentials(opts, env = process.env) {
|
|
|
313
333
|
const inputs = optKey === undefined && envKey === undefined
|
|
314
334
|
? undefined
|
|
315
335
|
: `${optKey ?? "\0"}\n${envKey ?? "\0"}\n${envUrl ?? "\0"}\n${opts.apiUrl ?? "\0"}`;
|
|
316
|
-
if (inputs !== undefined && inputs === credsLastInputs && credsLast !== undefined)
|
|
317
|
-
return { apiKey: credsLast.apiKey, apiUrl: credsLast.apiUrl };
|
|
336
|
+
if (inputs !== undefined && inputs === credsLastInputs && credsLast !== undefined) {
|
|
337
|
+
return { apiKey: credsLast.apiKey, apiUrl: credsLast.apiUrl, ...(credsLast.origin ? { origin: credsLast.origin } : {}) };
|
|
338
|
+
}
|
|
318
339
|
const worked = resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl);
|
|
319
340
|
if (inputs !== undefined && worked.refusal === undefined) {
|
|
320
341
|
credsLastInputs = inputs;
|
|
321
|
-
credsLast = { apiKey: worked.apiKey, apiUrl: worked.apiUrl };
|
|
342
|
+
credsLast = { apiKey: worked.apiKey, apiUrl: worked.apiUrl, ...(worked.origin ? { origin: worked.origin } : {}) };
|
|
322
343
|
}
|
|
323
344
|
return worked;
|
|
324
345
|
}
|
|
@@ -352,8 +373,15 @@ function resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl) {
|
|
|
352
373
|
if (refusal)
|
|
353
374
|
return { apiKey: undefined, apiUrl, refusal };
|
|
354
375
|
}
|
|
355
|
-
|
|
376
|
+
const origin = !key ? undefined
|
|
377
|
+
: source === "opts" ? { source: "option" }
|
|
378
|
+
: source === "env" ? { source: "env" }
|
|
379
|
+
: source === "file" ? { source: "stored", ...(store === undefined ? {} : { store }) }
|
|
380
|
+
: { source: "fallback" };
|
|
381
|
+
return { apiKey: key, apiUrl, ...(origin ? { origin } : {}) };
|
|
356
382
|
}
|
|
383
|
+
exports.NO_KEY_MESSAGE = "no API key: none was given, PROMIGENCE_API_KEY is not set, and none is stored";
|
|
384
|
+
exports.NO_KEY_FIX = "Pass { apiKey } or set PROMIGENCE_API_KEY. No key yet: `promigence login` signs you up or in and stores one.";
|
|
357
385
|
function keyRefusal(key, from) {
|
|
358
386
|
if (/^[\x21-\x7E]+$/.test(key))
|
|
359
387
|
return undefined;
|
|
@@ -633,6 +661,8 @@ function snapshotCreateBody(opts) {
|
|
|
633
661
|
}
|
|
634
662
|
if (o.alias)
|
|
635
663
|
body["alias"] = o.alias;
|
|
664
|
+
if (o.alias && o.moveAlias)
|
|
665
|
+
body["move_alias"] = true;
|
|
636
666
|
if (o.grade) {
|
|
637
667
|
if (!o.grade.cmd)
|
|
638
668
|
throw new core_js_1.InvalidArgumentError("snapshots.create grade needs a cmd", "Pass { grade: { cmd: 'pytest -q tests/' } }.");
|
|
@@ -667,6 +697,9 @@ const REFUSED_503_CODES = new Set([
|
|
|
667
697
|
"builder_unavailable", "builder_busy", "overloaded", "tier_unavailable", "registry_unavailable",
|
|
668
698
|
"secrets_unavailable",
|
|
669
699
|
]);
|
|
700
|
+
const SDK_FIXES = {
|
|
701
|
+
cap_exceeded: "Raise `spendCap`, lower `timeoutMs` or `count`, or pass `allowPartial` to launch what fits.",
|
|
702
|
+
};
|
|
670
703
|
function withoutCliHelp(fix) {
|
|
671
704
|
const out = fix.replace(/\s*See `promigence [^`]*--help`\.?\s*$/, "").trim();
|
|
672
705
|
if (out === fix.trim())
|
|
@@ -676,7 +709,7 @@ function withoutCliHelp(fix) {
|
|
|
676
709
|
function errorFor(code, message, fix, extra = {}) {
|
|
677
710
|
const attach = (e) => {
|
|
678
711
|
const w = e;
|
|
679
|
-
w.docs = extra.docs
|
|
712
|
+
w.docs = extra.docs !== undefined && e.code === code ? extra.docs : (0, core_js_1.errorsDocsFor)(e.code);
|
|
680
713
|
if (extra.status !== undefined)
|
|
681
714
|
w.status = extra.status;
|
|
682
715
|
if (extra.requestId !== undefined)
|
|
@@ -692,6 +725,8 @@ function errorFor(code, message, fix, extra = {}) {
|
|
|
692
725
|
if ((code === "usage" || code === "invalid_argument" || code === "unsupported") && (0, core_js_1.previewsOff)(message)) {
|
|
693
726
|
return attach(new core_js_1.PreviewsUnavailableError());
|
|
694
727
|
}
|
|
728
|
+
if (!extra.cli && Object.prototype.hasOwnProperty.call(SDK_FIXES, code))
|
|
729
|
+
fix = SDK_FIXES[code];
|
|
695
730
|
switch (code) {
|
|
696
731
|
case "auth": return attach(new core_js_1.AuthError(message, fix));
|
|
697
732
|
case "not_found": return attach(new core_js_1.NotFoundError(message, fix));
|
|
@@ -705,8 +740,13 @@ function errorFor(code, message, fix, extra = {}) {
|
|
|
705
740
|
case "not_verified": return attach(new core_js_1.NotVerifiedError(message, fix));
|
|
706
741
|
case "timeout": return attach(new core_js_1.TimeoutError(message, fix));
|
|
707
742
|
case "usage":
|
|
708
|
-
case "invalid_argument":
|
|
743
|
+
case "invalid_argument": {
|
|
744
|
+
const ended = code === "usage" ? (0, core_js_1.sandboxEndedIn)(message) : undefined;
|
|
745
|
+
if (ended !== undefined) {
|
|
746
|
+
return attach(new core_js_1.SandboxEndedError(message, ended.sandboxId, ended.state, extra.cli ? fix : core_js_1.SANDBOX_ENDED_FIX));
|
|
747
|
+
}
|
|
709
748
|
return attach(new core_js_1.InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
|
|
749
|
+
}
|
|
710
750
|
case "not_released": return attach(new core_js_1.NotReleasedError(message));
|
|
711
751
|
case "service_restart": return attach(new core_js_1.ServiceRestartError(message, fix));
|
|
712
752
|
default:
|
|
@@ -776,6 +816,21 @@ const WAIT_MS_CAP = 30_000;
|
|
|
776
816
|
exports.LONG_POLL_MS = 5_000;
|
|
777
817
|
const BUILD_POLL_MS = 10_000;
|
|
778
818
|
const PROGRESS_POLL_MS = 200;
|
|
819
|
+
exports.RUN_SETTLE_MS = 5_000;
|
|
820
|
+
function runWorkDone(run, endedSince, now = Date.now()) {
|
|
821
|
+
if (run.state === "completed" || run.state === "cap_killed" || run.state === "aborted")
|
|
822
|
+
return true;
|
|
823
|
+
const sbs = Array.isArray(run.sandboxes) ? run.sandboxes : [];
|
|
824
|
+
const allDone = sbs.length > 0 && sbs.every((s) => s.state === "ended" || s.command_outcome != null);
|
|
825
|
+
if (!allDone) {
|
|
826
|
+
delete endedSince.at;
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
if (sbs.some((s) => s.state !== "ended"))
|
|
830
|
+
return true;
|
|
831
|
+
endedSince.at ??= now;
|
|
832
|
+
return now - endedSince.at >= exports.RUN_SETTLE_MS;
|
|
833
|
+
}
|
|
779
834
|
const WAITED_HEADER = "promigence-waited-ms";
|
|
780
835
|
const seg = (s) => encodeURIComponent(s).replace(/%40/g, "@");
|
|
781
836
|
function waitQuery(waitMs) {
|
|
@@ -813,6 +868,9 @@ class PromigenceClient {
|
|
|
813
868
|
apiUrl;
|
|
814
869
|
#apiKey;
|
|
815
870
|
#keyRefusal;
|
|
871
|
+
#keyOrigin;
|
|
872
|
+
#onKeyUse;
|
|
873
|
+
#keyUseSaid = false;
|
|
816
874
|
#bearer;
|
|
817
875
|
fetchImpl;
|
|
818
876
|
ownFetch;
|
|
@@ -834,6 +892,8 @@ class PromigenceClient {
|
|
|
834
892
|
const resolved = resolveCredentials(opts);
|
|
835
893
|
this.#apiKey = resolved.apiKey;
|
|
836
894
|
this.#keyRefusal = resolved.refusal;
|
|
895
|
+
this.#keyOrigin = resolved.origin;
|
|
896
|
+
this.#onKeyUse = opts.onKeyUse;
|
|
837
897
|
this.apiUrl = resolved.apiUrl;
|
|
838
898
|
const env = opts.fetch === undefined ? (0, proxy_js_1.transportEnv)() : undefined;
|
|
839
899
|
const proxy = env !== undefined ? (0, proxy_js_1.proxyFor)(this.apiUrl, env) : undefined;
|
|
@@ -899,11 +959,18 @@ class PromigenceClient {
|
|
|
899
959
|
catch {
|
|
900
960
|
}
|
|
901
961
|
}
|
|
962
|
+
get keyOrigin() {
|
|
963
|
+
return this.#keyOrigin;
|
|
964
|
+
}
|
|
902
965
|
requireKey() {
|
|
903
966
|
if (this.#keyRefusal)
|
|
904
967
|
throw this.#keyRefusal;
|
|
905
968
|
if (!this.#apiKey)
|
|
906
|
-
throw new core_js_1.AuthError();
|
|
969
|
+
throw this.cliErrors ? new core_js_1.AuthError(exports.NO_KEY_MESSAGE) : new core_js_1.AuthError(exports.NO_KEY_MESSAGE, exports.NO_KEY_FIX);
|
|
970
|
+
if (!this.#keyUseSaid && this.#onKeyUse !== undefined && this.#keyOrigin !== undefined) {
|
|
971
|
+
this.#keyUseSaid = true;
|
|
972
|
+
this.#onKeyUse(this.#keyOrigin);
|
|
973
|
+
}
|
|
907
974
|
return this.#apiKey;
|
|
908
975
|
}
|
|
909
976
|
bearer() {
|
|
@@ -976,7 +1043,8 @@ class PromigenceClient {
|
|
|
976
1043
|
const idempotencyKey = opts.idempotencyKey ?? (keyedCreate ? newIdempotencyKey() : undefined);
|
|
977
1044
|
if (idempotencyKey)
|
|
978
1045
|
headers["idempotency-key"] = idempotencyKey;
|
|
979
|
-
const replayable = opts.method === "GET" || opts.method === "DELETE" ||
|
|
1046
|
+
const replayable = opts.method === "GET" || opts.method === "DELETE" || opts.replayable === true
|
|
1047
|
+
|| (keyedCreate && Boolean(idempotencyKey));
|
|
980
1048
|
const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;
|
|
981
1049
|
let dispatcher;
|
|
982
1050
|
let resentDelete = false;
|
|
@@ -1173,7 +1241,7 @@ class PromigenceClient {
|
|
|
1173
1241
|
}
|
|
1174
1242
|
}
|
|
1175
1243
|
}
|
|
1176
|
-
async bytes(method, path, body, opts = {}) {
|
|
1244
|
+
async bytes(method, path, body, opts = {}, read = method === "GET") {
|
|
1177
1245
|
const url = `${this.apiUrl}${path}`;
|
|
1178
1246
|
const headers = {
|
|
1179
1247
|
accept: "application/octet-stream, application/json",
|
|
@@ -1183,7 +1251,7 @@ class PromigenceClient {
|
|
|
1183
1251
|
if (this.caller)
|
|
1184
1252
|
headers[caller_js_1.CALLER_HEADER] = this.caller;
|
|
1185
1253
|
if (body !== undefined)
|
|
1186
|
-
headers["content-type"] =
|
|
1254
|
+
headers["content-type"] = body.contentType;
|
|
1187
1255
|
const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
|
|
1188
1256
|
if (opts.signal)
|
|
1189
1257
|
signals.push(opts.signal);
|
|
@@ -1197,7 +1265,7 @@ class PromigenceClient {
|
|
|
1197
1265
|
method,
|
|
1198
1266
|
headers,
|
|
1199
1267
|
signal,
|
|
1200
|
-
...(body === undefined ? {} : { body: body }),
|
|
1268
|
+
...(body === undefined ? {} : { body: body.data }),
|
|
1201
1269
|
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1202
1270
|
});
|
|
1203
1271
|
}
|
|
@@ -1217,21 +1285,23 @@ class PromigenceClient {
|
|
|
1217
1285
|
}
|
|
1218
1286
|
catch { }
|
|
1219
1287
|
const retryAfterMs = parseRetryAfter(res.headers.get("retry-after"));
|
|
1220
|
-
const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt,
|
|
1288
|
+
const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, read, opts.signal);
|
|
1221
1289
|
if (wait !== undefined) {
|
|
1222
1290
|
this.stats.retries++;
|
|
1223
1291
|
await sleep(wait);
|
|
1224
1292
|
continue;
|
|
1225
1293
|
}
|
|
1226
1294
|
const code = payload.error?.code ?? codeForStatus(res.status);
|
|
1295
|
+
const rid = headerOf(res, "request-id") ?? headerOf(res, "x-request-id");
|
|
1227
1296
|
throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
|
|
1228
1297
|
status: res.status,
|
|
1229
1298
|
...bodyExtra(payload),
|
|
1230
1299
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
1300
|
+
...(rid ? { requestId: rid } : {}),
|
|
1231
1301
|
});
|
|
1232
1302
|
}
|
|
1233
1303
|
}
|
|
1234
|
-
async stream(path, onChunk, opts = {}) {
|
|
1304
|
+
async stream(method, path, onChunk, opts = {}, body) {
|
|
1235
1305
|
const url = `${this.apiUrl}${path}`;
|
|
1236
1306
|
const headers = {
|
|
1237
1307
|
accept: "application/octet-stream, application/json",
|
|
@@ -1240,17 +1310,25 @@ class PromigenceClient {
|
|
|
1240
1310
|
};
|
|
1241
1311
|
if (this.caller)
|
|
1242
1312
|
headers[caller_js_1.CALLER_HEADER] = this.caller;
|
|
1313
|
+
if (body !== undefined)
|
|
1314
|
+
headers["content-type"] = body.contentType;
|
|
1243
1315
|
const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
|
|
1244
1316
|
if (opts.signal)
|
|
1245
1317
|
signals.push(opts.signal);
|
|
1246
1318
|
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1247
|
-
const timedOut = () => new core_js_1.TimeoutError(
|
|
1319
|
+
const timedOut = () => new core_js_1.TimeoutError(`${method} ${path} timed out after ${opts.timeoutMs ?? exports.FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
|
|
1248
1320
|
const dispatcher = this.guardDispatcher();
|
|
1249
1321
|
for (let attempt = 0;; attempt++) {
|
|
1250
1322
|
let res;
|
|
1251
1323
|
this.stats.requests++;
|
|
1252
1324
|
try {
|
|
1253
|
-
res = await this.fetchImpl(url, {
|
|
1325
|
+
res = await this.fetchImpl(url, {
|
|
1326
|
+
method,
|
|
1327
|
+
headers,
|
|
1328
|
+
signal,
|
|
1329
|
+
...(body === undefined ? {} : { body: body.data }),
|
|
1330
|
+
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1331
|
+
});
|
|
1254
1332
|
}
|
|
1255
1333
|
catch (err) {
|
|
1256
1334
|
const e = err;
|
|
@@ -1299,10 +1377,12 @@ class PromigenceClient {
|
|
|
1299
1377
|
continue;
|
|
1300
1378
|
}
|
|
1301
1379
|
const code = payload.error?.code ?? codeForStatus(res.status);
|
|
1302
|
-
|
|
1380
|
+
const rid = headerOf(res, "request-id") ?? headerOf(res, "x-request-id");
|
|
1381
|
+
throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
|
|
1303
1382
|
status: res.status,
|
|
1304
1383
|
...bodyExtra(payload),
|
|
1305
1384
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
1385
|
+
...(rid ? { requestId: rid } : {}),
|
|
1306
1386
|
});
|
|
1307
1387
|
}
|
|
1308
1388
|
}
|
|
@@ -1378,6 +1458,29 @@ class PromigenceClient {
|
|
|
1378
1458
|
const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
|
|
1379
1459
|
return pageOf(r, "entries", limit);
|
|
1380
1460
|
},
|
|
1461
|
+
holds: () => this.request({ method: "GET", path: "/billing/holds" }),
|
|
1462
|
+
buyCredits: (amountUsd) => this.request({ method: "POST", path: "/billing/credits", body: { amount_usd: amountUsd } }),
|
|
1463
|
+
settleCredits: (sessionId) => {
|
|
1464
|
+
if (!sessionId?.trim())
|
|
1465
|
+
throw new core_js_1.InvalidArgumentError("billing.settleCredits needs a session id", "Pass the session_id buyCredits() answered.");
|
|
1466
|
+
return this.request({ method: "POST", path: `/billing/credits/${encodeURIComponent(sessionId)}/settle`, body: {} });
|
|
1467
|
+
},
|
|
1468
|
+
autoReload: () => this.request({ method: "GET", path: "/billing/auto-reload" }),
|
|
1469
|
+
setAutoReload: (opts) => this.request({
|
|
1470
|
+
method: "PUT",
|
|
1471
|
+
path: "/billing/auto-reload",
|
|
1472
|
+
body: {
|
|
1473
|
+
on: opts.on,
|
|
1474
|
+
...(opts.thresholdUsd === undefined ? {} : { threshold_usd: opts.thresholdUsd }),
|
|
1475
|
+
...(opts.amountUsd === undefined ? {} : { amount_usd: opts.amountUsd }),
|
|
1476
|
+
...(opts.authorizationVersion === undefined ? {} : { authorization_version: opts.authorizationVersion }),
|
|
1477
|
+
},
|
|
1478
|
+
}),
|
|
1479
|
+
repayDispute: (disputeId) => {
|
|
1480
|
+
if (!disputeId?.trim())
|
|
1481
|
+
throw new core_js_1.InvalidArgumentError("billing.repayDispute needs a dispute id", "Pass the hold's dispute_id from billing.usage().");
|
|
1482
|
+
return this.request({ method: "POST", path: `/billing/disputes/${encodeURIComponent(disputeId)}/repay`, body: {} });
|
|
1483
|
+
},
|
|
1381
1484
|
checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
|
|
1382
1485
|
portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
|
|
1383
1486
|
referrals: () => this.request({ method: "GET", path: "/referrals" }),
|
|
@@ -1436,6 +1539,9 @@ class PromigenceClient {
|
|
|
1436
1539
|
rename: (org, project, name) => this.request({
|
|
1437
1540
|
method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: { name },
|
|
1438
1541
|
}),
|
|
1542
|
+
update: async (org, project, update) => this.request({
|
|
1543
|
+
method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: projectUpdateBody(update),
|
|
1544
|
+
}),
|
|
1439
1545
|
},
|
|
1440
1546
|
};
|
|
1441
1547
|
}
|
|
@@ -1652,12 +1758,8 @@ class PromigenceClient {
|
|
|
1652
1758
|
return { sandboxes: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
|
|
1653
1759
|
},
|
|
1654
1760
|
waitDone: async (runId, opts = {}) => {
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
const allDone = Array.isArray(run.sandboxes) && run.sandboxes.length > 0 &&
|
|
1658
|
-
run.sandboxes.every((s) => s.state === "ended" || s.command_outcome != null);
|
|
1659
|
-
return terminal || allDone;
|
|
1660
|
-
});
|
|
1761
|
+
const endedSince = {};
|
|
1762
|
+
const r = await this.waitLoop(opts, exports.LONG_POLL_MS, { run_id: runId, state: "running" }, (read) => this.runs.get(runId, read), (run) => runWorkDone(run, endedSince));
|
|
1661
1763
|
return { run: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
|
|
1662
1764
|
},
|
|
1663
1765
|
};
|
|
@@ -2066,10 +2168,19 @@ class PromigenceClient {
|
|
|
2066
2168
|
}), sandboxId);
|
|
2067
2169
|
return r.ports ?? [];
|
|
2068
2170
|
},
|
|
2069
|
-
revoke: (sandboxId, tokenOrPort) =>
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2171
|
+
revoke: (sandboxId, tokenOrPort) => {
|
|
2172
|
+
const which = String(tokenOrPort).trim();
|
|
2173
|
+
return /^[0-9]+$/.test(which)
|
|
2174
|
+
? this.request({
|
|
2175
|
+
method: "DELETE",
|
|
2176
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/${which}`,
|
|
2177
|
+
})
|
|
2178
|
+
: this.request({
|
|
2179
|
+
method: "POST",
|
|
2180
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/revoke`,
|
|
2181
|
+
body: { token: which },
|
|
2182
|
+
});
|
|
2183
|
+
},
|
|
2073
2184
|
},
|
|
2074
2185
|
ssh: {
|
|
2075
2186
|
open: (sandboxId, opts) => {
|
|
@@ -2184,6 +2295,7 @@ class PromigenceClient {
|
|
|
2184
2295
|
...(opts.spendCap === undefined ? {} : { spend_cap: checkSpendCap(opts.spendCap) }),
|
|
2185
2296
|
},
|
|
2186
2297
|
}),
|
|
2298
|
+
resumePausedForCredits: () => this.request({ method: "POST", path: "/sandboxes/resume", body: { paused_for: "credits" } }),
|
|
2187
2299
|
snapshot: (sandboxId, opts = {}) => this.request({
|
|
2188
2300
|
method: "POST",
|
|
2189
2301
|
path: `/sandboxes/${encodeURIComponent(sandboxId)}/snapshot`,
|
|
@@ -2254,38 +2366,42 @@ class PromigenceClient {
|
|
|
2254
2366
|
},
|
|
2255
2367
|
files: {
|
|
2256
2368
|
list: (sandboxId, path = "/", opts = {}) => {
|
|
2257
|
-
const
|
|
2369
|
+
const body = { path };
|
|
2258
2370
|
if (opts.recursive)
|
|
2259
|
-
|
|
2371
|
+
body.recursive = true;
|
|
2260
2372
|
if (opts.depth !== undefined)
|
|
2261
|
-
|
|
2373
|
+
body.depth = Math.max(0, Math.floor(opts.depth));
|
|
2262
2374
|
if (opts.limit !== undefined)
|
|
2263
|
-
|
|
2375
|
+
body.limit = Math.max(1, Math.floor(opts.limit));
|
|
2264
2376
|
if (opts.hidden === false)
|
|
2265
|
-
|
|
2377
|
+
body.hidden = false;
|
|
2266
2378
|
return this.request({
|
|
2267
|
-
method: "
|
|
2268
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list
|
|
2379
|
+
method: "POST",
|
|
2380
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list`,
|
|
2381
|
+
body,
|
|
2382
|
+
replayable: true,
|
|
2269
2383
|
});
|
|
2270
2384
|
},
|
|
2271
2385
|
search: (sandboxId, path, opts = {}) => {
|
|
2272
2386
|
if (!opts.pattern && !opts.contains) {
|
|
2273
2387
|
throw new core_js_1.InvalidArgumentError("files.search needs `pattern`, `contains`, or both", "Pass { pattern: '*.py' } for a name glob, { contains: 'TODO' } for text inside files.");
|
|
2274
2388
|
}
|
|
2275
|
-
const
|
|
2389
|
+
const body = { path };
|
|
2276
2390
|
if (opts.pattern)
|
|
2277
|
-
|
|
2391
|
+
body.pattern = opts.pattern;
|
|
2278
2392
|
if (opts.contains)
|
|
2279
|
-
|
|
2393
|
+
body.contains = opts.contains;
|
|
2280
2394
|
if (opts.ignoreCase)
|
|
2281
|
-
|
|
2395
|
+
body.ignore_case = true;
|
|
2282
2396
|
if (opts.limit !== undefined)
|
|
2283
|
-
|
|
2397
|
+
body.limit = Math.max(1, Math.floor(opts.limit));
|
|
2284
2398
|
if (opts.hidden === false)
|
|
2285
|
-
|
|
2399
|
+
body.hidden = false;
|
|
2286
2400
|
return this.request({
|
|
2287
|
-
method: "
|
|
2288
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search
|
|
2401
|
+
method: "POST",
|
|
2402
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search`,
|
|
2403
|
+
body,
|
|
2404
|
+
replayable: true,
|
|
2289
2405
|
});
|
|
2290
2406
|
},
|
|
2291
2407
|
mkdir: (sandboxId, path, opts = {}) => this.request({
|
|
@@ -2342,11 +2458,13 @@ class PromigenceClient {
|
|
|
2342
2458
|
body: { archive, into, ...(opts.strip === undefined ? {} : { strip: Math.max(0, Math.floor(opts.strip)) }) },
|
|
2343
2459
|
}),
|
|
2344
2460
|
stat: (sandboxId, path) => this.request({
|
|
2345
|
-
method: "
|
|
2346
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat
|
|
2461
|
+
method: "POST",
|
|
2462
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat`,
|
|
2463
|
+
body: { path },
|
|
2464
|
+
replayable: true,
|
|
2347
2465
|
}),
|
|
2348
|
-
get: (sandboxId, path, opts = {}) => this.bytes("
|
|
2349
|
-
download: (sandboxId, path, onChunk, opts = {}) => this.stream(`/sandboxes/${encodeURIComponent(sandboxId)}/files
|
|
2466
|
+
get: (sandboxId, path, opts = {}) => this.bytes("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, jsonBody({ path }), opts, true),
|
|
2467
|
+
download: (sandboxId, path, onChunk, opts = {}) => this.stream("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, onChunk, opts, jsonBody({ path })),
|
|
2350
2468
|
text: async (sandboxId, path, opts = {}) => new TextDecoder().decode(await this.sandboxes.files.get(sandboxId, path, opts)),
|
|
2351
2469
|
put: async (sandboxId, path, data, opts = {}) => {
|
|
2352
2470
|
const body = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
@@ -2356,8 +2474,7 @@ class PromigenceClient {
|
|
|
2356
2474
|
}, opts);
|
|
2357
2475
|
},
|
|
2358
2476
|
putFrom: async (sandboxId, path, source, opts = {}) => {
|
|
2359
|
-
const
|
|
2360
|
-
const base = `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}${mode}`;
|
|
2477
|
+
const url = `/sandboxes/${encodeURIComponent(sandboxId)}/files`;
|
|
2361
2478
|
let sent = 0;
|
|
2362
2479
|
let chunks = 0;
|
|
2363
2480
|
let last;
|
|
@@ -2366,7 +2483,12 @@ class PromigenceClient {
|
|
|
2366
2483
|
if (slice.length === 0 && sent < source.size) {
|
|
2367
2484
|
throw new core_js_1.InvalidArgumentError(`the source of ${path} ended at ${sent} of ${source.size} bytes`, "The file shrank while it was being sent; send it again once nothing is writing it.");
|
|
2368
2485
|
}
|
|
2369
|
-
const
|
|
2486
|
+
const fields = [["path", path]];
|
|
2487
|
+
if (opts.mode !== undefined)
|
|
2488
|
+
fields.push(["mode", opts.mode.toString(8)]);
|
|
2489
|
+
if (sent > 0)
|
|
2490
|
+
fields.push(["offset", String(sent)]);
|
|
2491
|
+
const raw = await this.bytes("PUT", url, fileForm(fields, slice), opts);
|
|
2370
2492
|
last = JSON.parse(new TextDecoder().decode(raw));
|
|
2371
2493
|
sent += slice.length;
|
|
2372
2494
|
chunks += 1;
|
|
@@ -2375,8 +2497,8 @@ class PromigenceClient {
|
|
|
2375
2497
|
},
|
|
2376
2498
|
delete: (sandboxId, path, opts = {}) => this.request({
|
|
2377
2499
|
method: "DELETE",
|
|
2378
|
-
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files
|
|
2379
|
-
|
|
2500
|
+
path: `/sandboxes/${encodeURIComponent(sandboxId)}/files`,
|
|
2501
|
+
body: { path, ...(opts.recursive ? { recursive: true } : {}) },
|
|
2380
2502
|
}),
|
|
2381
2503
|
},
|
|
2382
2504
|
output: async (execId, opts = {}) => {
|
|
@@ -2555,10 +2677,10 @@ class PromigenceClient {
|
|
|
2555
2677
|
throw new core_js_1.InvalidArgumentError("snapshots.delete needs a snapshot", "Pass a snap_ id, an alias, or alias@commit7.");
|
|
2556
2678
|
return this.request({ method: "DELETE", path: `/snapshots/${seg(idOrAlias)}` });
|
|
2557
2679
|
},
|
|
2558
|
-
alias: (idOrAlias, alias) => this.request({
|
|
2680
|
+
alias: (idOrAlias, alias, opts = {}) => this.request({
|
|
2559
2681
|
method: "PUT",
|
|
2560
2682
|
path: `/snapshots/${seg(idOrAlias)}/alias`,
|
|
2561
|
-
body: { alias },
|
|
2683
|
+
body: { alias, ...(alias !== null && opts.moveAlias ? { move_alias: true } : {}) },
|
|
2562
2684
|
}),
|
|
2563
2685
|
waitBuild: async (buildId, opts = {}) => {
|
|
2564
2686
|
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");
|
|
@@ -16,9 +16,11 @@ class CommandTimeoutError extends core_js_1.CommandExitError {
|
|
|
16
16
|
code;
|
|
17
17
|
fix;
|
|
18
18
|
guide;
|
|
19
|
+
docs;
|
|
19
20
|
constructor(result, timeoutMs) {
|
|
20
21
|
super(result);
|
|
21
22
|
this.code = "task_timeout";
|
|
23
|
+
this.docs = (0, core_js_1.errorsDocsFor)(this.code);
|
|
22
24
|
this.timeoutMs = timeoutMs;
|
|
23
25
|
this.message = timeoutMs === undefined
|
|
24
26
|
? `command timed out (killed at its timeout, exit ${result.exitCode})`
|
package/dist/cjs/context.d.ts
CHANGED
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* - Empty directories and permission bits travel as they are on disk; a set-user-ID or
|
|
16
16
|
* set-group-ID bit is cleared, with a note.
|
|
17
17
|
* - Files that look like credentials (`.env`, private keys, cloud credentials, ...) are refused,
|
|
18
|
-
* by name, unless `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* by name, unless `allowSecretFiles` names them (each one) or `allowSecrets` says otherwise. The
|
|
19
|
+
* check goes by file name and reads only two kinds of file, to clear the plain false
|
|
20
|
+
* positives: a `.pem` holding certificates or public keys and no private key, and an `.npmrc`
|
|
21
|
+
* with no token or password in it. It is best-effort, so look over the list (`listContext`)
|
|
22
|
+
* before you send a directory.
|
|
21
23
|
* - Symbolic links travel as links, never as the file they point at.
|
|
22
24
|
*/
|
|
23
25
|
/** The prefix a packed directory travels under in `dockerfile.context`. */
|
|
@@ -65,9 +67,17 @@ export declare function dockerignoreMatcher(text: string): {
|
|
|
65
67
|
/**
|
|
66
68
|
* Does this path look like a credential (`.env`, a private key, cloud or registry credentials, a
|
|
67
69
|
* token file, ...)? By name only: best-effort, it cannot see what a file holds. Templates such as
|
|
68
|
-
* `.env.example` are not.
|
|
70
|
+
* `.env.example` are not. [`secretLikeFile`] also reads the few files whose contents can clear them.
|
|
69
71
|
*/
|
|
70
72
|
export declare function secretLike(path: string): boolean;
|
|
73
|
+
/** The most of a file [`secretLikeFile`] reads; a larger one is judged by its name alone. */
|
|
74
|
+
export declare const CONTENT_CHECK_MAX_BYTES: number;
|
|
75
|
+
/**
|
|
76
|
+
* [`secretLike`] for a file on disk at `abs` (`rel` is its path in the upload): a name that looks
|
|
77
|
+
* like a credential, unless what the file holds clears it ([`clearedByContent`]). A link, a file
|
|
78
|
+
* over [`CONTENT_CHECK_MAX_BYTES`] and a file that cannot be read keep the name's verdict.
|
|
79
|
+
*/
|
|
80
|
+
export declare function secretLikeFile(rel: string, abs: string): boolean;
|
|
71
81
|
/** What a build context is packed from and sent with. */
|
|
72
82
|
export interface ContextOptions {
|
|
73
83
|
/**
|
|
@@ -84,6 +94,11 @@ export interface ContextOptions {
|
|
|
84
94
|
* nothing is sent.
|
|
85
95
|
*/
|
|
86
96
|
allowSecrets?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Send these files that look like credentials, and only these: paths inside the context,
|
|
99
|
+
* `/`-separated, as `listContext` names them. Every other one is still refused.
|
|
100
|
+
*/
|
|
101
|
+
allowSecretFiles?: readonly string[];
|
|
87
102
|
/** Called with each note: what was left out by default, or changed on the way. */
|
|
88
103
|
onNote?: (note: string) => void;
|
|
89
104
|
}
|