@promigence/sdk 0.1.0-beta.13 → 0.1.0-beta.15

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/client.js CHANGED
@@ -42,6 +42,26 @@ function bytesToB64(b) {
42
42
  function modeParam(mode) {
43
43
  return typeof mode === "number" ? mode.toString(8) : mode;
44
44
  }
45
+ function jsonBody(value) {
46
+ return { data: new TextEncoder().encode(JSON.stringify(value)), contentType: "application/json" };
47
+ }
48
+ function fileForm(fields, data) {
49
+ const boundary = `promigence-${globalThis.crypto.randomUUID().replace(/-/g, "")}`;
50
+ let head = "";
51
+ for (const [name, value] of fields) {
52
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
53
+ }
54
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\n`
55
+ + "Content-Type: application/octet-stream\r\n\r\n";
56
+ const enc = new TextEncoder();
57
+ const pre = enc.encode(head);
58
+ const post = enc.encode(`\r\n--${boundary}--\r\n`);
59
+ const out = new Uint8Array(pre.length + data.length + post.length);
60
+ out.set(pre, 0);
61
+ out.set(data, pre.length);
62
+ out.set(post, pre.length + data.length);
63
+ return { data: out, contentType: `multipart/form-data; boundary=${boundary}` };
64
+ }
45
65
  export function execOutputBytes(r, stream = "stdout") {
46
66
  const b64 = r[`${stream}_b64`];
47
67
  if (typeof b64 === "string")
@@ -71,6 +91,32 @@ function pageQuery(limit, max, cursor) {
71
91
  q.set("cursor", cursor);
72
92
  return q.toString();
73
93
  }
94
+ export const OUTPUT_RETENTION_MAX_DAYS = 30;
95
+ function projectUpdateBody(u) {
96
+ const body = {};
97
+ if (u.name !== undefined)
98
+ body["name"] = u.name;
99
+ if (u.outputRetentionDays !== undefined) {
100
+ const d = u.outputRetentionDays;
101
+ if (d !== null && (!Number.isInteger(d) || d < 1 || d > OUTPUT_RETENTION_MAX_DAYS)) {
102
+ throw new InvalidArgumentError(`outputRetentionDays must be a whole number from 1 to ${OUTPUT_RETENTION_MAX_DAYS}, or null, not ${String(d)}`, "Pass null for the organization's setting.");
103
+ }
104
+ body["output_retention_days"] = d;
105
+ }
106
+ if (u.failureCaptures !== undefined) {
107
+ if (u.failureCaptures !== null && typeof u.failureCaptures !== "boolean") {
108
+ throw new InvalidArgumentError("failureCaptures must be true, false or null", "Pass null for the organization's setting.");
109
+ }
110
+ if (Object.keys(body).length) {
111
+ throw new InvalidArgumentError("failureCaptures is set on its own", "Change it in a call of its own, without name or outputRetentionDays.");
112
+ }
113
+ body["failure_captures"] = u.failureCaptures;
114
+ }
115
+ if (!Object.keys(body).length) {
116
+ throw new InvalidArgumentError("projects.update needs something to change", "Pass name, outputRetentionDays or failureCaptures.");
117
+ }
118
+ return body;
119
+ }
74
120
  function expiryFields(o) {
75
121
  const body = {};
76
122
  if (o.expiresInDays !== undefined)
@@ -944,7 +990,8 @@ export class PromigenceClient {
944
990
  const idempotencyKey = opts.idempotencyKey ?? (keyedCreate ? newIdempotencyKey() : undefined);
945
991
  if (idempotencyKey)
946
992
  headers["idempotency-key"] = idempotencyKey;
947
- const replayable = opts.method === "GET" || opts.method === "DELETE" || (keyedCreate && Boolean(idempotencyKey));
993
+ const replayable = opts.method === "GET" || opts.method === "DELETE" || opts.replayable === true
994
+ || (keyedCreate && Boolean(idempotencyKey));
948
995
  const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;
949
996
  let dispatcher;
950
997
  let resentDelete = false;
@@ -1141,7 +1188,7 @@ export class PromigenceClient {
1141
1188
  }
1142
1189
  }
1143
1190
  }
1144
- async bytes(method, path, body, opts = {}) {
1191
+ async bytes(method, path, body, opts = {}, read = method === "GET") {
1145
1192
  const url = `${this.apiUrl}${path}`;
1146
1193
  const headers = {
1147
1194
  accept: "application/octet-stream, application/json",
@@ -1151,7 +1198,7 @@ export class PromigenceClient {
1151
1198
  if (this.caller)
1152
1199
  headers[CALLER_HEADER] = this.caller;
1153
1200
  if (body !== undefined)
1154
- headers["content-type"] = "application/octet-stream";
1201
+ headers["content-type"] = body.contentType;
1155
1202
  const signals = [AbortSignal.timeout(opts.timeoutMs ?? FILE_TIMEOUT_MS)];
1156
1203
  if (opts.signal)
1157
1204
  signals.push(opts.signal);
@@ -1165,7 +1212,7 @@ export class PromigenceClient {
1165
1212
  method,
1166
1213
  headers,
1167
1214
  signal,
1168
- ...(body === undefined ? {} : { body: body }),
1215
+ ...(body === undefined ? {} : { body: body.data }),
1169
1216
  ...(dispatcher === undefined ? {} : { dispatcher }),
1170
1217
  });
1171
1218
  }
@@ -1185,7 +1232,7 @@ export class PromigenceClient {
1185
1232
  }
1186
1233
  catch { }
1187
1234
  const retryAfterMs = parseRetryAfter(res.headers.get("retry-after"));
1188
- const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, method === "GET", opts.signal);
1235
+ const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, read, opts.signal);
1189
1236
  if (wait !== undefined) {
1190
1237
  this.stats.retries++;
1191
1238
  await sleep(wait);
@@ -1199,7 +1246,7 @@ export class PromigenceClient {
1199
1246
  });
1200
1247
  }
1201
1248
  }
1202
- async stream(path, onChunk, opts = {}) {
1249
+ async stream(method, path, onChunk, opts = {}, body) {
1203
1250
  const url = `${this.apiUrl}${path}`;
1204
1251
  const headers = {
1205
1252
  accept: "application/octet-stream, application/json",
@@ -1208,17 +1255,25 @@ export class PromigenceClient {
1208
1255
  };
1209
1256
  if (this.caller)
1210
1257
  headers[CALLER_HEADER] = this.caller;
1258
+ if (body !== undefined)
1259
+ headers["content-type"] = body.contentType;
1211
1260
  const signals = [AbortSignal.timeout(opts.timeoutMs ?? FILE_TIMEOUT_MS)];
1212
1261
  if (opts.signal)
1213
1262
  signals.push(opts.signal);
1214
1263
  const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1215
- const timedOut = () => new TimeoutError(`GET ${path} timed out after ${opts.timeoutMs ?? FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
1264
+ const timedOut = () => new TimeoutError(`${method} ${path} timed out after ${opts.timeoutMs ?? FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
1216
1265
  const dispatcher = this.guardDispatcher();
1217
1266
  for (let attempt = 0;; attempt++) {
1218
1267
  let res;
1219
1268
  this.stats.requests++;
1220
1269
  try {
1221
- res = await this.fetchImpl(url, { method: "GET", headers, signal, ...(dispatcher === undefined ? {} : { dispatcher }) });
1270
+ res = await this.fetchImpl(url, {
1271
+ method,
1272
+ headers,
1273
+ signal,
1274
+ ...(body === undefined ? {} : { body: body.data }),
1275
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1276
+ });
1222
1277
  }
1223
1278
  catch (err) {
1224
1279
  const e = err;
@@ -1267,7 +1322,7 @@ export class PromigenceClient {
1267
1322
  continue;
1268
1323
  }
1269
1324
  const code = payload.error?.code ?? codeForStatus(res.status);
1270
- throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from GET ${path}`, payload.error?.fix ?? "See the API response for details.", {
1325
+ throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
1271
1326
  status: res.status,
1272
1327
  ...bodyExtra(payload),
1273
1328
  ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
@@ -1346,6 +1401,29 @@ export class PromigenceClient {
1346
1401
  const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
1347
1402
  return pageOf(r, "entries", limit);
1348
1403
  },
1404
+ holds: () => this.request({ method: "GET", path: "/billing/holds" }),
1405
+ buyCredits: (amountUsd) => this.request({ method: "POST", path: "/billing/credits", body: { amount_usd: amountUsd } }),
1406
+ settleCredits: (sessionId) => {
1407
+ if (!sessionId?.trim())
1408
+ throw new InvalidArgumentError("billing.settleCredits needs a session id", "Pass the session_id buyCredits() answered.");
1409
+ return this.request({ method: "POST", path: `/billing/credits/${encodeURIComponent(sessionId)}/settle`, body: {} });
1410
+ },
1411
+ autoReload: () => this.request({ method: "GET", path: "/billing/auto-reload" }),
1412
+ setAutoReload: (opts) => this.request({
1413
+ method: "PUT",
1414
+ path: "/billing/auto-reload",
1415
+ body: {
1416
+ on: opts.on,
1417
+ ...(opts.thresholdUsd === undefined ? {} : { threshold_usd: opts.thresholdUsd }),
1418
+ ...(opts.amountUsd === undefined ? {} : { amount_usd: opts.amountUsd }),
1419
+ ...(opts.authorizationVersion === undefined ? {} : { authorization_version: opts.authorizationVersion }),
1420
+ },
1421
+ }),
1422
+ repayDispute: (disputeId) => {
1423
+ if (!disputeId?.trim())
1424
+ throw new InvalidArgumentError("billing.repayDispute needs a dispute id", "Pass the hold's dispute_id from billing.usage().");
1425
+ return this.request({ method: "POST", path: `/billing/disputes/${encodeURIComponent(disputeId)}/repay`, body: {} });
1426
+ },
1349
1427
  checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
1350
1428
  portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
1351
1429
  referrals: () => this.request({ method: "GET", path: "/referrals" }),
@@ -1404,6 +1482,9 @@ export class PromigenceClient {
1404
1482
  rename: (org, project, name) => this.request({
1405
1483
  method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: { name },
1406
1484
  }),
1485
+ update: async (org, project, update) => this.request({
1486
+ method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: projectUpdateBody(update),
1487
+ }),
1407
1488
  },
1408
1489
  };
1409
1490
  }
@@ -2152,6 +2233,7 @@ export class PromigenceClient {
2152
2233
  ...(opts.spendCap === undefined ? {} : { spend_cap: checkSpendCap(opts.spendCap) }),
2153
2234
  },
2154
2235
  }),
2236
+ resumePausedForCredits: () => this.request({ method: "POST", path: "/sandboxes/resume", body: { paused_for: "credits" } }),
2155
2237
  snapshot: (sandboxId, opts = {}) => this.request({
2156
2238
  method: "POST",
2157
2239
  path: `/sandboxes/${encodeURIComponent(sandboxId)}/snapshot`,
@@ -2222,38 +2304,42 @@ export class PromigenceClient {
2222
2304
  },
2223
2305
  files: {
2224
2306
  list: (sandboxId, path = "/", opts = {}) => {
2225
- const q = [`path=${encodeURIComponent(path)}`];
2307
+ const body = { path };
2226
2308
  if (opts.recursive)
2227
- q.push("recursive=1");
2309
+ body.recursive = true;
2228
2310
  if (opts.depth !== undefined)
2229
- q.push(`depth=${Math.max(0, Math.floor(opts.depth))}`);
2311
+ body.depth = Math.max(0, Math.floor(opts.depth));
2230
2312
  if (opts.limit !== undefined)
2231
- q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
2313
+ body.limit = Math.max(1, Math.floor(opts.limit));
2232
2314
  if (opts.hidden === false)
2233
- q.push("hidden=0");
2315
+ body.hidden = false;
2234
2316
  return this.request({
2235
- method: "GET",
2236
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list?${q.join("&")}`,
2317
+ method: "POST",
2318
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list`,
2319
+ body,
2320
+ replayable: true,
2237
2321
  });
2238
2322
  },
2239
2323
  search: (sandboxId, path, opts = {}) => {
2240
2324
  if (!opts.pattern && !opts.contains) {
2241
2325
  throw new InvalidArgumentError("files.search needs `pattern`, `contains`, or both", "Pass { pattern: '*.py' } for a name glob, { contains: 'TODO' } for text inside files.");
2242
2326
  }
2243
- const q = [`path=${encodeURIComponent(path)}`];
2327
+ const body = { path };
2244
2328
  if (opts.pattern)
2245
- q.push(`pattern=${encodeURIComponent(opts.pattern)}`);
2329
+ body.pattern = opts.pattern;
2246
2330
  if (opts.contains)
2247
- q.push(`contains=${encodeURIComponent(opts.contains)}`);
2331
+ body.contains = opts.contains;
2248
2332
  if (opts.ignoreCase)
2249
- q.push("ignore_case=1");
2333
+ body.ignore_case = true;
2250
2334
  if (opts.limit !== undefined)
2251
- q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
2335
+ body.limit = Math.max(1, Math.floor(opts.limit));
2252
2336
  if (opts.hidden === false)
2253
- q.push("hidden=0");
2337
+ body.hidden = false;
2254
2338
  return this.request({
2255
- method: "GET",
2256
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search?${q.join("&")}`,
2339
+ method: "POST",
2340
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search`,
2341
+ body,
2342
+ replayable: true,
2257
2343
  });
2258
2344
  },
2259
2345
  mkdir: (sandboxId, path, opts = {}) => this.request({
@@ -2310,11 +2396,13 @@ export class PromigenceClient {
2310
2396
  body: { archive, into, ...(opts.strip === undefined ? {} : { strip: Math.max(0, Math.floor(opts.strip)) }) },
2311
2397
  }),
2312
2398
  stat: (sandboxId, path) => this.request({
2313
- method: "GET",
2314
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat?path=${encodeURIComponent(path)}`,
2399
+ method: "POST",
2400
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat`,
2401
+ body: { path },
2402
+ replayable: true,
2315
2403
  }),
2316
- get: (sandboxId, path, opts = {}) => this.bytes("GET", `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, undefined, opts),
2317
- download: (sandboxId, path, onChunk, opts = {}) => this.stream(`/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, onChunk, opts),
2404
+ get: (sandboxId, path, opts = {}) => this.bytes("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, jsonBody({ path }), opts, true),
2405
+ download: (sandboxId, path, onChunk, opts = {}) => this.stream("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, onChunk, opts, jsonBody({ path })),
2318
2406
  text: async (sandboxId, path, opts = {}) => new TextDecoder().decode(await this.sandboxes.files.get(sandboxId, path, opts)),
2319
2407
  put: async (sandboxId, path, data, opts = {}) => {
2320
2408
  const body = typeof data === "string" ? new TextEncoder().encode(data) : data;
@@ -2324,8 +2412,7 @@ export class PromigenceClient {
2324
2412
  }, opts);
2325
2413
  },
2326
2414
  putFrom: async (sandboxId, path, source, opts = {}) => {
2327
- const mode = opts.mode === undefined ? "" : `&mode=${opts.mode.toString(8)}`;
2328
- const base = `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}${mode}`;
2415
+ const url = `/sandboxes/${encodeURIComponent(sandboxId)}/files`;
2329
2416
  let sent = 0;
2330
2417
  let chunks = 0;
2331
2418
  let last;
@@ -2334,7 +2421,12 @@ export class PromigenceClient {
2334
2421
  if (slice.length === 0 && sent < source.size) {
2335
2422
  throw new InvalidArgumentError(`the source of ${path} ended at ${sent} of ${source.size} bytes`, "The file shrank while it was being sent; send it again once nothing is writing it.");
2336
2423
  }
2337
- const raw = await this.bytes("PUT", sent === 0 ? base : `${base}&offset=${sent}`, slice, opts);
2424
+ const fields = [["path", path]];
2425
+ if (opts.mode !== undefined)
2426
+ fields.push(["mode", opts.mode.toString(8)]);
2427
+ if (sent > 0)
2428
+ fields.push(["offset", String(sent)]);
2429
+ const raw = await this.bytes("PUT", url, fileForm(fields, slice), opts);
2338
2430
  last = JSON.parse(new TextDecoder().decode(raw));
2339
2431
  sent += slice.length;
2340
2432
  chunks += 1;
@@ -2343,8 +2435,8 @@ export class PromigenceClient {
2343
2435
  },
2344
2436
  delete: (sandboxId, path, opts = {}) => this.request({
2345
2437
  method: "DELETE",
2346
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`
2347
- + (opts.recursive ? "&recursive=1" : ""),
2438
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files`,
2439
+ body: { path, ...(opts.recursive ? { recursive: true } : {}) },
2348
2440
  }),
2349
2441
  },
2350
2442
  output: async (execId, opts = {}) => {
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.13";
2
+ export declare const VERSION = "0.1.0-beta.15";
3
3
  export declare const DOCS_URL = "https://promigence.ai";
4
4
  /** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
5
5
  export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { hintArg } from "./hint.js";
2
- export const VERSION = "0.1.0-beta.13";
2
+ export const VERSION = "0.1.0-beta.15";
3
3
  export const DOCS_URL = "https://promigence.ai";
4
4
  export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
5
5
  export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
@@ -247,6 +247,8 @@ export const EXIT_BY_CODE = {
247
247
  project_cap_reached: 5,
248
248
  spend_limit_reached: 5,
249
249
  billing_hold: 5,
250
+ credits_exhausted: 5, purchase_limit: 5, purchases_blocked: 5,
251
+ card_needed: 5, auto_reload_unavailable: 5,
250
252
  tier_over_plan: 2,
251
253
  network_host_refused: 9, network_policy_unsupported: 3,
252
254
  secrets_in_snapshot: 2, secrets_unavailable: 3,
package/dist/http2.d.ts CHANGED
@@ -189,6 +189,7 @@ export declare const __http2Internals: {
189
189
  closedReason: string;
190
190
  messages: number;
191
191
  posts: number;
192
+ startedAt: number;
192
193
  } | undefined;
193
194
  /** The transport thread's number of connections and spacing between new requests on one, for an environment (`threadKnobs`). */
194
195
  threadKnobs(env: NodeJS.ProcessEnv): {