@promigence/sdk 0.1.0-beta.7 → 0.1.0-beta.8

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
@@ -2,7 +2,7 @@ import { readFileSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { inspect } from "node:util";
4
4
  import { dirname, join } from "node:path";
5
- import { ApiError, AuthError, EnvFailedError, PromigenceError, InvalidArgumentError, NotFoundError, NotReleasedError, NotVerifiedError, RateLimitError, SpendCapError, TimeoutError, UnreachableError, DEFAULT_API_URL, ERRORS_DOCS_URL, FILE_PUT_CHUNK_BYTES, MAX_OUTPUT_BYTES_DEFAULT, VERSION, WAIT_TIMEOUT_MS, PreviewsUnavailableError, exitForCode, previewsOff, withPreviewsFix, } from "./core.js";
5
+ import { ApiError, AuthError, EnvFailedError, PromigenceError, InvalidArgumentError, NotFoundError, NotReleasedError, NotVerifiedError, RateLimitError, ServiceRestartError, SpendCapError, TimeoutError, UnreachableError, DEFAULT_API_URL, ERRORS_DOCS_URL, FILE_PUT_CHUNK_BYTES, MAX_OUTPUT_BYTES_DEFAULT, VERSION, WAIT_TIMEOUT_MS, PreviewsUnavailableError, exitForCode, previewsOff, withPreviewsFix, } from "./core.js";
6
6
  import { buildBundle } from "./bundle.js";
7
7
  import { keychainReadCached, keychainStore, storeLabel } from "./credstore.js";
8
8
  import { CALLER_HEADER, resolveCaller, userAgentWithCaller } from "./caller.js";
@@ -135,7 +135,7 @@ function decodeCredentials(buf) {
135
135
  return buf.subarray(3).toString("utf8");
136
136
  return buf.toString("utf8");
137
137
  }
138
- export function storedCredentials(env = process.env) {
138
+ export function storedCredentials(env = process.env, platform = process.platform) {
139
139
  const path = credentialsPath(env);
140
140
  let present = true;
141
141
  try {
@@ -145,7 +145,7 @@ export function storedCredentials(env = process.env) {
145
145
  present = e.code !== "ENOENT";
146
146
  }
147
147
  if (!present) {
148
- const store = keychainStore(env);
148
+ const store = keychainStore(env, platform);
149
149
  if (store === undefined)
150
150
  return undefined;
151
151
  const r = keychainReadCached(store, path, env);
@@ -259,8 +259,6 @@ export function keyOrigin(env = process.env) {
259
259
  const stored = readCredentialsWithStore(env);
260
260
  if (stored.creds.api_key)
261
261
  return stored.store === "macos-keychain" ? "macOS Keychain" : stored.store === "secret-service" ? "Secret Service keyring" : "credentials file";
262
- if (compatApiKey(env["E2B_API_KEY"]))
263
- return "E2B_API_KEY (compat)";
264
262
  return undefined;
265
263
  }
266
264
  function resolveCredentials(opts, env = process.env) {
@@ -269,17 +267,19 @@ function resolveCredentials(opts, env = process.env) {
269
267
  const t = u.replace(/\/+$/, "");
270
268
  return /^https?:\/\/[^/?#]+$/i.test(t) ? `${t}/v1` : t;
271
269
  };
272
- const { creds, store } = readCredentialsWithStore(env);
273
270
  const envKey = set(env["PROMIGENCE_API_KEY"]?.trim());
274
271
  const envUrl = set(env["PROMIGENCE_API_URL"]);
275
272
  const optKey = opts.apiKey?.trim();
273
+ const given = optKey !== undefined || envKey !== undefined;
274
+ const { creds, store } = given
275
+ ? { creds: {}, store: undefined }
276
+ : readCredentialsWithStore(env);
276
277
  const source = optKey !== undefined ? "opts" : envKey !== undefined ? "env" : creds.api_key !== undefined ? "file" : undefined;
277
278
  const ours = optKey ?? envKey ?? creds.api_key;
278
- const compat = ours === undefined ? compatApiKey(env["E2B_API_KEY"]) : undefined;
279
- const compatUrl = compat === undefined
280
- ? undefined
281
- : apiUrlFromCompatUrl(env["E2B_API_URL"]) ?? apiUrlFromCompatDomain(env["E2B_DOMAIN"]);
282
- const fromFile = trim(creds.api_url ?? compatUrl ?? DEFAULT_API_URL);
279
+ const fallback = ours === undefined ? opts.fallbackKey : undefined;
280
+ const fallbackKey = set(fallback?.apiKey.trim());
281
+ const fallbackUrl = fallbackKey === undefined ? undefined : set(fallback?.apiUrl);
282
+ const fromFile = trim(creds.api_url ?? fallbackUrl ?? DEFAULT_API_URL);
283
283
  const fromEnv = envUrl !== undefined ? trim(envUrl) : fromFile;
284
284
  const apiUrl = opts.apiUrl !== undefined ? trim(opts.apiUrl) : fromEnv;
285
285
  const paired = source === "file" ? fromFile : source === "env" ? fromEnv : apiUrl;
@@ -291,10 +291,10 @@ function resolveCredentials(opts, env = process.env) {
291
291
  : "Pass apiKey together with apiUrl, or set PROMIGENCE_API_URL to that URL instead of passing apiUrl.");
292
292
  return { apiKey: undefined, apiUrl, refusal };
293
293
  }
294
- const key = ours ?? compat;
294
+ const key = ours ?? fallbackKey;
295
295
  if (key) {
296
296
  const from = source === "opts" ? "the apiKey option" : source === "env" ? "PROMIGENCE_API_KEY"
297
- : source === "file" ? storedName(store, env) : "E2B_API_KEY";
297
+ : source === "file" ? storedName(store, env) : fallback.from;
298
298
  const refusal = keyRefusal(key, from);
299
299
  if (refusal)
300
300
  return { apiKey: undefined, apiUrl, refusal };
@@ -652,6 +652,7 @@ export function errorFor(code, message, fix, extra = {}) {
652
652
  case "invalid_argument":
653
653
  return attach(new InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
654
654
  case "not_released": return attach(new NotReleasedError(message));
655
+ case "service_restart": return attach(new ServiceRestartError(message, fix));
655
656
  default:
656
657
  if (ENV_CODES.has(code) || extra.category === "env")
657
658
  return attach(new EnvFailedError(code, message, fix));
@@ -1258,6 +1259,14 @@ export class PromigenceClient {
1258
1259
  const r = await this.request({ method: "GET", path: `/me/ledger?${pageQuery(limit, 1000, opts.cursor)}` });
1259
1260
  return pageOf(r, "entries", limit);
1260
1261
  },
1262
+ historyPage: async (opts = {}) => {
1263
+ const limit = opts.limit ?? 50;
1264
+ const q = new URLSearchParams(pageQuery(limit, 100, opts.cursor));
1265
+ if (opts.kind !== undefined && opts.kind !== "all")
1266
+ q.set("kind", opts.kind);
1267
+ const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
1268
+ return pageOf(r, "entries", limit);
1269
+ },
1261
1270
  checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
1262
1271
  portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
1263
1272
  referrals: () => this.request({ method: "GET", path: "/referrals" }),
@@ -1752,6 +1761,10 @@ export class PromigenceClient {
1752
1761
  on.heartbeat?.();
1753
1762
  continue;
1754
1763
  }
1764
+ if (event === "reconnect") {
1765
+ reader.cancel().catch(() => { });
1766
+ throw new ServiceRestartError(`a service restart stopped the command on sandbox ${sandboxId} while its output was streaming`, "Not billed: the time the command ran is credited back. The sandbox kept running; the command may have done part of its work before it stopped: run it again if that is safe.");
1767
+ }
1755
1768
  let parsed = {};
1756
1769
  try {
1757
1770
  parsed = JSON.parse(data);
@@ -1987,6 +2000,9 @@ export class PromigenceClient {
1987
2000
  ...(opts.onData ? { onData: opts.onData } : {}),
1988
2001
  ...(opts.onExit ? { onExit: opts.onExit } : {}),
1989
2002
  ...(opts.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
2003
+ ...(opts.resume === undefined ? {} : { resume: opts.resume }),
2004
+ ...(opts.reconnect === undefined ? {} : { reconnect: opts.reconnect }),
2005
+ ...(opts.onReconnected ? { onReconnected: opts.onReconnected } : {}),
1990
2006
  refuse: (status, text, retryAfter) => {
1991
2007
  let payload = {};
1992
2008
  try {
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.7";
2
+ export declare const VERSION = "0.1.0-beta.8";
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";
@@ -190,6 +190,15 @@ export declare class TimeoutError extends PromigenceError {
190
190
  runId?: string;
191
191
  });
192
192
  }
193
+ /**
194
+ * A service update may stop a command that was running while its output was streaming to you
195
+ * (`execStream`, `commands.run` with output callbacks); the time it ran is credited back, and the
196
+ * sandbox keeps running. `retryable` is `false`: the command may have done part of its work before
197
+ * it stopped, so run it again only if that is safe. exit 3
198
+ */
199
+ export declare class ServiceRestartError extends PromigenceError {
200
+ constructor(message: string, fix: string);
201
+ }
193
202
  /** An API error with no more specific class (5xx, unknown code). exit from the table. */
194
203
  export declare class ApiError extends PromigenceError {
195
204
  }
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { hintArg } from "./hint.js";
2
- export const VERSION = "0.1.0-beta.7";
2
+ export const VERSION = "0.1.0-beta.8";
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";
@@ -194,6 +194,11 @@ export class TimeoutError extends PromigenceError {
194
194
  this.runId = ids.runId;
195
195
  }
196
196
  }
197
+ export class ServiceRestartError extends PromigenceError {
198
+ constructor(message, fix) {
199
+ super({ code: "service_restart", message, fix, exitCode: 3, category: "env", retryable: false });
200
+ }
201
+ }
197
202
  export class ApiError extends PromigenceError {
198
203
  }
199
204
  export class NotReleasedError extends PromigenceError {
@@ -210,6 +215,7 @@ export const EXIT_BY_CODE = {
210
215
  invalid_argument: 2, unsupported: 2, not_released: 1, task_failed: 4,
211
216
  env_failed: 3, snapshot_rejected: 6, build_failed: 3, snapshot_failed: 4,
212
217
  not_connected: 7,
218
+ service_restart: 3,
213
219
  fork_failed: 3, start_timeout: 3, readiness_failed: 3, verify_too_slow: 3,
214
220
  verify_each_failed: 3, toolchain_mismatch: 3, sandbox_unresponsive: 3, oom_within_tier: 3,
215
221
  disk_full_within_tier: 3, sandbox_interrupted: 3, integrity_mismatch: 3, egress_unavailable: 3,
@@ -236,10 +242,11 @@ export const EXIT_BY_CODE = {
236
242
  build_timeout: 4,
237
243
  image_unresolvable: 2,
238
244
  registry_unavailable: 3,
239
- trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached: 10,
245
+ trial_credits_exhausted: 5, card_required: 5, credit_unclaimed: 5, concurrency_limit: 5, egress_cap_reached: 5,
240
246
  billing_unavailable: 5,
241
247
  project_cap_reached: 5,
242
248
  spend_limit_reached: 5,
249
+ billing_hold: 5,
243
250
  tier_over_plan: 2,
244
251
  network_host_refused: 9, network_policy_unsupported: 3,
245
252
  secrets_in_snapshot: 2, secrets_unavailable: 3,
@@ -3,7 +3,8 @@
3
3
  * the credentials file (`~/.config/promigence/credentials`, 0600). The CLI writes it
4
4
  * (`promigence login`, `promigence auth login`, `promigence logout`); the CLI and both SDKs read it.
5
5
  *
6
- * macOS the login Keychain, through `security` (when HOME holds one)
6
+ * macOS the login Keychain, through `security` (when the user's home holds one: HOME,
7
+ * or the account's own home when the environment has no HOME)
7
8
  * Linux and BSD the Secret Service (GNOME Keyring, KWallet), through `secret-tool`, when a
8
9
  * desktop session bus is there (a CI job or an SSH shell has none: the file)
9
10
  * Windows the file: no tool that ships with Windows can read a stored secret back
@@ -34,7 +35,7 @@ export declare function storeLabel(store: CredentialStore, path?: string): strin
34
35
  * The keychain this process stores a sign-in in, or undefined for the file. `auto` is decided by
35
36
  * the platform and by whether its tool (and, for the Secret Service, a session bus) is there.
36
37
  */
37
- export declare function keychainStore(env?: NodeJS.ProcessEnv, platform?: string): KeychainStore | undefined;
38
+ export declare function keychainStore(env?: NodeJS.ProcessEnv, platform?: string, home?: () => string): KeychainStore | undefined;
38
39
  /**
39
40
  * The keychains a sign-out (or a sign-in that lands in the file) must clear for this path: the one
40
41
  * in use, and the platform's own even when `PROMIGENCE_CREDENTIAL_STORE=file` (an item a previous
package/dist/credstore.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { accessSync, constants, existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
3
4
  import { delimiter, join } from "node:path";
4
5
  export const KEYCHAIN_SERVICE = "promigence-cli";
5
6
  const KEYCHAIN_LABEL = "Promigence CLI credentials";
@@ -30,8 +31,19 @@ function sessionBus(env) {
30
31
  const run = env["XDG_RUNTIME_DIR"];
31
32
  return Boolean(run) && existsSync(join(run, "bus"));
32
33
  }
34
+ function userHome(env, home = homedir) {
35
+ const h = env["HOME"];
36
+ if (h)
37
+ return h;
38
+ try {
39
+ return home() || undefined;
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
33
45
  const warnedSetting = new Set();
34
- export function keychainStore(env = process.env, platform = process.platform) {
46
+ export function keychainStore(env = process.env, platform = process.platform, home = homedir) {
35
47
  const raw = (env["PROMIGENCE_CREDENTIAL_STORE"] ?? "").trim().toLowerCase();
36
48
  switch (raw) {
37
49
  case "file": return undefined;
@@ -46,8 +58,8 @@ export function keychainStore(env = process.env, platform = process.platform) {
46
58
  }
47
59
  }
48
60
  if (platform === "darwin") {
49
- const home = env["HOME"];
50
- return home && existsSync(join(home, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
61
+ const h = userHome(env, home);
62
+ return h && existsSync(join(h, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
51
63
  }
52
64
  if (platform === "win32")
53
65
  return undefined;
@@ -94,8 +106,9 @@ function runTool(store, args, env, input, timeout) {
94
106
  const bin = onPath(toolName(store), env);
95
107
  if (bin === undefined)
96
108
  throw Object.assign(new Error(`${toolName(store)} not found`), { code: "ENOENT" });
109
+ const h = store === "macos-keychain" && !env["HOME"] ? userHome(env) : undefined;
97
110
  return execFileSync(bin, args, {
98
- encoding: "utf8", timeout, env,
111
+ encoding: "utf8", timeout, env: h ? { ...env, HOME: h } : env,
99
112
  ...(input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input, stdio: ["pipe", "pipe", "pipe"] }),
100
113
  });
101
114
  }
@@ -243,6 +243,36 @@ export interface BackgroundExecList {
243
243
  execs: Array<BackgroundExec>;
244
244
  sandbox_id: string;
245
245
  }
246
+ /**
247
+ * `GET /v1/billing/history` — your billing history, newest first: every credit you received and
248
+ * every payment, one page at a time.
249
+ */
250
+ export interface BillingHistory {
251
+ entries: Array<BillingHistoryEntry>;
252
+ /**
253
+ * Pass as `cursor` for the next, older page; `null` on the last page.
254
+ */
255
+ next_cursor?: string | null;
256
+ }
257
+ /**
258
+ * One entry of your billing history: a credit you received or a payment. Exactly one of
259
+ * `credit` and `payment` is set.
260
+ */
261
+ export interface BillingHistoryEntry {
262
+ /**
263
+ * When it happened, RFC 3339: when a credit was added, when a payment was made, failed or
264
+ * was refunded.
265
+ */
266
+ at: string;
267
+ /**
268
+ * A credit you received; `null` for a payment.
269
+ */
270
+ credit?: CreditGrant | null;
271
+ /**
272
+ * A payment; `null` for a credit.
273
+ */
274
+ payment?: Payment | null;
275
+ }
246
276
  /**
247
277
  * The billing period `usage` covers.
248
278
  */
@@ -261,9 +291,9 @@ export interface BillingPeriod {
261
291
  start: string;
262
292
  }
263
293
  /**
264
- * `GET /v1/billing` — plan, card on file, this month's usage, and the upcoming invoice
265
- * preview. Never fails because the payment provider is slow: the estimate is the only part
266
- * that asks it, and its failure is a field, not a status.
294
+ * `GET /v1/billing` — plan, card on file, this month's usage, and the upcoming invoice preview.
295
+ * Never fails because the payment provider is slow: the estimate is the only part that asks it,
296
+ * and its failure is a field, not a status.
267
297
  */
268
298
  export interface BillingStatus {
269
299
  /**
@@ -274,11 +304,22 @@ export interface BillingStatus {
274
304
  * `null` while no card is on file.
275
305
  */
276
306
  card?: CardOnFile | null;
307
+ /**
308
+ * A sentence to show beside the card when it does not unlock paid limits for this
309
+ * organization (it already pays for another one, or it is being confirmed with a $1 charge, or
310
+ * that charge did not go through); `null` otherwise.
311
+ */
312
+ card_note?: string | null;
277
313
  card_on_file: boolean;
278
314
  /**
279
315
  * True once a billing customer exists for the organisation.
280
316
  */
281
317
  customer: boolean;
318
+ /**
319
+ * The $1 that confirms the payment method on file when it has no stable payer (Apple Pay,
320
+ * Google Pay, Amazon Pay); `null` when it needed none.
321
+ */
322
+ method_check?: MethodCheckView | null;
282
323
  notice?: TrialNotice | null;
283
324
  org_id: string;
284
325
  period: BillingPeriod;
@@ -830,7 +871,7 @@ export interface CliTokenCreate {
830
871
  /**
831
872
  * Every error and failure code the API returns.
832
873
  */
833
- export type Code = "fork_failed" | "start_timeout" | "readiness_failed" | "verify_too_slow" | "verify_each_failed" | "sandbox_unresponsive" | "oom_within_tier" | "disk_full_within_tier" | "sandbox_interrupted" | "integrity_mismatch" | "egress_unavailable" | "service_timeout" | "capacity_busy" | "verify_did_not_cover" | "nonzero_exit" | "task_timeout" | "oom_over_tier" | "disk_over_tier" | "cap_killed" | "cap_exceeded" | "aborted_by_client" | "expired" | "not_started" | "superseded" | "usage" | "internal" | "auth" | "not_found" | "unreachable" | "rate_limited" | "timeout" | "not_verified" | "enoent" | "trial_credits_exhausted" | "card_required" | "concurrency_limit" | "egress_cap_reached" | "network_policy_unsupported" | "network_host_refused" | "toolchain_mismatch" | "resume_unavailable" | "sandbox_unreachable" | "region_at_capacity" | "tier_unavailable" | "snapshot_unavailable" | "capacity_limit" | "build_timeout" | "builder_unavailable" | "builder_busy" | "overloaded" | "preempted" | "grader_failed" | "grade_not_passed" | "shutdown_by_task" | "modified_by_task" | "cap_paused" | "credits_paused" | "stopped_by_support" | "forbidden" | "snapshot_incompatible" | "storage_limit_reached" | "dockerfile_build_failed" | "image_unresolvable" | "registry_unavailable" | "credit_unclaimed" | "billing_unavailable" | "project_cap_reached" | "spend_limit_reached" | "tier_over_plan" | "secrets_in_snapshot" | "secrets_unavailable" | "secret_binding_unsupported" | "idempotency_key_reused" | "idempotency_key_in_use" | "repo_credentials_not_supported" | "account_exists" | "invite_invalid" | "invite_required" | "key_expired" | "account_blocked" | "setup_required";
874
+ export type Code = "fork_failed" | "start_timeout" | "readiness_failed" | "verify_too_slow" | "verify_each_failed" | "sandbox_unresponsive" | "oom_within_tier" | "disk_full_within_tier" | "sandbox_interrupted" | "integrity_mismatch" | "egress_unavailable" | "service_timeout" | "capacity_busy" | "verify_did_not_cover" | "nonzero_exit" | "task_timeout" | "oom_over_tier" | "disk_over_tier" | "cap_killed" | "cap_exceeded" | "aborted_by_client" | "expired" | "not_started" | "superseded" | "usage" | "internal" | "auth" | "not_found" | "unreachable" | "rate_limited" | "timeout" | "not_verified" | "enoent" | "trial_credits_exhausted" | "card_required" | "concurrency_limit" | "egress_cap_reached" | "network_policy_unsupported" | "network_host_refused" | "toolchain_mismatch" | "resume_unavailable" | "sandbox_unreachable" | "region_at_capacity" | "tier_unavailable" | "snapshot_unavailable" | "capacity_limit" | "build_timeout" | "builder_unavailable" | "builder_busy" | "overloaded" | "preempted" | "grader_failed" | "grade_not_passed" | "shutdown_by_task" | "modified_by_task" | "cap_paused" | "credits_paused" | "stopped_by_support" | "forbidden" | "snapshot_incompatible" | "storage_limit_reached" | "dockerfile_build_failed" | "image_unresolvable" | "registry_unavailable" | "credit_unclaimed" | "billing_unavailable" | "project_cap_reached" | "spend_limit_reached" | "billing_hold" | "tier_over_plan" | "secrets_in_snapshot" | "secrets_unavailable" | "secret_binding_unsupported" | "idempotency_key_reused" | "idempotency_key_in_use" | "repo_credentials_not_supported" | "account_exists" | "invite_invalid" | "invite_required" | "key_expired" | "account_blocked" | "setup_required";
834
875
  export interface Concurrency {
835
876
  }
836
877
  /**
@@ -916,6 +957,41 @@ export interface CreditClaim {
916
957
  terms_version?: string | null;
917
958
  token?: string | null;
918
959
  }
960
+ /**
961
+ * A credit your organization received, and what is left of it.
962
+ */
963
+ export interface CreditGrant {
964
+ /**
965
+ * When it expires, RFC 3339; `null` when it never expires.
966
+ */
967
+ expires_at?: string | null;
968
+ /**
969
+ * When it was added, RFC 3339.
970
+ */
971
+ granted_at: string;
972
+ /**
973
+ * USD.
974
+ */
975
+ granted_usd: number;
976
+ /**
977
+ * What it is: `free_credit` (the free credit), `card_bonus` (credit for adding a card),
978
+ * `referral` (a referral reward), `plan` (usage included with a paid plan),
979
+ * `from_promigence` (credit Promigence added to your account), `service_restart` (credit
980
+ * for a command a service restart stopped), `confirmation_credit` (a $1 charge that
981
+ * confirmed a payment method, added back as credit), or `other`.
982
+ */
983
+ kind: string;
984
+ /**
985
+ * What is left of it, USD: 0 once it is used, expired or taken back. Credit that expires
986
+ * soonest is used first, and credit that never expires last.
987
+ */
988
+ remaining_usd: number;
989
+ /**
990
+ * `active` (some of it is left), `used` (all of it was used), `expired` (it expired before
991
+ * all of it was used), or `taken_back`.
992
+ */
993
+ status: string;
994
+ }
919
995
  /**
920
996
  * The account's free credit (`GET /v1/credits/offer`, and the answer to a claim). The credit is
921
997
  * claimed in the browser, where the terms are accepted: `POST /v1/credits/claim-link` makes the
@@ -942,6 +1018,11 @@ export interface CreditOffer {
942
1018
  * One sentence for a person.
943
1019
  */
944
1020
  message: string;
1021
+ /**
1022
+ * USD charged to confirm a payment method with no stable payer when it is added (Apple Pay,
1023
+ * Google Pay, Amazon Pay): it is added to your credit, and the card bonus comes with it.
1024
+ */
1025
+ method_check_usd?: number;
945
1026
  /**
946
1027
  * `claimable`, `claimed`, `pending_review`, `needs_card`, `not_eligible`, `offer_ended`,
947
1028
  * `denied` or `none`.
@@ -1075,7 +1156,7 @@ export interface ErrorBody {
1075
1156
  /**
1076
1157
  * Every error code the API returns. `category` decides whether the failure is billed: `env` is ours (not billed before hand-over; after it, the time since the sandbox's last snapshot is credited back, up to 24 hours, when our side shows the cause), `task` is your command and is billed. The full table is `x-promigence-error-codes` at the document root.
1077
1158
  */
1078
- export type ErrorCode = "fork_failed" | "start_timeout" | "readiness_failed" | "verify_too_slow" | "verify_each_failed" | "toolchain_mismatch" | "sandbox_unresponsive" | "oom_within_tier" | "disk_full_within_tier" | "sandbox_interrupted" | "integrity_mismatch" | "egress_unavailable" | "service_timeout" | "preempted" | "verify_did_not_cover" | "grader_failed" | "nonzero_exit" | "grade_not_passed" | "task_timeout" | "oom_over_tier" | "disk_over_tier" | "shutdown_by_task" | "modified_by_task" | "cap_killed" | "cap_exceeded" | "cap_paused" | "credits_paused" | "aborted_by_client" | "stopped_by_support" | "expired" | "not_started" | "superseded" | "usage" | "internal" | "auth" | "forbidden" | "not_found" | "unreachable" | "rate_limited" | "timeout" | "not_verified" | "snapshot_incompatible" | "storage_limit_reached" | "resume_unavailable" | "sandbox_unreachable" | "capacity_busy" | "region_at_capacity" | "builder_unavailable" | "builder_busy" | "overloaded" | "tier_unavailable" | "snapshot_unavailable" | "capacity_limit" | "build_timeout" | "dockerfile_build_failed" | "image_unresolvable" | "registry_unavailable" | "enoent" | "trial_credits_exhausted" | "credit_unclaimed" | "setup_required" | "card_required" | "billing_unavailable" | "concurrency_limit" | "egress_cap_reached" | "project_cap_reached" | "spend_limit_reached" | "tier_over_plan" | "network_host_refused" | "network_policy_unsupported" | "secrets_in_snapshot" | "secrets_unavailable" | "secret_binding_unsupported" | "idempotency_key_reused" | "idempotency_key_in_use" | "repo_credentials_not_supported" | "account_exists" | "invite_invalid" | "invite_required" | "key_expired" | "account_blocked";
1159
+ export type ErrorCode = "fork_failed" | "start_timeout" | "readiness_failed" | "verify_too_slow" | "verify_each_failed" | "toolchain_mismatch" | "sandbox_unresponsive" | "oom_within_tier" | "disk_full_within_tier" | "sandbox_interrupted" | "integrity_mismatch" | "egress_unavailable" | "service_timeout" | "preempted" | "verify_did_not_cover" | "grader_failed" | "nonzero_exit" | "grade_not_passed" | "task_timeout" | "oom_over_tier" | "disk_over_tier" | "shutdown_by_task" | "modified_by_task" | "cap_killed" | "cap_exceeded" | "cap_paused" | "credits_paused" | "aborted_by_client" | "stopped_by_support" | "expired" | "not_started" | "superseded" | "usage" | "internal" | "auth" | "forbidden" | "not_found" | "unreachable" | "rate_limited" | "timeout" | "not_verified" | "snapshot_incompatible" | "storage_limit_reached" | "resume_unavailable" | "sandbox_unreachable" | "capacity_busy" | "region_at_capacity" | "builder_unavailable" | "builder_busy" | "overloaded" | "tier_unavailable" | "snapshot_unavailable" | "capacity_limit" | "build_timeout" | "dockerfile_build_failed" | "image_unresolvable" | "registry_unavailable" | "enoent" | "trial_credits_exhausted" | "credit_unclaimed" | "setup_required" | "card_required" | "billing_unavailable" | "concurrency_limit" | "egress_cap_reached" | "project_cap_reached" | "spend_limit_reached" | "billing_hold" | "tier_over_plan" | "network_host_refused" | "network_policy_unsupported" | "secrets_in_snapshot" | "secrets_unavailable" | "secret_binding_unsupported" | "idempotency_key_reused" | "idempotency_key_in_use" | "repo_credentials_not_supported" | "account_exists" | "invite_invalid" | "invite_required" | "key_expired" | "account_blocked";
1079
1160
  /**
1080
1161
  * `POST /v1/sandboxes/{id}/execs`, and the `exec` of `POST /v1/runs`: one command. `argv` (a
1081
1162
  * non-empty list) or `sh` (a shell line) is required.
@@ -2547,6 +2628,29 @@ export interface MemberView {
2547
2628
  */
2548
2629
  you: boolean;
2549
2630
  }
2631
+ /**
2632
+ * The $1 charged to confirm a payment method with no stable payer (`GET /v1/billing`
2633
+ * `method_check`).
2634
+ */
2635
+ export interface MethodCheckView {
2636
+ /**
2637
+ * USD charged; added to your credit once paid.
2638
+ */
2639
+ amount_usd: number;
2640
+ /**
2641
+ * USD of credit that came with it (the card bonus), once paid; `null` otherwise.
2642
+ */
2643
+ bonus_usd?: number | null;
2644
+ /**
2645
+ * The method as a person reads it: `Apple Pay`, `Google Pay`, `Amazon Pay`, ...
2646
+ */
2647
+ method: string;
2648
+ /**
2649
+ * `confirming` (charged, its answer not in yet), `paid` (it counts as your payment method) or
2650
+ * `failed` (declined, or it needed you there to approve it: it does not count; add a card).
2651
+ */
2652
+ status: string;
2653
+ }
2550
2654
  /**
2551
2655
  * `POST /v1/orgs` and `POST /v1/orgs/{org}/projects`.
2552
2656
  */
@@ -2814,6 +2918,44 @@ export interface ParkedBytes {
2814
2918
  */
2815
2919
  allocated: number;
2816
2920
  }
2921
+ /**
2922
+ * A payment: an invoice paid, a payment that failed, a refund, or the charge that confirmed a
2923
+ * payment method.
2924
+ */
2925
+ export interface Payment {
2926
+ /**
2927
+ * USD: what was paid, what was due, what was refunded, or what confirmed the method.
2928
+ */
2929
+ amount_usd: number;
2930
+ /**
2931
+ * The invoice's number, as the invoice shows it; `null` when not known.
2932
+ */
2933
+ invoice_number?: string | null;
2934
+ /**
2935
+ * The invoice's page: its receipt once it is paid, where to pay it while a payment has
2936
+ * failed; `null` when not known.
2937
+ */
2938
+ invoice_url?: string | null;
2939
+ /**
2940
+ * `invoice_paid`, `payment_failed`, `refund`, or `method_check` (a charge, usually $1, that
2941
+ * confirmed a payment method; it comes back as credit).
2942
+ */
2943
+ kind: string;
2944
+ /**
2945
+ * `method_check`: the payment method it confirmed, by name (`Apple Pay`); `null` otherwise.
2946
+ */
2947
+ method?: string | null;
2948
+ /**
2949
+ * The plan whose fee the invoice billed; `null` when it billed usage alone.
2950
+ */
2951
+ plan?: string | null;
2952
+ /**
2953
+ * `paid` (for a payment that failed: paid since), `retrying` (it failed and will be tried
2954
+ * again), `failed` (it failed and will not be tried again; for `method_check`, the method
2955
+ * was not confirmed), `refunded`, or `confirmed` (`method_check`: the method is confirmed).
2956
+ */
2957
+ status: string;
2958
+ }
2817
2959
  export interface Percentiles {
2818
2960
  p50: number;
2819
2961
  p95: number;
@@ -4502,7 +4644,7 @@ export interface SandboxView {
4502
4644
  */
4503
4645
  outcome?: string | null;
4504
4646
  /**
4505
- * While it is paused: when it will be deleted, with its memory and files, unless it is
4647
+ * While it is paused: when it will be deleted, with its files and state, unless it is
4506
4648
  * resumed before then (RFC 3339). A paused sandbox is kept for 30 days, and resuming it
4507
4649
  * starts the 30 days again. Absent while it is not paused, and for a paused sandbox that is
4508
4650
  * not due to be deleted.
package/dist/proxy.js CHANGED
@@ -199,8 +199,9 @@ export function requestVia(url, options, env = process.env) {
199
199
  connect.end();
200
200
  return undefined;
201
201
  };
202
+ const { agent: _agent, ...direct } = options;
202
203
  return httpsRequest(url, {
203
- ...options,
204
+ ...direct,
204
205
  createConnection: createConnection,
205
206
  });
206
207
  }
package/dist/pty.d.ts CHANGED
@@ -13,6 +13,10 @@
13
13
  */
14
14
  /** Largest terminal input frame sent; the server's cap is 1 MiB per client frame. */
15
15
  export declare const PTY_INPUT_CHUNK: number;
16
+ /** How much of what was typed a session keeps, to send again to a terminal picked up again. */
17
+ export declare const PTY_INPUT_KEEP: number;
18
+ /** The header a terminal is picked up again with (its resume token). */
19
+ export declare const PTY_RESUME_HEADER = "promigence-resume";
16
20
  export interface PtySize {
17
21
  cols: number;
18
22
  rows: number;
@@ -53,6 +57,33 @@ export interface PtyConnectOpts {
53
57
  caller?: string;
54
58
  onData?: (data: Uint8Array) => void;
55
59
  onExit?: (exit: PtyExit) => void;
60
+ /**
61
+ * Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
62
+ * terminal cannot be picked up again ([`resume`]). The session goes on over a new connection,
63
+ * with its callbacks, its size and what was typed meanwhile, in a NEW shell: what ran in the old
64
+ * one has ended. Tried for up to 30 s ([`PTY_RECONNECT_FOR_MS`]); past that the session ends with
65
+ * `error.code` `"service_restart"`.
66
+ *
67
+ * Default: on for a shell, off when `command` is set (reconnecting starts the command again,
68
+ * which may not be safe to run twice); `true` or `false` decides either way.
69
+ */
70
+ reconnect?: boolean;
71
+ /**
72
+ * Pick the same terminal up again when its connection drops or the service restarts (default
73
+ * on): the terminal keeps running for a short while, and the session picks up where it left off
74
+ * over a new connection, with what was typed meanwhile delivered once — the same shell, or the
75
+ * same command, still running. Where it cannot be picked up (it ended meanwhile, or too much time
76
+ * passed), a restart falls back to [`reconnect`]; a dropped connection ends the session, as
77
+ * before.
78
+ */
79
+ resume?: boolean;
80
+ /**
81
+ * Called after each reconnect: `resumed` is `true` when it is the same terminal picked up
82
+ * again, `false` when it is a new shell (what ran in the old one has ended).
83
+ */
84
+ onReconnected?: (how: {
85
+ resumed: boolean;
86
+ }) => void;
56
87
  /** Handshake bound (default 30 s). The session itself has none. */
57
88
  timeoutMs?: number;
58
89
  /** Maps a refused upgrade (`status`, body text, its `Retry-After`) onto the SDK's error classes. */
@@ -80,15 +111,28 @@ export declare class FrameReader {
80
111
  */
81
112
  export declare class PtySession {
82
113
  #private;
83
- private readonly socket;
84
- /** The id of the terminal's process inside the sandbox (`req_id`); what `pty.kill(pid)` addresses. */
114
+ private socket;
115
+ /**
116
+ * The id of the terminal's process inside the sandbox (`req_id`); what `pty.kill(pid)`
117
+ * addresses. It names this session: it stays the same after a reconnect.
118
+ */
85
119
  readonly pid: number;
86
120
  cols: number;
87
121
  rows: number;
88
122
  private closed;
89
123
  private readonly done;
124
+ /** Frames written while the session reconnects, sent once it has (at most 1 MiB). */
125
+ private pending;
126
+ /** Keystrokes written to a connection, in all, and the last of them, from `inputBase` on: what a
127
+ * terminal picked up again did not take is sent again. */
128
+ private inputSent;
129
+ private inputBase;
130
+ private inputLog;
90
131
  /** `true` once the terminal has ended (its exit arrived, or the socket closed). */
91
132
  get ended(): boolean;
133
+ private flush;
134
+ /** Write a frame to the connection now, counting keystrokes. */
135
+ private writeNow;
92
136
  private send;
93
137
  /** Keystrokes / a paste. A string is sent as UTF-8. */
94
138
  write(data: string | Uint8Array): void;
@@ -105,5 +149,23 @@ export declare class PtySession {
105
149
  */
106
150
  disconnect(): void;
107
151
  }
108
- /** Open a terminal. Rejects with `opts.refuse(...)` if the server does not switch protocols. */
152
+ /** The `Close` code a terminal is asked to reconnect with: 1012, "service restart" (RFC 6455's registry). */
153
+ export declare const PTY_RECONNECT_CODE = 1012;
154
+ /** How long a terminal tries to reconnect before it ends (`error.code` `"service_restart"`). */
155
+ export declare const PTY_RECONNECT_FOR_MS = 30000;
156
+ /** The first wait before a reconnect, doubled each try up to [`PTY_RECONNECT_MAX_WAIT_MS`]; each wait is jittered (50-100 %). */
157
+ export declare const PTY_RECONNECT_FIRST_WAIT_MS = 250;
158
+ export declare const PTY_RECONNECT_MAX_WAIT_MS = 4000;
159
+ /** How a session that could not reconnect within [`PTY_RECONNECT_FOR_MS`] ends. */
160
+ export declare const PTY_RESTART_EXIT: PtyExit;
161
+ /** How a session that is not to reconnect (`reconnect` off, or a command's) ends at a 1012. */
162
+ export declare const PTY_RESTART_CLOSED_EXIT: PtyExit;
163
+ /**
164
+ * Open a terminal. Rejects with `opts.refuse(...)` if the server does not switch protocols.
165
+ *
166
+ * When its connection drops, or the service asks it to reconnect (`Close` 1012), the session goes
167
+ * on over a new connection: the same terminal picked up again where the service still holds it
168
+ * ([`PtyConnectOpts.resume`]), else, at a restart, a new shell ([`PtyConnectOpts.reconnect`]).
169
+ * `wait()` resolves only when the session really ends.
170
+ */
109
171
  export declare function connectPty(opts: PtyConnectOpts): Promise<PtySession>;