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

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.
@@ -420,7 +420,19 @@ export interface SnapshotBuild {
420
420
  /** `true` when the snapshot this build made has since been deleted. */
421
421
  snapshot_deleted?: boolean;
422
422
  tier_assigned?: string;
423
+ /**
424
+ * The most the build can cost, USD, said before it runs: `max_seconds` at `per_second_usd`. A build
425
+ * is billed per second for the time it runs; what it cost is `cost_usd` once it has ended.
426
+ */
423
427
  build_price?: number;
428
+ /** What one second of the build costs, USD: its tier's per-second rate. */
429
+ per_second_usd?: number;
430
+ /** The longest the build may run, in seconds: it is billed for at most this long. */
431
+ max_seconds?: number;
432
+ /** Once it has ended, the seconds it is billed for; 0 when it failed on our side. */
433
+ billable_seconds?: number;
434
+ /** Once it has ended, what it cost, USD; 0 when nothing was charged. */
435
+ cost_usd?: number;
424
436
  repo?: string;
425
437
  commit?: string;
426
438
  verification?: Record<string, unknown>;
@@ -847,7 +859,10 @@ export interface PricingView {
847
859
  gib_second_usd: number;
848
860
  preemptible_vcpu_second_usd: number;
849
861
  preemptible_gib_second_usd: number;
850
- /** One snapshot build, charged once it verifies. */
862
+ /**
863
+ * The most one snapshot build of the default tier can cost (`limits.build_max_s` seconds at that
864
+ * tier's rate). A build is billed per second at its own tier's rate for the time it runs.
865
+ */
851
866
  build_usd: number;
852
867
  /** Per GiB of data out above the month's allowance; `null` when not priced. */
853
868
  egress_gb_usd: number | null;
@@ -872,6 +887,8 @@ export interface PricingView {
872
887
  max_egress_gb_month: number | null;
873
888
  /** Outbound mail ports open. */
874
889
  smtp: boolean;
890
+ /** The longest a snapshot build may run, in seconds: the most one build is billed for. */
891
+ build_max_s?: number;
875
892
  };
876
893
  /** The free credit a new account gets (an account's own: `GET /credits/offer`). */
877
894
  free_credit: {
@@ -1447,7 +1464,11 @@ export interface PromigenceClientOpts {
1447
1464
  * From the CLI: `promigence --request-timeout DUR` or `PROMIGENCE_REQUEST_TIMEOUT`.
1448
1465
  */
1449
1466
  requestTimeoutMs?: number;
1450
- /** Retries on 429/503 only (default 3). */
1467
+ /**
1468
+ * How many times a request is sent again (default 3): after a 429 or a 503, and after a 502 or
1469
+ * 504 that is not the API's own answer when sending twice is harmless (a read, a delete, or a
1470
+ * create with its idempotency key).
1471
+ */
1451
1472
  maxRetries?: number;
1452
1473
  /** Backoff base when the server sends no `Retry-After` (default 250 ms). */
1453
1474
  retryBaseMs?: number;
@@ -1885,7 +1906,7 @@ interface RequestOpts {
1885
1906
  signal?: AbortSignal;
1886
1907
  /** Skip the Authorization header (unauthenticated routes). */
1887
1908
  anonymous?: boolean;
1888
- /** Called with the response headers of the attempt that answered (not of a retried 429/503). */
1909
+ /** Called with the response headers of the attempt that answered (not of one that was retried). */
1889
1910
  onHeaders?: (headers: Headers) => void;
1890
1911
  /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1891
1912
  headers?: Record<string, string>;
@@ -1960,8 +1981,10 @@ export declare class PromigenceClient {
1960
1981
  * rule `request()` applies, for the calls that cannot go through it (byte transfers, the
1961
1982
  * opening of an exec stream, the terminal handshake). Retried: a 429, and a 503 that says
1962
1983
  * nothing started — any 503 for a request safe to send twice, else only one carrying a
1963
- * `Retry-After` (every refusal at the door sets one) or a refusal code. At most `maxRetries`
1964
- * times, and never a `Retry-After` longer than MAX_RETRY_SLEEP_MS: that one is the caller's.
1984
+ * `Retry-After` (every refusal at the door sets one) or a refusal code — and, for a request
1985
+ * safe to send twice, a 502 or 504 that is not the API's own answer (it carries no error
1986
+ * code). At most `maxRetries` times, and never a `Retry-After` longer than
1987
+ * MAX_RETRY_SLEEP_MS: that one is the caller's.
1965
1988
  */
1966
1989
  private refusalBackoff;
1967
1990
  /** A long-poll request must outlive its own `wait_ms`; everything else keeps the default. */
@@ -1998,7 +2021,7 @@ export declare class PromigenceClient {
1998
2021
  }): Promise<StatusResponse>;
1999
2022
  /**
2000
2023
  * `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
2001
- * shape on sale and its price, the build price, the limits, the free credit and the plan they
2024
+ * shape on sale and its price (a snapshot build bills at its tier's), the limits, the free credit and the plan they
2002
2025
  * come from, read on every call from what billing charges. Needs a key: without one this throws
2003
2026
  * `AuthError` and sends nothing. The published list prices are on the pricing page.
2004
2027
  *
@@ -870,7 +870,8 @@ class PromigenceClient {
870
870
  refusalBackoff(status, retryAfterMs, code, attempt, replayable, signal) {
871
871
  if (attempt >= this.maxRetries || signal?.aborted)
872
872
  return undefined;
873
- if (status !== 429 && status !== 503)
873
+ const gateway = (status === 502 || status === 504) && replayable && code === undefined;
874
+ if (status !== 429 && status !== 503 && !gateway)
874
875
  return undefined;
875
876
  if ((retryAfterMs ?? 0) > MAX_RETRY_SLEEP_MS)
876
877
  return undefined;
@@ -993,12 +994,17 @@ class PromigenceClient {
993
994
  throw new core_js_1.UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
994
995
  }
995
996
  const retryAfterMs = parseRetryAfter(headerOf(res, "retry-after"));
996
- let retryable = (res.status === 429 || res.status === 503) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
997
+ const gateway = (res.status === 502 || res.status === 504) && replayable;
998
+ let retryable = (res.status === 429 || res.status === 503 || gateway) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
997
999
  let body;
998
1000
  if (retryable && res.status === 503 && !replayable && retryAfterMs === undefined) {
999
1001
  body = await res.text().catch(() => "");
1000
1002
  retryable = REFUSED_503_CODES.has(errorCodeOf(body) ?? "");
1001
1003
  }
1004
+ if (retryable && gateway) {
1005
+ body = await res.text().catch(() => "");
1006
+ retryable = errorCodeOf(body) === undefined;
1007
+ }
1002
1008
  if (retryable && attempt < this.maxRetries) {
1003
1009
  const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
1004
1010
  const jitter = Math.random();
@@ -1006,6 +1012,8 @@ class PromigenceClient {
1006
1012
  ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
1007
1013
  : backoff * (0.5 + jitter * 0.5);
1008
1014
  this.stats.retries++;
1015
+ if (gateway && opts.method === "DELETE")
1016
+ resentDelete = true;
1009
1017
  if (body === undefined)
1010
1018
  try {
1011
1019
  await res.text();
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.8";
2
+ export declare const VERSION = "0.1.0-beta.9";
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";
@@ -14,12 +14,14 @@ export declare const WAIT_TIMEOUT_MS = 100000;
14
14
  */
15
15
  export declare const IMAGE_BUILD_TIMEOUT_MS: number;
16
16
  /**
17
- * The list price of one new snapshot build this SDK was built with, in USD (charged once it
18
- * verifies; a dedupe or a join is free). Prices change: the live one is `client.pricing()`'s
19
- * `rates.build_usd` (this key's organization's), and a build's own arrives as `build_price` in
20
- * the `POST /snapshots` answer. This is only the fallback when the API cannot be asked.
17
+ * The most one new snapshot build of the default tier can cost at the list prices this SDK was
18
+ * built with, in USD: its time limit (an hour) at that tier's per-second rate. A build is billed per
19
+ * second for the time it runs; reusing or joining an identical build is free. Prices change: the
20
+ * live figure is `client.pricing()`'s `rates.build_usd` (this key's organization's), and a build's
21
+ * own arrives as `build_price` in the `POST /snapshots` answer. This is only the fallback when the
22
+ * API cannot be asked.
21
23
  */
22
- export declare const IMAGE_BUILD_LIST_PRICE = 1;
24
+ export declare const IMAGE_BUILD_LIST_PRICE = 0.1656;
23
25
  /** Inline output budget for an exec before `truncated` + `_ref`. */
24
26
  export declare const MAX_OUTPUT_BYTES_DEFAULT: number;
25
27
  /** One request of a `files.put`. **Not a file-size limit** — `put` loops. */
package/dist/cjs/core.js CHANGED
@@ -7,14 +7,14 @@ exports.fixBeyondMessage = fixBeyondMessage;
7
7
  exports.previewsOff = previewsOff;
8
8
  exports.exitForCode = exitForCode;
9
9
  const hint_js_1 = require("./hint.js");
10
- exports.VERSION = "0.1.0-beta.8";
10
+ exports.VERSION = "0.1.0-beta.9";
11
11
  exports.DOCS_URL = "https://promigence.ai";
12
12
  exports.ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
13
13
  exports.DEFAULT_API_URL = "https://api.promigence.ai/v1";
14
14
  exports.KEY_CONTACT = "support@promigence.ai";
15
15
  exports.WAIT_TIMEOUT_MS = 100_000;
16
16
  exports.IMAGE_BUILD_TIMEOUT_MS = 15 * 60_000;
17
- exports.IMAGE_BUILD_LIST_PRICE = 1.0;
17
+ exports.IMAGE_BUILD_LIST_PRICE = 0.1656;
18
18
  exports.MAX_OUTPUT_BYTES_DEFAULT = 64 * 1024;
19
19
  exports.FILE_PUT_CHUNK_BYTES = 16 * 1024 * 1024;
20
20
  const PREVIEWS_OFF = /^previews are not enabled on this deployment\.?$/i;
@@ -34,7 +34,7 @@ export interface GithubPending {
34
34
  state: string;
35
35
  message: string;
36
36
  }
37
- /** A snapshot that follows a branch: every push builds it again (at the build price). */
37
+ /** A snapshot that follows a branch: every push builds it again (each build billed per second). */
38
38
  export interface GithubTrack {
39
39
  track_id: string;
40
40
  repo: string;
@@ -37,7 +37,7 @@ export interface GitlabPending {
37
37
  expires_at: string;
38
38
  message: string;
39
39
  }
40
- /** A snapshot that follows a GitLab branch: each new commit builds it again (at the build price). */
40
+ /** A snapshot that follows a GitLab branch: each new commit builds it again (each build billed per second). */
41
41
  export interface GitlabTrack {
42
42
  track_id: string;
43
43
  repo: string;
@@ -346,16 +346,29 @@ export interface BuildJoined {
346
346
  alias?: string | null;
347
347
  alias_applied?: boolean | null;
348
348
  /**
349
- * The alias that was asked for, when the running build carries another. It was not applied;
350
- * `warnings` says what to do once the build verifies.
349
+ * The alias that was asked for, when the build in progress was to carry another, or none.
350
+ * The build carries it now (`alias_applied: true`) and its snapshot is stored under it;
351
+ * `warnings` names the alias the build was to carry.
351
352
  */
352
353
  alias_requested?: string | null;
354
+ /**
355
+ * Once the build has ended, the seconds it is billed for: the time it ran, whether it was
356
+ * stored, its own `setup` or `verify` failed, or it ran to its time limit; 0 when it failed
357
+ * on our side. Absent while it is queued or running, and for a build priced per build.
358
+ */
359
+ billable_seconds?: number | null;
353
360
  build_id: string;
354
361
  /**
355
- * What the build cost, in USD.
362
+ * The most the build can cost, in USD, as said when it was started: `max_seconds` at
363
+ * `per_second_usd`. What it was billed is `cost_usd`.
356
364
  */
357
365
  build_price: number;
358
366
  commit: string;
367
+ /**
368
+ * Once the build has ended, what it cost in USD; 0 when nothing was charged. Absent while it
369
+ * is queued or running.
370
+ */
371
+ cost_usd?: number | null;
359
372
  /**
360
373
  * RFC 3339.
361
374
  */
@@ -378,10 +391,20 @@ export interface BuildJoined {
378
391
  * Always true here.
379
392
  */
380
393
  joined: boolean;
394
+ /**
395
+ * The longest the build may run, in seconds: it is billed for at most this long. Absent for
396
+ * a build priced per build.
397
+ */
398
+ max_seconds?: number | null;
381
399
  /**
382
400
  * What to do next, ready to run.
383
401
  */
384
402
  next: Array<string>;
403
+ /**
404
+ * What one second of the build costs, in USD: its tier's per-second rate. Absent for a build
405
+ * priced per build, before builds were billed per second.
406
+ */
407
+ per_second_usd?: number | null;
385
408
  repo: string;
386
409
  setup: Array<string>;
387
410
  /**
@@ -412,8 +435,8 @@ export interface BuildJoined {
412
435
  /**
413
436
  * True when the build finished `verified` and its verification command passed. False while
414
437
  * it is queued or running, when it failed or was rejected, and for an image built without
415
- * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
416
- * is charged if the command exits non-zero.
438
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored if the
439
+ * command exits non-zero; the time the build ran is billed.
417
440
  */
418
441
  verified: boolean;
419
442
  /**
@@ -455,7 +478,8 @@ export interface BuildQueued {
455
478
  alias?: string | null;
456
479
  build_id: string;
457
480
  /**
458
- * What the build costs, in USD, said before it runs.
481
+ * The most the build can cost, in USD, said before it runs: `max_seconds` at
482
+ * `per_second_usd`. It is billed per second for the time it runs.
459
483
  */
460
484
  build_price: number;
461
485
  commit: string;
@@ -469,10 +493,18 @@ export interface BuildQueued {
469
493
  * given, so a client can say which earlier request this repeats.
470
494
  */
471
495
  idempotent_replay_age_s?: number | null;
496
+ /**
497
+ * The longest the build may run, in seconds: it is billed for at most this long.
498
+ */
499
+ max_seconds?: number | null;
472
500
  /**
473
501
  * What to do next, ready to run.
474
502
  */
475
503
  next: Array<string>;
504
+ /**
505
+ * What one second of the build costs, in USD: its tier's per-second rate.
506
+ */
507
+ per_second_usd?: number | null;
476
508
  repo: string;
477
509
  /**
478
510
  * What it is being built from. The `kind` field says which of the three forms it is.
@@ -511,12 +543,24 @@ export interface BuildView {
511
543
  * the snapshot is deleted: the name may belong to another snapshot by then.
512
544
  */
513
545
  alias?: string | null;
546
+ /**
547
+ * Once the build has ended, the seconds it is billed for: the time it ran, whether it was
548
+ * stored, its own `setup` or `verify` failed, or it ran to its time limit; 0 when it failed
549
+ * on our side. Absent while it is queued or running, and for a build priced per build.
550
+ */
551
+ billable_seconds?: number | null;
514
552
  build_id: string;
515
553
  /**
516
- * What the build cost, in USD.
554
+ * The most the build can cost, in USD, as said when it was started: `max_seconds` at
555
+ * `per_second_usd`. What it was billed is `cost_usd`.
517
556
  */
518
557
  build_price: number;
519
558
  commit: string;
559
+ /**
560
+ * Once the build has ended, what it cost in USD; 0 when nothing was charged. Absent while it
561
+ * is queued or running.
562
+ */
563
+ cost_usd?: number | null;
520
564
  /**
521
565
  * RFC 3339.
522
566
  */
@@ -529,10 +573,20 @@ export interface BuildView {
529
573
  * RFC 3339.
530
574
  */
531
575
  finished_at?: string | null;
576
+ /**
577
+ * The longest the build may run, in seconds: it is billed for at most this long. Absent for
578
+ * a build priced per build.
579
+ */
580
+ max_seconds?: number | null;
532
581
  /**
533
582
  * What to do next, ready to run.
534
583
  */
535
584
  next: Array<string>;
585
+ /**
586
+ * What one second of the build costs, in USD: its tier's per-second rate. Absent for a build
587
+ * priced per build, before builds were billed per second.
588
+ */
589
+ per_second_usd?: number | null;
536
590
  repo: string;
537
591
  setup: Array<string>;
538
592
  /**
@@ -563,8 +617,8 @@ export interface BuildView {
563
617
  /**
564
618
  * True when the build finished `verified` and its verification command passed. False while
565
619
  * it is queued or running, when it failed or was rejected, and for an image built without
566
- * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
567
- * is charged if the command exits non-zero.
620
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored if the
621
+ * command exits non-zero; the time the build ran is billed.
568
622
  */
569
623
  verified: boolean;
570
624
  /**
@@ -2460,7 +2514,7 @@ export interface LedgerEntryView {
2460
2514
  */
2461
2515
  seconds: number;
2462
2516
  /**
2463
- * What was charged: a sandbox id or a build id.
2517
+ * What was charged: a sandbox id or a build id; for credit, what it is for (e.g. "Credit from Promigence").
2464
2518
  */
2465
2519
  subject: string;
2466
2520
  tier: string;
@@ -3103,6 +3157,11 @@ export interface PricingFreeCredit {
3103
3157
  * The limits in force.
3104
3158
  */
3105
3159
  export interface PricingLimits {
3160
+ /**
3161
+ * The longest a snapshot build may run, in seconds, and so the most seconds one build is
3162
+ * billed for.
3163
+ */
3164
+ build_max_s?: number | null;
3106
3165
  /**
3107
3166
  * A sandbox's timeout when its run names none, in seconds.
3108
3167
  */
@@ -3161,8 +3220,11 @@ export interface PricingPlan {
3161
3220
  */
3162
3221
  export interface PricingRates {
3163
3222
  /**
3164
- * One snapshot build, charged once it verifies. A build that fails `verify` stores nothing
3165
- * and is not charged; reusing an identical build is free.
3223
+ * The most one snapshot build of the default tier can cost: `limits.build_max_s` seconds at
3224
+ * that tier's per-second rate. A build is billed per second at its own tier's rate (`tiers`)
3225
+ * for the time it runs, whether it is stored, its own `setup` or `verify` fails, or it runs to
3226
+ * its time limit; a failure on our side, and reusing or joining an identical build, cost
3227
+ * nothing.
3166
3228
  */
3167
3229
  build_usd: number;
3168
3230
  /**
@@ -5201,8 +5263,7 @@ export interface SnapshotCreate {
5201
5263
  */
5202
5264
  export type SnapshotCreated = BuildQueued | BuildJoined | SnapshotDeduped;
5203
5265
  /**
5204
- * Inputs that are already built: the existing snapshot, and whether the alias that was asked
5205
- * for could be applied to it.
5266
+ * Inputs that are already built: the existing snapshot, named with the alias that was asked for.
5206
5267
  */
5207
5268
  export interface SnapshotDeduped {
5208
5269
  /**
@@ -5211,8 +5272,10 @@ export interface SnapshotDeduped {
5211
5272
  alias?: string | null;
5212
5273
  alias_applied?: boolean | null;
5213
5274
  /**
5214
- * The alias that was asked for, when it differs from the snapshot's. An unnamed snapshot
5215
- * takes the name (`alias_applied: true`); a named one keeps its own and `warnings` says so.
5275
+ * The alias that was asked for, when the snapshot did not hold it. The snapshot takes it
5276
+ * (`alias_applied: true`) as a new build would: another snapshot of the project that had it
5277
+ * keeps its row, without a name. A snapshot has one alias, so a name it had before gives
5278
+ * way, and `warnings` says which.
5216
5279
  */
5217
5280
  alias_requested?: string | null;
5218
5281
  /**
@@ -136,7 +136,7 @@ export interface SandboxCreateOpts extends SandboxOpts {
136
136
  /**
137
137
  * A container image instead of a snapshot — a tag (`python:3.12`, pinned to its digest by the
138
138
  * API) or `registry/name@sha256:<64 hex>`: the snapshot of this image is built on first use
139
- * (a new build is charged your build price once it verifies, `client.pricing()`; pass
139
+ * (a new build is billed per second while it runs, at its tier's rate, `client.pricing()`; pass
140
140
  * `allowBuild: true` to allow it) and
141
141
  * reused after (same digest + same inputs → the same snapshot). A name with no tag or digest is
142
142
  * refused: it would pin whatever `:latest` is today. The fields below shape that snapshot and
@@ -156,17 +156,17 @@ export interface SandboxCreateOpts extends SandboxOpts {
156
156
  buildTimeoutMs?: number;
157
157
  /**
158
158
  * Let `Sandbox.create({ image })` build the image's snapshot when none exists yet. A new build
159
- * costs `build_price` (your plan's, `client.pricing()`; charged once it verifies); reusing a snapshot already built
160
- * is free. Without it, a create from an image is refused before anything is sent: whether the
159
+ * is billed per second while it runs, at most `build_price` (your plan's rates, `client.pricing()`);
160
+ * reusing a snapshot already built is free. Without it, a create from an image is refused before anything is sent: whether the
161
161
  * image's snapshot exists is only known once a build is submitted, and a submitted build cannot
162
162
  * be withdrawn. The other way to pay once and on purpose: `promigence snapshot create --image
163
163
  * REF --alias NAME`, then `Sandbox.create("NAME")`.
164
164
  */
165
165
  allowBuild?: boolean;
166
166
  /**
167
- * Called with the `POST /snapshots` answer before any wait: `build_price`, and `deduped` or
168
- * `joined` when nothing new is charged. Without it, a new build's price is reported as a
169
- * process warning (stderr) before the build is waited for.
167
+ * Called with the `POST /snapshots` answer before any wait: `build_price` (the most the build can
168
+ * cost) and `per_second_usd`, and `deduped` or `joined` when nothing new is charged. Without it,
169
+ * a new build's price is reported as a process warning (stderr) before the build is waited for.
170
170
  */
171
171
  onBuild?: (build: SnapshotBuild) => void | Promise<void>;
172
172
  /**
@@ -198,9 +198,8 @@ export interface SandboxCreateOpts extends SandboxOpts {
198
198
  */
199
199
  export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
200
200
  /**
201
- * The `POST /snapshots` answer, before any wait: `build_price` and whether it was `deduped`
202
- * or `joined` (nothing new charged). An implicit build costs money, so a caller
203
- * that prints prices prints this one too.
201
+ * The `POST /snapshots` answer, before any wait: `build_price` (the most it can cost) and
202
+ * whether it was `deduped` or `joined` (nothing new charged). An implicit build costs money, so a caller that prints prices prints this one too.
204
203
  */
205
204
  onBuild?: (build: SnapshotBuild) => void | Promise<void>;
206
205
  /** Ends the wait for a build at once; the build itself, once submitted, is not withdrawn. */
@@ -375,7 +374,7 @@ export declare class Sandbox {
375
374
  /**
376
375
  * Fork of 1 from a snapshot (`snap_<id>` | `<alias>` | `<alias>@<commit7>`), or from the
377
376
  * snapshot of an image: `Sandbox.create({ image: 'python:3.12', allowBuild: true })` builds it
378
- * on first use (a new build at your build price, bounded by `buildTimeoutMs`, default 15 min) and reuses it
377
+ * on first use (a new build billed per second at its tier's rate, bounded by `buildTimeoutMs`, default 15 min) and reuses it
379
378
  * free after. An image needs `allowBuild: true` and a tag or digest; without either it is
380
379
  * refused before any request.
381
380
  *
@@ -75,12 +75,16 @@ function buildNotice(image) {
75
75
  return (b) => {
76
76
  if (buildIsFree(b))
77
77
  return;
78
- const price = typeof b.build_price === "number" ? `build_price $${b.build_price.toFixed(2)}` : "charged your build price";
79
- process.emitWarning(`building a snapshot of ${image}: ${price}, charged once it verifies (later creates from this image reuse it free)`, { code: "PROMIGENCE_BUILD" });
78
+ const price = typeof b.build_price === "number"
79
+ ? typeof b.per_second_usd === "number"
80
+ ? `billed per second while it builds ($${Number(b.per_second_usd.toPrecision(12))}/s), at most $${b.build_price.toFixed(2)}`
81
+ : `build_price $${b.build_price.toFixed(2)}, charged once it verifies`
82
+ : "billed at your plan's rates";
83
+ process.emitWarning(`building a snapshot of ${image}: ${price} (later creates from this image reuse it free)`, { code: "PROMIGENCE_BUILD" });
80
84
  };
81
85
  }
82
86
  function buildNotAllowed(image) {
83
- return new core_js_1.InvalidArgumentError(`Sandbox.create({ image: ${JSON.stringify(image)} }) builds that image's snapshot unless one already exists, and a new build is charged your build price once it verifies (\`promigence pricing\` shows it; reusing a snapshot already built is free). Nothing was sent.`, `To allow the build: Sandbox.create({ image: ${JSON.stringify(image)}, allowBuild: true }). Or build it once, ` +
87
+ return new core_js_1.InvalidArgumentError(`Sandbox.create({ image: ${JSON.stringify(image)} }) builds that image's snapshot unless one already exists, and a new build is billed per second while it runs (\`promigence pricing\` shows the rates; reusing a snapshot already built is free). Nothing was sent.`, `To allow the build: Sandbox.create({ image: ${JSON.stringify(image)}, allowBuild: true }). Or build it once, ` +
84
88
  `\`promigence snapshot create --image ${(0, hint_js_1.hintArg)(image)} --alias NAME\`, and create from its name: Sandbox.create("NAME").`);
85
89
  }
86
90
  async function imageSnapshot(client, opts, hooks = {}) {
package/dist/client.d.ts CHANGED
@@ -420,7 +420,19 @@ export interface SnapshotBuild {
420
420
  /** `true` when the snapshot this build made has since been deleted. */
421
421
  snapshot_deleted?: boolean;
422
422
  tier_assigned?: string;
423
+ /**
424
+ * The most the build can cost, USD, said before it runs: `max_seconds` at `per_second_usd`. A build
425
+ * is billed per second for the time it runs; what it cost is `cost_usd` once it has ended.
426
+ */
423
427
  build_price?: number;
428
+ /** What one second of the build costs, USD: its tier's per-second rate. */
429
+ per_second_usd?: number;
430
+ /** The longest the build may run, in seconds: it is billed for at most this long. */
431
+ max_seconds?: number;
432
+ /** Once it has ended, the seconds it is billed for; 0 when it failed on our side. */
433
+ billable_seconds?: number;
434
+ /** Once it has ended, what it cost, USD; 0 when nothing was charged. */
435
+ cost_usd?: number;
424
436
  repo?: string;
425
437
  commit?: string;
426
438
  verification?: Record<string, unknown>;
@@ -847,7 +859,10 @@ export interface PricingView {
847
859
  gib_second_usd: number;
848
860
  preemptible_vcpu_second_usd: number;
849
861
  preemptible_gib_second_usd: number;
850
- /** One snapshot build, charged once it verifies. */
862
+ /**
863
+ * The most one snapshot build of the default tier can cost (`limits.build_max_s` seconds at that
864
+ * tier's rate). A build is billed per second at its own tier's rate for the time it runs.
865
+ */
851
866
  build_usd: number;
852
867
  /** Per GiB of data out above the month's allowance; `null` when not priced. */
853
868
  egress_gb_usd: number | null;
@@ -872,6 +887,8 @@ export interface PricingView {
872
887
  max_egress_gb_month: number | null;
873
888
  /** Outbound mail ports open. */
874
889
  smtp: boolean;
890
+ /** The longest a snapshot build may run, in seconds: the most one build is billed for. */
891
+ build_max_s?: number;
875
892
  };
876
893
  /** The free credit a new account gets (an account's own: `GET /credits/offer`). */
877
894
  free_credit: {
@@ -1447,7 +1464,11 @@ export interface PromigenceClientOpts {
1447
1464
  * From the CLI: `promigence --request-timeout DUR` or `PROMIGENCE_REQUEST_TIMEOUT`.
1448
1465
  */
1449
1466
  requestTimeoutMs?: number;
1450
- /** Retries on 429/503 only (default 3). */
1467
+ /**
1468
+ * How many times a request is sent again (default 3): after a 429 or a 503, and after a 502 or
1469
+ * 504 that is not the API's own answer when sending twice is harmless (a read, a delete, or a
1470
+ * create with its idempotency key).
1471
+ */
1451
1472
  maxRetries?: number;
1452
1473
  /** Backoff base when the server sends no `Retry-After` (default 250 ms). */
1453
1474
  retryBaseMs?: number;
@@ -1885,7 +1906,7 @@ interface RequestOpts {
1885
1906
  signal?: AbortSignal;
1886
1907
  /** Skip the Authorization header (unauthenticated routes). */
1887
1908
  anonymous?: boolean;
1888
- /** Called with the response headers of the attempt that answered (not of a retried 429/503). */
1909
+ /** Called with the response headers of the attempt that answered (not of one that was retried). */
1889
1910
  onHeaders?: (headers: Headers) => void;
1890
1911
  /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1891
1912
  headers?: Record<string, string>;
@@ -1960,8 +1981,10 @@ export declare class PromigenceClient {
1960
1981
  * rule `request()` applies, for the calls that cannot go through it (byte transfers, the
1961
1982
  * opening of an exec stream, the terminal handshake). Retried: a 429, and a 503 that says
1962
1983
  * nothing started — any 503 for a request safe to send twice, else only one carrying a
1963
- * `Retry-After` (every refusal at the door sets one) or a refusal code. At most `maxRetries`
1964
- * times, and never a `Retry-After` longer than MAX_RETRY_SLEEP_MS: that one is the caller's.
1984
+ * `Retry-After` (every refusal at the door sets one) or a refusal code — and, for a request
1985
+ * safe to send twice, a 502 or 504 that is not the API's own answer (it carries no error
1986
+ * code). At most `maxRetries` times, and never a `Retry-After` longer than
1987
+ * MAX_RETRY_SLEEP_MS: that one is the caller's.
1965
1988
  */
1966
1989
  private refusalBackoff;
1967
1990
  /** A long-poll request must outlive its own `wait_ms`; everything else keeps the default. */
@@ -1998,7 +2021,7 @@ export declare class PromigenceClient {
1998
2021
  }): Promise<StatusResponse>;
1999
2022
  /**
2000
2023
  * `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
2001
- * shape on sale and its price, the build price, the limits, the free credit and the plan they
2024
+ * shape on sale and its price (a snapshot build bills at its tier's), the limits, the free credit and the plan they
2002
2025
  * come from, read on every call from what billing charges. Needs a key: without one this throws
2003
2026
  * `AuthError` and sends nothing. The published list prices are on the pricing page.
2004
2027
  *
package/dist/client.js CHANGED
@@ -837,7 +837,8 @@ export class PromigenceClient {
837
837
  refusalBackoff(status, retryAfterMs, code, attempt, replayable, signal) {
838
838
  if (attempt >= this.maxRetries || signal?.aborted)
839
839
  return undefined;
840
- if (status !== 429 && status !== 503)
840
+ const gateway = (status === 502 || status === 504) && replayable && code === undefined;
841
+ if (status !== 429 && status !== 503 && !gateway)
841
842
  return undefined;
842
843
  if ((retryAfterMs ?? 0) > MAX_RETRY_SLEEP_MS)
843
844
  return undefined;
@@ -960,12 +961,17 @@ export class PromigenceClient {
960
961
  throw new UnreachableError(`cannot reach ${url}: ${transportDetail(e)}`);
961
962
  }
962
963
  const retryAfterMs = parseRetryAfter(headerOf(res, "retry-after"));
963
- let retryable = (res.status === 429 || res.status === 503) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
964
+ const gateway = (res.status === 502 || res.status === 504) && replayable;
965
+ let retryable = (res.status === 429 || res.status === 503 || gateway) && (retryAfterMs ?? 0) <= MAX_RETRY_SLEEP_MS;
964
966
  let body;
965
967
  if (retryable && res.status === 503 && !replayable && retryAfterMs === undefined) {
966
968
  body = await res.text().catch(() => "");
967
969
  retryable = REFUSED_503_CODES.has(errorCodeOf(body) ?? "");
968
970
  }
971
+ if (retryable && gateway) {
972
+ body = await res.text().catch(() => "");
973
+ retryable = errorCodeOf(body) === undefined;
974
+ }
969
975
  if (retryable && attempt < this.maxRetries) {
970
976
  const backoff = Math.min(MAX_RETRY_SLEEP_MS, this.retryBaseMs * 2 ** attempt);
971
977
  const jitter = Math.random();
@@ -973,6 +979,8 @@ export class PromigenceClient {
973
979
  ? retryAfterMs + jitter * Math.min(250, this.retryBaseMs)
974
980
  : backoff * (0.5 + jitter * 0.5);
975
981
  this.stats.retries++;
982
+ if (gateway && opts.method === "DELETE")
983
+ resentDelete = true;
976
984
  if (body === undefined)
977
985
  try {
978
986
  await res.text();
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.8";
2
+ export declare const VERSION = "0.1.0-beta.9";
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";
@@ -14,12 +14,14 @@ export declare const WAIT_TIMEOUT_MS = 100000;
14
14
  */
15
15
  export declare const IMAGE_BUILD_TIMEOUT_MS: number;
16
16
  /**
17
- * The list price of one new snapshot build this SDK was built with, in USD (charged once it
18
- * verifies; a dedupe or a join is free). Prices change: the live one is `client.pricing()`'s
19
- * `rates.build_usd` (this key's organization's), and a build's own arrives as `build_price` in
20
- * the `POST /snapshots` answer. This is only the fallback when the API cannot be asked.
17
+ * The most one new snapshot build of the default tier can cost at the list prices this SDK was
18
+ * built with, in USD: its time limit (an hour) at that tier's per-second rate. A build is billed per
19
+ * second for the time it runs; reusing or joining an identical build is free. Prices change: the
20
+ * live figure is `client.pricing()`'s `rates.build_usd` (this key's organization's), and a build's
21
+ * own arrives as `build_price` in the `POST /snapshots` answer. This is only the fallback when the
22
+ * API cannot be asked.
21
23
  */
22
- export declare const IMAGE_BUILD_LIST_PRICE = 1;
24
+ export declare const IMAGE_BUILD_LIST_PRICE = 0.1656;
23
25
  /** Inline output budget for an exec before `truncated` + `_ref`. */
24
26
  export declare const MAX_OUTPUT_BYTES_DEFAULT: number;
25
27
  /** One request of a `files.put`. **Not a file-size limit** — `put` loops. */
package/dist/core.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { hintArg } from "./hint.js";
2
- export const VERSION = "0.1.0-beta.8";
2
+ export const VERSION = "0.1.0-beta.9";
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";
6
6
  export const KEY_CONTACT = "support@promigence.ai";
7
7
  export const WAIT_TIMEOUT_MS = 100_000;
8
8
  export const IMAGE_BUILD_TIMEOUT_MS = 15 * 60_000;
9
- export const IMAGE_BUILD_LIST_PRICE = 1.0;
9
+ export const IMAGE_BUILD_LIST_PRICE = 0.1656;
10
10
  export const MAX_OUTPUT_BYTES_DEFAULT = 64 * 1024;
11
11
  export const FILE_PUT_CHUNK_BYTES = 16 * 1024 * 1024;
12
12
  const PREVIEWS_OFF = /^previews are not enabled on this deployment\.?$/i;
package/dist/github.d.ts CHANGED
@@ -34,7 +34,7 @@ export interface GithubPending {
34
34
  state: string;
35
35
  message: string;
36
36
  }
37
- /** A snapshot that follows a branch: every push builds it again (at the build price). */
37
+ /** A snapshot that follows a branch: every push builds it again (each build billed per second). */
38
38
  export interface GithubTrack {
39
39
  track_id: string;
40
40
  repo: string;
package/dist/gitlab.d.ts CHANGED
@@ -37,7 +37,7 @@ export interface GitlabPending {
37
37
  expires_at: string;
38
38
  message: string;
39
39
  }
40
- /** A snapshot that follows a GitLab branch: each new commit builds it again (at the build price). */
40
+ /** A snapshot that follows a GitLab branch: each new commit builds it again (each build billed per second). */
41
41
  export interface GitlabTrack {
42
42
  track_id: string;
43
43
  repo: string;
@@ -346,16 +346,29 @@ export interface BuildJoined {
346
346
  alias?: string | null;
347
347
  alias_applied?: boolean | null;
348
348
  /**
349
- * The alias that was asked for, when the running build carries another. It was not applied;
350
- * `warnings` says what to do once the build verifies.
349
+ * The alias that was asked for, when the build in progress was to carry another, or none.
350
+ * The build carries it now (`alias_applied: true`) and its snapshot is stored under it;
351
+ * `warnings` names the alias the build was to carry.
351
352
  */
352
353
  alias_requested?: string | null;
354
+ /**
355
+ * Once the build has ended, the seconds it is billed for: the time it ran, whether it was
356
+ * stored, its own `setup` or `verify` failed, or it ran to its time limit; 0 when it failed
357
+ * on our side. Absent while it is queued or running, and for a build priced per build.
358
+ */
359
+ billable_seconds?: number | null;
353
360
  build_id: string;
354
361
  /**
355
- * What the build cost, in USD.
362
+ * The most the build can cost, in USD, as said when it was started: `max_seconds` at
363
+ * `per_second_usd`. What it was billed is `cost_usd`.
356
364
  */
357
365
  build_price: number;
358
366
  commit: string;
367
+ /**
368
+ * Once the build has ended, what it cost in USD; 0 when nothing was charged. Absent while it
369
+ * is queued or running.
370
+ */
371
+ cost_usd?: number | null;
359
372
  /**
360
373
  * RFC 3339.
361
374
  */
@@ -378,10 +391,20 @@ export interface BuildJoined {
378
391
  * Always true here.
379
392
  */
380
393
  joined: boolean;
394
+ /**
395
+ * The longest the build may run, in seconds: it is billed for at most this long. Absent for
396
+ * a build priced per build.
397
+ */
398
+ max_seconds?: number | null;
381
399
  /**
382
400
  * What to do next, ready to run.
383
401
  */
384
402
  next: Array<string>;
403
+ /**
404
+ * What one second of the build costs, in USD: its tier's per-second rate. Absent for a build
405
+ * priced per build, before builds were billed per second.
406
+ */
407
+ per_second_usd?: number | null;
385
408
  repo: string;
386
409
  setup: Array<string>;
387
410
  /**
@@ -412,8 +435,8 @@ export interface BuildJoined {
412
435
  /**
413
436
  * True when the build finished `verified` and its verification command passed. False while
414
437
  * it is queued or running, when it failed or was rejected, and for an image built without
415
- * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
416
- * is charged if the command exits non-zero.
438
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored if the
439
+ * command exits non-zero; the time the build ran is billed.
417
440
  */
418
441
  verified: boolean;
419
442
  /**
@@ -455,7 +478,8 @@ export interface BuildQueued {
455
478
  alias?: string | null;
456
479
  build_id: string;
457
480
  /**
458
- * What the build costs, in USD, said before it runs.
481
+ * The most the build can cost, in USD, said before it runs: `max_seconds` at
482
+ * `per_second_usd`. It is billed per second for the time it runs.
459
483
  */
460
484
  build_price: number;
461
485
  commit: string;
@@ -469,10 +493,18 @@ export interface BuildQueued {
469
493
  * given, so a client can say which earlier request this repeats.
470
494
  */
471
495
  idempotent_replay_age_s?: number | null;
496
+ /**
497
+ * The longest the build may run, in seconds: it is billed for at most this long.
498
+ */
499
+ max_seconds?: number | null;
472
500
  /**
473
501
  * What to do next, ready to run.
474
502
  */
475
503
  next: Array<string>;
504
+ /**
505
+ * What one second of the build costs, in USD: its tier's per-second rate.
506
+ */
507
+ per_second_usd?: number | null;
476
508
  repo: string;
477
509
  /**
478
510
  * What it is being built from. The `kind` field says which of the three forms it is.
@@ -511,12 +543,24 @@ export interface BuildView {
511
543
  * the snapshot is deleted: the name may belong to another snapshot by then.
512
544
  */
513
545
  alias?: string | null;
546
+ /**
547
+ * Once the build has ended, the seconds it is billed for: the time it ran, whether it was
548
+ * stored, its own `setup` or `verify` failed, or it ran to its time limit; 0 when it failed
549
+ * on our side. Absent while it is queued or running, and for a build priced per build.
550
+ */
551
+ billable_seconds?: number | null;
514
552
  build_id: string;
515
553
  /**
516
- * What the build cost, in USD.
554
+ * The most the build can cost, in USD, as said when it was started: `max_seconds` at
555
+ * `per_second_usd`. What it was billed is `cost_usd`.
517
556
  */
518
557
  build_price: number;
519
558
  commit: string;
559
+ /**
560
+ * Once the build has ended, what it cost in USD; 0 when nothing was charged. Absent while it
561
+ * is queued or running.
562
+ */
563
+ cost_usd?: number | null;
520
564
  /**
521
565
  * RFC 3339.
522
566
  */
@@ -529,10 +573,20 @@ export interface BuildView {
529
573
  * RFC 3339.
530
574
  */
531
575
  finished_at?: string | null;
576
+ /**
577
+ * The longest the build may run, in seconds: it is billed for at most this long. Absent for
578
+ * a build priced per build.
579
+ */
580
+ max_seconds?: number | null;
532
581
  /**
533
582
  * What to do next, ready to run.
534
583
  */
535
584
  next: Array<string>;
585
+ /**
586
+ * What one second of the build costs, in USD: its tier's per-second rate. Absent for a build
587
+ * priced per build, before builds were billed per second.
588
+ */
589
+ per_second_usd?: number | null;
536
590
  repo: string;
537
591
  setup: Array<string>;
538
592
  /**
@@ -563,8 +617,8 @@ export interface BuildView {
563
617
  /**
564
618
  * True when the build finished `verified` and its verification command passed. False while
565
619
  * it is queued or running, when it failed or was rejected, and for an image built without
566
- * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
567
- * is charged if the command exits non-zero.
620
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored if the
621
+ * command exits non-zero; the time the build ran is billed.
568
622
  */
569
623
  verified: boolean;
570
624
  /**
@@ -2460,7 +2514,7 @@ export interface LedgerEntryView {
2460
2514
  */
2461
2515
  seconds: number;
2462
2516
  /**
2463
- * What was charged: a sandbox id or a build id.
2517
+ * What was charged: a sandbox id or a build id; for credit, what it is for (e.g. "Credit from Promigence").
2464
2518
  */
2465
2519
  subject: string;
2466
2520
  tier: string;
@@ -3103,6 +3157,11 @@ export interface PricingFreeCredit {
3103
3157
  * The limits in force.
3104
3158
  */
3105
3159
  export interface PricingLimits {
3160
+ /**
3161
+ * The longest a snapshot build may run, in seconds, and so the most seconds one build is
3162
+ * billed for.
3163
+ */
3164
+ build_max_s?: number | null;
3106
3165
  /**
3107
3166
  * A sandbox's timeout when its run names none, in seconds.
3108
3167
  */
@@ -3161,8 +3220,11 @@ export interface PricingPlan {
3161
3220
  */
3162
3221
  export interface PricingRates {
3163
3222
  /**
3164
- * One snapshot build, charged once it verifies. A build that fails `verify` stores nothing
3165
- * and is not charged; reusing an identical build is free.
3223
+ * The most one snapshot build of the default tier can cost: `limits.build_max_s` seconds at
3224
+ * that tier's per-second rate. A build is billed per second at its own tier's rate (`tiers`)
3225
+ * for the time it runs, whether it is stored, its own `setup` or `verify` fails, or it runs to
3226
+ * its time limit; a failure on our side, and reusing or joining an identical build, cost
3227
+ * nothing.
3166
3228
  */
3167
3229
  build_usd: number;
3168
3230
  /**
@@ -5201,8 +5263,7 @@ export interface SnapshotCreate {
5201
5263
  */
5202
5264
  export type SnapshotCreated = BuildQueued | BuildJoined | SnapshotDeduped;
5203
5265
  /**
5204
- * Inputs that are already built: the existing snapshot, and whether the alias that was asked
5205
- * for could be applied to it.
5266
+ * Inputs that are already built: the existing snapshot, named with the alias that was asked for.
5206
5267
  */
5207
5268
  export interface SnapshotDeduped {
5208
5269
  /**
@@ -5211,8 +5272,10 @@ export interface SnapshotDeduped {
5211
5272
  alias?: string | null;
5212
5273
  alias_applied?: boolean | null;
5213
5274
  /**
5214
- * The alias that was asked for, when it differs from the snapshot's. An unnamed snapshot
5215
- * takes the name (`alias_applied: true`); a named one keeps its own and `warnings` says so.
5275
+ * The alias that was asked for, when the snapshot did not hold it. The snapshot takes it
5276
+ * (`alias_applied: true`) as a new build would: another snapshot of the project that had it
5277
+ * keeps its row, without a name. A snapshot has one alias, so a name it had before gives
5278
+ * way, and `warnings` says which.
5216
5279
  */
5217
5280
  alias_requested?: string | null;
5218
5281
  /**
package/dist/sandbox.d.ts CHANGED
@@ -136,7 +136,7 @@ export interface SandboxCreateOpts extends SandboxOpts {
136
136
  /**
137
137
  * A container image instead of a snapshot — a tag (`python:3.12`, pinned to its digest by the
138
138
  * API) or `registry/name@sha256:<64 hex>`: the snapshot of this image is built on first use
139
- * (a new build is charged your build price once it verifies, `client.pricing()`; pass
139
+ * (a new build is billed per second while it runs, at its tier's rate, `client.pricing()`; pass
140
140
  * `allowBuild: true` to allow it) and
141
141
  * reused after (same digest + same inputs → the same snapshot). A name with no tag or digest is
142
142
  * refused: it would pin whatever `:latest` is today. The fields below shape that snapshot and
@@ -156,17 +156,17 @@ export interface SandboxCreateOpts extends SandboxOpts {
156
156
  buildTimeoutMs?: number;
157
157
  /**
158
158
  * Let `Sandbox.create({ image })` build the image's snapshot when none exists yet. A new build
159
- * costs `build_price` (your plan's, `client.pricing()`; charged once it verifies); reusing a snapshot already built
160
- * is free. Without it, a create from an image is refused before anything is sent: whether the
159
+ * is billed per second while it runs, at most `build_price` (your plan's rates, `client.pricing()`);
160
+ * reusing a snapshot already built is free. Without it, a create from an image is refused before anything is sent: whether the
161
161
  * image's snapshot exists is only known once a build is submitted, and a submitted build cannot
162
162
  * be withdrawn. The other way to pay once and on purpose: `promigence snapshot create --image
163
163
  * REF --alias NAME`, then `Sandbox.create("NAME")`.
164
164
  */
165
165
  allowBuild?: boolean;
166
166
  /**
167
- * Called with the `POST /snapshots` answer before any wait: `build_price`, and `deduped` or
168
- * `joined` when nothing new is charged. Without it, a new build's price is reported as a
169
- * process warning (stderr) before the build is waited for.
167
+ * Called with the `POST /snapshots` answer before any wait: `build_price` (the most the build can
168
+ * cost) and `per_second_usd`, and `deduped` or `joined` when nothing new is charged. Without it,
169
+ * a new build's price is reported as a process warning (stderr) before the build is waited for.
170
170
  */
171
171
  onBuild?: (build: SnapshotBuild) => void | Promise<void>;
172
172
  /**
@@ -198,9 +198,8 @@ export interface SandboxCreateOpts extends SandboxOpts {
198
198
  */
199
199
  export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
200
200
  /**
201
- * The `POST /snapshots` answer, before any wait: `build_price` and whether it was `deduped`
202
- * or `joined` (nothing new charged). An implicit build costs money, so a caller
203
- * that prints prices prints this one too.
201
+ * The `POST /snapshots` answer, before any wait: `build_price` (the most it can cost) and
202
+ * whether it was `deduped` or `joined` (nothing new charged). An implicit build costs money, so a caller that prints prices prints this one too.
204
203
  */
205
204
  onBuild?: (build: SnapshotBuild) => void | Promise<void>;
206
205
  /** Ends the wait for a build at once; the build itself, once submitted, is not withdrawn. */
@@ -375,7 +374,7 @@ export declare class Sandbox {
375
374
  /**
376
375
  * Fork of 1 from a snapshot (`snap_<id>` | `<alias>` | `<alias>@<commit7>`), or from the
377
376
  * snapshot of an image: `Sandbox.create({ image: 'python:3.12', allowBuild: true })` builds it
378
- * on first use (a new build at your build price, bounded by `buildTimeoutMs`, default 15 min) and reuses it
377
+ * on first use (a new build billed per second at its tier's rate, bounded by `buildTimeoutMs`, default 15 min) and reuses it
379
378
  * free after. An image needs `allowBuild: true` and a tag or digest; without either it is
380
379
  * refused before any request.
381
380
  *
package/dist/sandbox.js CHANGED
@@ -65,12 +65,16 @@ function buildNotice(image) {
65
65
  return (b) => {
66
66
  if (buildIsFree(b))
67
67
  return;
68
- const price = typeof b.build_price === "number" ? `build_price $${b.build_price.toFixed(2)}` : "charged your build price";
69
- process.emitWarning(`building a snapshot of ${image}: ${price}, charged once it verifies (later creates from this image reuse it free)`, { code: "PROMIGENCE_BUILD" });
68
+ const price = typeof b.build_price === "number"
69
+ ? typeof b.per_second_usd === "number"
70
+ ? `billed per second while it builds ($${Number(b.per_second_usd.toPrecision(12))}/s), at most $${b.build_price.toFixed(2)}`
71
+ : `build_price $${b.build_price.toFixed(2)}, charged once it verifies`
72
+ : "billed at your plan's rates";
73
+ process.emitWarning(`building a snapshot of ${image}: ${price} (later creates from this image reuse it free)`, { code: "PROMIGENCE_BUILD" });
70
74
  };
71
75
  }
72
76
  function buildNotAllowed(image) {
73
- return new InvalidArgumentError(`Sandbox.create({ image: ${JSON.stringify(image)} }) builds that image's snapshot unless one already exists, and a new build is charged your build price once it verifies (\`promigence pricing\` shows it; reusing a snapshot already built is free). Nothing was sent.`, `To allow the build: Sandbox.create({ image: ${JSON.stringify(image)}, allowBuild: true }). Or build it once, ` +
77
+ return new InvalidArgumentError(`Sandbox.create({ image: ${JSON.stringify(image)} }) builds that image's snapshot unless one already exists, and a new build is billed per second while it runs (\`promigence pricing\` shows the rates; reusing a snapshot already built is free). Nothing was sent.`, `To allow the build: Sandbox.create({ image: ${JSON.stringify(image)}, allowBuild: true }). Or build it once, ` +
74
78
  `\`promigence snapshot create --image ${hintArg(image)} --alias NAME\`, and create from its name: Sandbox.create("NAME").`);
75
79
  }
76
80
  export async function imageSnapshot(client, opts, hooks = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promigence/sdk",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "The fastest sandboxes for AI agents and code. The Promigence TypeScript SDK: snapshot an environment once, fork it as often as you need, run commands in every copy. A simple Sandbox class and the full PromigenceClient, with zero runtime dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://www.promigence.ai",