promigence 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.
- package/README.md +6 -0
- package/dist/commands.d.ts +3 -1
- package/dist/commands.js +51 -11
- package/dist/generated.d.ts +1 -1
- package/dist/generated.js +7 -7
- package/dist/github.js +4 -4
- package/dist/gitlab.js +1 -1
- package/dist/guide.js +8 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init.js +1 -1
- package/dist/mcp.js +5 -3
- package/dist/pricing.d.ts +11 -1
- package/dist/pricing.js +23 -5
- package/dist/runners-spec.js +9 -1
- package/dist/runners.js +14 -9
- package/dist/sdk/client.d.ts +29 -6
- package/dist/sdk/client.js +10 -2
- package/dist/sdk/core.d.ts +8 -6
- package/dist/sdk/core.js +2 -2
- package/dist/sdk/github.d.ts +1 -1
- package/dist/sdk/gitlab.d.ts +1 -1
- package/dist/sdk/models.gen.d.ts +79 -16
- package/dist/sdk/sandbox.d.ts +9 -10
- package/dist/sdk/sandbox.js +7 -3
- package/dist/surface.js +2 -2
- package/llms.txt +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/commands.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { PromigenceClient, type ExecResponse, type OptimizeFor, type RunReport, type SandboxRef, type ScoreMode, type SecretRecord } from "./sdk/index.js";
|
|
1
|
+
import { PromigenceClient, type ExecResponse, type OptimizeFor, type RunReport, type SandboxRef, type ScoreMode, type SecretRecord, type SnapshotBuild } from "./sdk/index.js";
|
|
2
2
|
import { type Mode } from "./io.js";
|
|
3
|
+
import { type BuildPrice } from "./pricing.js";
|
|
3
4
|
import { type Command, type NextHint } from "./surface.js";
|
|
4
5
|
export interface Ctx {
|
|
5
6
|
cmd: Command;
|
|
@@ -46,6 +47,7 @@ export declare function gradeOpts(ctx: Ctx): {
|
|
|
46
47
|
score?: ScoreMode;
|
|
47
48
|
} | undefined;
|
|
48
49
|
export declare function secretListLines(s: SecretRecord): string[];
|
|
50
|
+
export declare function buildPriceOf(b: SnapshotBuild): BuildPrice | undefined;
|
|
49
51
|
export declare function verifyTiming(v: Record<string, unknown> | undefined): string | undefined;
|
|
50
52
|
export declare function buildLogLines(log: string[] | undefined, state: string): string[];
|
|
51
53
|
export declare function snapshotSource(s: {
|
package/dist/commands.js
CHANGED
|
@@ -10,7 +10,7 @@ import { DevcontainerError, detectOne, load, planJson, planLines, planSetup, res
|
|
|
10
10
|
import { detect, ecosystemOfCommands, imageCreateCommand, readConfig } from "./init.js";
|
|
11
11
|
import { CREDENTIAL_REMEDY, confirmOverride, credentialRefusal, emitUploadList, localTarNoMetadataArgs } from "./uploadguard.js";
|
|
12
12
|
import { emit, errorEnvelope, errorTextLines, exitWith, forkLine, forkSummary, gradeLine, ms, noCommandFix, okEnvelope, parseMaxOutput, recordNotice, sandboxGrade, parseCommandTimeout, parseCount, parseDuration, parseUsd, pickExit, reportSummaryLines, toPromigenceError, totalsLine, usageError, write, writeBytes, } from "./io.js";
|
|
13
|
-
import { buildPrice } from "./pricing.js";
|
|
13
|
+
import { buildPrice, buildPriceWords } from "./pricing.js";
|
|
14
14
|
import { DOCS_URL, WAIT_TIMEOUT_DEFAULT } from "./surface.js";
|
|
15
15
|
import { escapeLine, fieldForTerminal, forTerminal, jsonLine, sh, shellArg } from "./term.js";
|
|
16
16
|
import { snapshotArg } from "./bundle.js";
|
|
@@ -430,6 +430,10 @@ function buildFields(b) {
|
|
|
430
430
|
out["source"] = b.source;
|
|
431
431
|
if (typeof b.verified === "boolean")
|
|
432
432
|
out["verified"] = b.verified;
|
|
433
|
+
for (const k of ["build_price", "per_second_usd", "max_seconds", "billable_seconds", "cost_usd"]) {
|
|
434
|
+
if (typeof b[k] === "number")
|
|
435
|
+
out[k] = b[k];
|
|
436
|
+
}
|
|
433
437
|
const ab = b;
|
|
434
438
|
if (ab.alias_requested !== undefined)
|
|
435
439
|
out["alias_requested"] = ab.alias_requested;
|
|
@@ -440,6 +444,11 @@ function buildFields(b) {
|
|
|
440
444
|
function isBuilding(b) {
|
|
441
445
|
return b.state === "queued" || b.state === "running";
|
|
442
446
|
}
|
|
447
|
+
export function buildPriceOf(b) {
|
|
448
|
+
return typeof b.build_price === "number" && typeof b.per_second_usd === "number" && typeof b.max_seconds === "number"
|
|
449
|
+
? { most_usd: b.build_price, per_second_usd: b.per_second_usd, max_s: b.max_seconds }
|
|
450
|
+
: undefined;
|
|
451
|
+
}
|
|
443
452
|
function buildExit(b) {
|
|
444
453
|
if (b.state === "verified" || b.state === "built")
|
|
445
454
|
return 0;
|
|
@@ -474,9 +483,15 @@ function buildLines(b) {
|
|
|
474
483
|
const timing = verifyTiming(b.verification);
|
|
475
484
|
if (timing)
|
|
476
485
|
lines.push(` verify: ${timing}`);
|
|
486
|
+
const cost = typeof b.cost_usd === "number" && !isBuilding(b) ? b.cost_usd : undefined;
|
|
487
|
+
const ran = typeof b.billable_seconds === "number" ? ` for the ${b.billable_seconds} s it ran` : "";
|
|
477
488
|
if (b.error) {
|
|
478
489
|
lines.push(` ${b.error.guard ? `guard ${b.error.guard}: ` : ""}${b.error.message ?? b.error.code ?? "rejected"}`);
|
|
479
|
-
|
|
490
|
+
const stored = `nothing was stored${b.error.stored === false ? "" : "?"}`;
|
|
491
|
+
lines.push(cost !== undefined && cost > 0 ? ` ${stored}; billed ${usd(cost)}${ran}` : ` ${stored} and nothing was charged`);
|
|
492
|
+
}
|
|
493
|
+
else if (cost !== undefined && cost > 0) {
|
|
494
|
+
lines.push(` billed ${usd(cost)}${ran}`);
|
|
480
495
|
}
|
|
481
496
|
for (const w of b.warnings ?? [])
|
|
482
497
|
lines.push(` warning: ${w}`);
|
|
@@ -875,14 +890,22 @@ const snapshotCreate = async (ctx) => {
|
|
|
875
890
|
const reused = started.deduped === true;
|
|
876
891
|
const joinedBuild = started.joined === true;
|
|
877
892
|
const replay = buildReplayNote(started, str(ctx, "--idempotency-key") !== undefined);
|
|
878
|
-
const
|
|
893
|
+
const free = reused || joinedBuild || replay !== undefined;
|
|
894
|
+
const builtAt = tier ?? (typeof started["tier"] === "string" ? started["tier"] : undefined);
|
|
895
|
+
const perSecond = buildPriceOf(started);
|
|
896
|
+
const flat = !free && perSecond === undefined && typeof started.build_price === "number" ? started.build_price : undefined;
|
|
897
|
+
const said = free || flat !== undefined ? perSecond : (perSecond ?? await buildPrice(c, builtAt));
|
|
898
|
+
const charged = free ? 0 : flat ?? said?.most_usd ?? 0;
|
|
879
899
|
submitted["build_price"] = charged;
|
|
880
|
-
if (
|
|
900
|
+
if (!free && said !== undefined)
|
|
901
|
+
Object.assign(submitted, { per_second_usd: said.per_second_usd, max_seconds: said.max_s });
|
|
902
|
+
if (free)
|
|
881
903
|
submitted["charged"] = false;
|
|
882
904
|
Object.assign(submitted, replayFields(started));
|
|
883
905
|
await write(process.stderr, ctx.mode === "json"
|
|
884
906
|
? JSON.stringify({
|
|
885
907
|
hint: "build_price", build_price: charged, unit: "USD", ...(tier === undefined ? {} : { tier }),
|
|
908
|
+
...(!free && said !== undefined ? { per_second_usd: said.per_second_usd, max_seconds: said.max_s } : {}),
|
|
886
909
|
...(reused ? { deduped: true } : {}), ...(joinedBuild ? { joined: true } : {}),
|
|
887
910
|
...(replay === undefined ? {} : { replayed: true }),
|
|
888
911
|
}) + "\n"
|
|
@@ -892,7 +915,9 @@ const snapshotCreate = async (ctx) => {
|
|
|
892
915
|
? `build_price: $0.00 - an identical build already ran (${started.build_id}); reusing it, nothing charged\n`
|
|
893
916
|
: joinedBuild
|
|
894
917
|
? `build_price: $0.00 - joining an identical build already running (${started.build_id}); it is charged once\n`
|
|
895
|
-
:
|
|
918
|
+
: flat !== undefined
|
|
919
|
+
? `build_price: $${flat.toFixed(2)} (tier ${tier ?? "default"}) - building ${label}\n`
|
|
920
|
+
: `build_price: ${buildPriceWords(said, builtAt ?? "default")} - building ${label}\n`);
|
|
896
921
|
if (sources.length && ctx.mode !== "json" && !reused) {
|
|
897
922
|
await write(process.stderr, sources
|
|
898
923
|
.map((s) => ` also ${s.repo}@${s.commit.slice(0, 7)} -> ${s.path ?? "beside the workspace, under its own name"}`)
|
|
@@ -920,7 +945,7 @@ const snapshotCreate = async (ctx) => {
|
|
|
920
945
|
const follow = sh `promigence snapshot status ${started.build_id} --wait`;
|
|
921
946
|
const err = new PromigenceError({
|
|
922
947
|
code: "aborted_by_client",
|
|
923
|
-
message: `interrupted by ${sig}; build ${started.build_id} goes on and nothing was cancelled (it is
|
|
948
|
+
message: `interrupted by ${sig}; build ${started.build_id} goes on and nothing was cancelled (it is billed per second until it ends)`,
|
|
924
949
|
fix: `Follow it: ${follow}.`,
|
|
925
950
|
exitCode: 130,
|
|
926
951
|
});
|
|
@@ -1587,7 +1612,8 @@ async function runImageSnapshot(ctx, c) {
|
|
|
1587
1612
|
checkImageRef(image);
|
|
1588
1613
|
const count = parseCount(required(ctx, "--count", "Pass --count N (number of sandboxes to fork)."), "--count");
|
|
1589
1614
|
const cap = await spendCap(ctx, c);
|
|
1590
|
-
const
|
|
1615
|
+
const most = await buildPrice(c);
|
|
1616
|
+
const price = most.most_usd;
|
|
1591
1617
|
const timeout = str(ctx, "--timeout");
|
|
1592
1618
|
const quoted = await c.runs.quote({
|
|
1593
1619
|
snapshot: DEFAULT_SNAPSHOT, count, ...(timeout === undefined ? {} : { timeoutMs: parseDuration(timeout, "--timeout") }),
|
|
@@ -1595,29 +1621,43 @@ async function runImageSnapshot(ctx, c) {
|
|
|
1595
1621
|
const needed = Math.ceil((price + (quoted ?? 0)) * 100) / 100;
|
|
1596
1622
|
if (cap < price + (quoted ?? 0)) {
|
|
1597
1623
|
const existing = (quoted ?? 0) <= cap ? await existingImageSnapshot(c, image) : undefined;
|
|
1598
|
-
throw new SpendCapError(`a new image's snapshot build
|
|
1624
|
+
throw new SpendCapError(`a new image's snapshot build can cost up to ${moneyUsd(price)} on top of the run` +
|
|
1599
1625
|
`${quoted === undefined ? "" : ` (about ${moneyUsd(quoted)})`}, over --cap ${moneyUsd(cap)}; nothing was built or started`, (existing === undefined ? "" : `This image already has snapshot ${existing}: run it under this cap with ${sh `promigence run --snapshot ${existing}`} instead of --image (any start command or settings it was made with come with it). Or `) +
|
|
1600
|
-
`${existing === undefined ? "Pass" : "pass"} --cap ${needed.toFixed(2)} or more (a build is
|
|
1626
|
+
`${existing === undefined ? "Pass" : "pass"} --cap ${needed.toFixed(2)} or more (a build is billed per second while it runs, only when this image has no snapshot yet, and reused free after), ` +
|
|
1601
1627
|
`or build it first, ${sh `promigence snapshot create --image ${image} --wait`}, and run its snapshot with --snapshot.`);
|
|
1602
1628
|
}
|
|
1603
1629
|
await write(process.stderr, ctx.mode === "json"
|
|
1604
1630
|
? jsonLine(process.stderr, { hint: "image snapshot", image })
|
|
1605
1631
|
: `image ${image}: creating or reusing its snapshot (a first build takes minutes)\n`);
|
|
1606
1632
|
let charged = 0;
|
|
1633
|
+
let perSecond = false;
|
|
1607
1634
|
let buildId;
|
|
1608
1635
|
const buildOpts = { image, allowBuild: true };
|
|
1609
1636
|
const snapshotId = await imageSnapshot(c, buildOpts, {
|
|
1610
1637
|
onBuild: async (b) => {
|
|
1611
1638
|
const free = b.deduped === true || b.joined === true || b.idempotent_replay === true || b.state === "verified" || b.state === "built";
|
|
1639
|
+
const said = buildPriceOf(b);
|
|
1640
|
+
perSecond = !free && said !== undefined;
|
|
1612
1641
|
charged = free ? 0 : (typeof b.build_price === "number" ? b.build_price : price);
|
|
1613
1642
|
buildId = b.build_id;
|
|
1614
1643
|
await write(process.stderr, ctx.mode === "json"
|
|
1615
|
-
? jsonLine(process.stderr, {
|
|
1644
|
+
? jsonLine(process.stderr, {
|
|
1645
|
+
hint: "build_price", build_price: charged, unit: "USD", image,
|
|
1646
|
+
...(perSecond && said ? { per_second_usd: said.per_second_usd, max_seconds: said.max_s } : {}),
|
|
1647
|
+
...(b.build_id ? { build_id: b.build_id } : {}), ...(free ? { charged: false } : {}),
|
|
1648
|
+
})
|
|
1616
1649
|
: free
|
|
1617
1650
|
? `build_price: $0.00 - this image's snapshot already exists; reusing it, nothing charged\n`
|
|
1618
|
-
:
|
|
1651
|
+
: perSecond && said
|
|
1652
|
+
? `build_price: ${buildPriceWords(said)} - building ${image}'s snapshot (billed per second while it builds; counted in --cap)\n`
|
|
1653
|
+
: `build_price: $${charged.toFixed(2)} - building ${image}'s snapshot (charged once it verifies; counted in --cap)\n`);
|
|
1619
1654
|
},
|
|
1620
1655
|
});
|
|
1656
|
+
if (perSecond && buildId !== undefined) {
|
|
1657
|
+
const done = await c.snapshots.status(buildId).catch(() => undefined);
|
|
1658
|
+
if (done !== undefined && typeof done.cost_usd === "number" && !isBuilding(done))
|
|
1659
|
+
charged = done.cost_usd;
|
|
1660
|
+
}
|
|
1621
1661
|
await write(process.stderr, ctx.mode === "json"
|
|
1622
1662
|
? jsonLine(process.stderr, { hint: "image snapshot ready", image, snapshot_id: snapshotId })
|
|
1623
1663
|
: fieldForTerminal(process.stderr, `image ${image}: snapshot ${snapshotId}`) + "\n");
|
package/dist/generated.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface TierEntry {
|
|
|
20
20
|
per_second_usd: number;
|
|
21
21
|
}
|
|
22
22
|
export declare const TIERS: readonly TierEntry[];
|
|
23
|
-
export declare const
|
|
23
|
+
export declare const BUILD_MAX_S = 3600;
|
|
24
24
|
export declare const PRICING: {
|
|
25
25
|
readonly vcpu_second_usd: 0.000014;
|
|
26
26
|
readonly gib_second_usd: 0.0000045;
|
package/dist/generated.js
CHANGED
|
@@ -158,12 +158,12 @@ export const ERRORS = [
|
|
|
158
158
|
{ code: "capacity_limit", category: "env", http: 409, exit: 3, billed: "no",
|
|
159
159
|
message: "{requested} {tier} sandboxes at once is more than one run can start at once; at most {max_at_once}",
|
|
160
160
|
fix: "Not billed; nothing started. Run at most {max_at_once} at once (lower `--count`), or use a smaller tier." },
|
|
161
|
-
{ code: "build_timeout", category: "other", http: 422, exit: 4, billed: "
|
|
161
|
+
{ code: "build_timeout", category: "other", http: 422, exit: 4, billed: "yes",
|
|
162
162
|
message: "the build ran past its {limit_min}-minute limit",
|
|
163
|
-
fix: "
|
|
164
|
-
{ code: "dockerfile_build_failed", category: "other", http: 400, exit: 2, billed: "
|
|
163
|
+
fix: "Nothing was stored; the {limit_min} minutes it ran are billed. Shorten `setup` (at most 64 steps, {limit_min} minutes in all) or bake the slow part into the image." },
|
|
164
|
+
{ code: "dockerfile_build_failed", category: "other", http: 400, exit: 2, billed: "yes",
|
|
165
165
|
message: "your image could not be built from the Dockerfile: {detail}",
|
|
166
|
-
fix: "Nothing was stored
|
|
166
|
+
fix: "Nothing was stored; the time the build ran is billed. Fix the Dockerfile (the lines above are the end of your own build output) and run `promigence snapshot create --dockerfile PATH ...` again." },
|
|
167
167
|
{ code: "image_unresolvable", category: "other", http: 422, exit: 2, billed: "no",
|
|
168
168
|
message: "`{image}` could not be resolved to a digest: {detail}",
|
|
169
169
|
fix: "Nothing was stored and nothing was billed. Check the name and tag; a private image needs `registry_secret` naming a stored secret that holds `user:password` (`promigence secrets set`). You can also pass the image already pinned as NAME@sha256:<digest>." },
|
|
@@ -263,9 +263,9 @@ export const CLIENT_ERRORS = [
|
|
|
263
263
|
{ code: "env_failed", category: "env", http: 0, exit: 3, billed: "no",
|
|
264
264
|
message: "the sandbox ended before hand-over",
|
|
265
265
|
fix: "Ours, not billed. The run's report names the failure's own code (`promigence run report RUN`); start the sandbox again." },
|
|
266
|
-
{ code: "snapshot_rejected", category: "other", http: 0, exit: 6, billed: "
|
|
266
|
+
{ code: "snapshot_rejected", category: "other", http: 0, exit: 6, billed: "unless_ours",
|
|
267
267
|
message: "a build guard refused it; nothing was stored",
|
|
268
|
-
fix: "
|
|
268
|
+
fix: "The time the build ran is billed, unless it ended on our side. Read the build log (`promigence snapshot status BUILD --log 80`): the guard named in the error says what to change (verify exited non-zero, a setup step failed, a time or size bound), then build again." },
|
|
269
269
|
{ code: "build_failed", category: "env", http: 0, exit: 3, billed: "no",
|
|
270
270
|
message: "the build could not finish; nothing was stored",
|
|
271
271
|
fix: "Not charged. Build again; if it repeats, email support@promigence.ai with the build id." },
|
|
@@ -291,7 +291,7 @@ export const TIERS = [
|
|
|
291
291
|
{ name: "bench-8x16", vcpus: 8, mem_gib: 16, disk_gib: 20, sellable: false, per_second_usd: 0 },
|
|
292
292
|
{ name: "bench-4x8", vcpus: 4, mem_gib: 8, disk_gib: 40, sellable: false, per_second_usd: 0 },
|
|
293
293
|
];
|
|
294
|
-
export const
|
|
294
|
+
export const BUILD_MAX_S = 3600;
|
|
295
295
|
export const PRICING = {
|
|
296
296
|
vcpu_second_usd: 0.000014,
|
|
297
297
|
gib_second_usd: 0.0000045,
|
package/dist/github.js
CHANGED
|
@@ -119,7 +119,7 @@ const githubStatus = async (ctx) => {
|
|
|
119
119
|
lines.push("pending:", ...s.pending.map((p) => ` installation ${p.installation_id}${p.account ? ` (${p.account})` : ""}: ${p.message}`));
|
|
120
120
|
}
|
|
121
121
|
if (s.tracks.length)
|
|
122
|
-
lines.push("tracked branches (each push rebuilds,
|
|
122
|
+
lines.push("tracked branches (each push rebuilds, billed per second):", ...s.tracks.map(trackLine));
|
|
123
123
|
const next = s.connections.length
|
|
124
124
|
? nexts(["promigence repos", "the repositories the connection can read"])
|
|
125
125
|
: nexts(["promigence github connect", "install the App on your GitHub account"]);
|
|
@@ -289,12 +289,12 @@ export async function githubCreate(ctx) {
|
|
|
289
289
|
};
|
|
290
290
|
if (track) {
|
|
291
291
|
const branch = ref ?? "the default branch";
|
|
292
|
-
const each = await buildPrice(client(ctx));
|
|
292
|
+
const each = (await buildPrice(client(ctx), tier)).most_usd;
|
|
293
293
|
await write(process.stderr, ctx.mode === "json"
|
|
294
294
|
? jsonLine(process.stderr, { hint: "track", repo, branch, alias, build_price_each: each, unit: "USD" })
|
|
295
295
|
: glPath
|
|
296
|
-
? `tracking: every new commit on ${branch} (checked every minute) rebuilds this snapshot and moves ${alias} to it,
|
|
297
|
-
: `tracking: every push to ${branch} rebuilds this snapshot and moves ${alias} to it,
|
|
296
|
+
? `tracking: every new commit on ${branch} (checked every minute) rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence gitlab untrack ${alias}`}\`\n`
|
|
297
|
+
: `tracking: every push to ${branch} rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence github untrack ${alias}`}\`\n`);
|
|
298
298
|
}
|
|
299
299
|
return { opts, label, submitted, tier };
|
|
300
300
|
}
|
package/dist/gitlab.js
CHANGED
|
@@ -120,7 +120,7 @@ const gitlabStatus = async (ctx) => {
|
|
|
120
120
|
lines.push("waiting for an owner's confirmation:", ...s.pending.map((p) => ` ${p.username} (${p.connection_id}): ${p.message}`));
|
|
121
121
|
}
|
|
122
122
|
if (s.tracks.length) {
|
|
123
|
-
lines.push("tracked branches (each new commit rebuilds,
|
|
123
|
+
lines.push("tracked branches (each new commit rebuilds, billed per second; checked every minute):", ...s.tracks.map(trackLine));
|
|
124
124
|
}
|
|
125
125
|
if (s.connections.length) {
|
|
126
126
|
lines.push("optional, for instant rebuilds: a project webhook (push events) with each connection's URL and token in --json (webhook_url, webhook_token)");
|
package/dist/guide.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS,
|
|
1
|
+
import { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING, } from "./generated.js";
|
|
2
2
|
export const BILLED_TEXT = {
|
|
3
3
|
no: "no: nothing is billed for this",
|
|
4
4
|
yes: "yes: the time used is billed",
|
|
@@ -55,7 +55,7 @@ function renderCore() {
|
|
|
55
55
|
" verify → snapshot → fork(N) → exec → validity report",
|
|
56
56
|
"",
|
|
57
57
|
"- **snapshot** — an image (pinned) + ordered `setup` steps + a `verify` command that, if given, must exit 0.",
|
|
58
|
-
" If verify fails nothing is stored
|
|
58
|
+
" If verify fails nothing is stored; the build's own time is billed. A branch is not a pin.",
|
|
59
59
|
"- **fork** — a warm copy of that workspace, with its `start` service ready;",
|
|
60
60
|
" N at once, each checked before you get it.",
|
|
61
61
|
"- **validity report** — per sandbox, and this split is the product: **env** failures are ours",
|
|
@@ -245,7 +245,8 @@ function renderCost(prices) {
|
|
|
245
245
|
const vcpu = prices?.vcpu_second_usd ?? PRICING.vcpu_second_usd;
|
|
246
246
|
const gib = prices?.gib_second_usd ?? PRICING.gib_second_usd;
|
|
247
247
|
const granularity = prices?.granularity_seconds ?? PRICING.granularity_seconds;
|
|
248
|
-
const
|
|
248
|
+
const buildMax = prices?.build_max_s ?? BUILD_MAX_S;
|
|
249
|
+
const buildHours = buildMax % 3600 === 0 ? `${buildMax / 3600} h` : `${Math.ceil(buildMax / 60)} min`;
|
|
249
250
|
const view = prices?.view;
|
|
250
251
|
const source = prices === undefined
|
|
251
252
|
? "List prices when this CLI was built. Live, and your plan's: `promigence pricing`."
|
|
@@ -269,8 +270,10 @@ function renderCost(prices) {
|
|
|
269
270
|
...tiers.map((t) => `| \`${t.name}\` | ${t.vcpus} | ${t.mem_gib} GiB | ${t.disk_gib} GiB | $${t.per_second_usd} | ${perHour(t.per_second_usd)} |`),
|
|
270
271
|
...(typeof limit === "number" ? ["", `Your plan runs sandboxes of up to ${limit} vCPU.`] : []),
|
|
271
272
|
"",
|
|
272
|
-
`Building a snapshot
|
|
273
|
-
"
|
|
273
|
+
`Building a snapshot bills per second at its tier's rate while it runs, at most ${buildHours} (the most it can`,
|
|
274
|
+
"cost is printed on stderr **before** the build starts; what it cost, when it ends). A build that",
|
|
275
|
+
"fails `verify` or a guard stores nothing and is billed the time it ran; one that fails on our",
|
|
276
|
+
"side is not billed, and reusing an identical build is free.",
|
|
274
277
|
"",
|
|
275
278
|
"`env` failures (`promigence agent-guide --section errors`): one before hand-over is not billed,",
|
|
276
279
|
"e.g. " + ERRORS.filter((e) => e.category === "env").slice(0, 3).map((e) => `\`${e.code}\``).join(", ") + ". After hand-over, a failure our",
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { SCHEMA, GUIDE_POINTER, GOLDEN_PATH, COMMANDS, LISTED_COMMANDS, GROUPS,
|
|
|
3
3
|
export { ALL_ERROR_CODES, BILLED_TEXT, BUDGETS, SECTIONS, estimateTokens, guideJson, lookupError, renderGuide, renderLlmsTxt,
|
|
4
4
|
renderGuide as renderAgentGuide, } from "./guide.js";
|
|
5
5
|
export type { Section } from "./guide.js";
|
|
6
|
-
export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS,
|
|
6
|
+
export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING } from "./generated.js";
|
|
7
7
|
export type { ErrorEntry, TierEntry } from "./generated.js";
|
|
8
8
|
export { FENCE_BEGIN, FENCE_END, FENCE_MAX_LINES, fenceBody, install, spliceFence } from "./install.js";
|
|
9
9
|
export type { Artifact, InstallResult } from "./install.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./sdk/index.js";
|
|
2
2
|
export { SCHEMA, GUIDE_POINTER, GOLDEN_PATH, COMMANDS, LISTED_COMMANDS, GROUPS, GLOBAL_FLAGS, EXIT_CODES, LIVE_COMMANDS, LIVE_SUMMARY, NOT_LIVE_SUMMARY, STATUS_LINE, DESCRIPTION, compactNames, EXIT_PRECEDENCE, EXIT_PRECEDENCE_TEXT, OUTPUT_MODE_PRECEDENCE, WAIT_TIMEOUT_DEFAULT, REQUEST_TIMEOUT_DEFAULT, GUIDE_COMMON_ERRORS, findCommand, knownFlags, nearestFlag, unknownFlags, renderHelp, renderCommandHelp, signInLine, notReleasedEnvelope, notReleasedLines, } from "./surface.js";
|
|
3
3
|
export { ALL_ERROR_CODES, BILLED_TEXT, BUDGETS, SECTIONS, estimateTokens, guideJson, lookupError, renderGuide, renderLlmsTxt, renderGuide as renderAgentGuide, } from "./guide.js";
|
|
4
|
-
export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS,
|
|
4
|
+
export { CLIENT_ERRORS, CLIENT_ERROR_BY_CODE, ERRORS, ERROR_BY_CODE, TIERS, BUILD_MAX_S, PRICING } from "./generated.js";
|
|
5
5
|
export { FENCE_BEGIN, FENCE_END, FENCE_MAX_LINES, fenceBody, install, spliceFence } from "./install.js";
|
|
6
6
|
export { detect, gitFacts, normalizeRemote, readConfig, renderConfig, stripRemoteCredentials } from "./init.js";
|
|
7
7
|
export { DEVCONTAINER_NAMES, DEVCONTAINER_SPEC, DevcontainerError, detectOne, discover, load, parseJsonc, planJson, planLines, planSetup, resolveGiven, shellQuote, snapshotEnv, snapshotUsers, substituteString, translate, workspaceRootFor, wrapCommand, } from "./devcontainer.js";
|
package/dist/init.js
CHANGED
|
@@ -344,7 +344,7 @@ export function renderConfig(c, notes = [], build) {
|
|
|
344
344
|
];
|
|
345
345
|
if (c.alias)
|
|
346
346
|
lines.push(`alias = ${tomlString(c.alias)}`);
|
|
347
|
-
lines.push("", "# Ordered; each must exit 0.", `setup = ${tomlArray(c.setup)}`, "", "# The command that decides whether the snapshot exists at all. If it does not exit 0,", "# nothing is stored
|
|
347
|
+
lines.push("", "# Ordered; each must exit 0.", `setup = ${tomlArray(c.setup)}`, "", "# The command that decides whether the snapshot exists at all. If it does not exit 0,", "# nothing is stored (the build's time is billed).", `verify = ${tomlString(c.verify)}`, "", "[guards]", "# Checked by the builder before anything is stored. A failed guard names itself.", `toolchain_present = ${tomlArray(c.toolchain)}`);
|
|
348
348
|
if (c.lockfile)
|
|
349
349
|
lines.push(`lockfile_unchanged = ${tomlString(c.lockfile)}`);
|
|
350
350
|
lines.push("install_sane = true", "verify_warm_ratio = true", "");
|
package/dist/mcp.js
CHANGED
|
@@ -138,7 +138,7 @@ export const MCP_TOOLS = [
|
|
|
138
138
|
type: "object",
|
|
139
139
|
properties: {
|
|
140
140
|
snapshot: { type: "string", description: "snap_<id>, an alias, or alias@commit7. Default promigence/base. Not with image." },
|
|
141
|
-
image: { type: "string", description: "a tag such as python:3.12 (the API pins it to a digest) or registry/name@sha256:<64 hex>. A new image's snapshot is built on first use (minutes,
|
|
141
|
+
image: { type: "string", description: "a tag such as python:3.12 (the API pins it to a digest) or registry/name@sha256:<64 hex>. A new image's snapshot is built on first use (minutes, billed per second while it builds at its tier's rate: \`promigence pricing\`) and reused free after." },
|
|
142
142
|
tier: { type: "string", description: "Machine size for an image's snapshot; a snapshot already has one.", enum: ["xsmall", "small", "medium", "large"] },
|
|
143
143
|
timeout_s: seconds("Sandbox lifetime", "; it is killed when this expires (default 15 minutes)"),
|
|
144
144
|
network,
|
|
@@ -158,7 +158,9 @@ export const MCP_TOOLS = [
|
|
|
158
158
|
}
|
|
159
159
|
const asked = typeof a["cap_usd"] === "number" ? a["cap_usd"] : undefined;
|
|
160
160
|
const capped = asked !== undefined && s.cap !== undefined ? Math.min(asked, s.cap) : asked ?? s.cap;
|
|
161
|
-
const build = typeof a["image"] === "string" && capped !== undefined
|
|
161
|
+
const build = typeof a["image"] === "string" && capped !== undefined
|
|
162
|
+
? (await buildPrice(s.client(), typeof a["tier"] === "string" ? a["tier"] : undefined)).most_usd
|
|
163
|
+
: 0;
|
|
162
164
|
let runCost = 0;
|
|
163
165
|
if (typeof a["image"] === "string" && capped !== undefined) {
|
|
164
166
|
const p = await livePricing(s.client());
|
|
@@ -171,7 +173,7 @@ export const MCP_TOOLS = [
|
|
|
171
173
|
const needed = (Math.ceil((build + runCost) * 100) / 100).toFixed(2);
|
|
172
174
|
throw new PromigenceError({
|
|
173
175
|
code: "cap_exceeded",
|
|
174
|
-
message: `a new image's snapshot build
|
|
176
|
+
message: `a new image's snapshot build can cost up to $${build.toFixed(2)}, over ${which} $${capped.toFixed(2)}` +
|
|
175
177
|
`${capped >= build ? ` with the sandbox's own cost (about $${runCost.toFixed(2)})` : ""}; nothing was built or started`,
|
|
176
178
|
fix: `Pass snapshot instead (build it once outside the agent: ${sh `promigence snapshot create --image ${a["image"]} --alias NAME`}), or ` +
|
|
177
179
|
(which === "cap_usd" ? `pass cap_usd ${needed} or more.` : `start the server with --cap ${needed} or more.`),
|
package/dist/pricing.d.ts
CHANGED
|
@@ -22,17 +22,27 @@ export interface Prices {
|
|
|
22
22
|
preemptible_vcpu_second_usd: number;
|
|
23
23
|
preemptible_gib_second_usd: number;
|
|
24
24
|
build_usd: number;
|
|
25
|
+
build_max_s: number;
|
|
25
26
|
granularity_seconds: number;
|
|
26
27
|
tiers: PriceTier[];
|
|
27
28
|
}
|
|
28
29
|
export declare function publishedPrices(): Prices;
|
|
30
|
+
export declare const DEFAULT_BUILD_TIER = "small";
|
|
29
31
|
export declare function compiledPrices(note?: string, reason?: string): Prices;
|
|
30
32
|
export declare function pricesFrom(view: PricingView): Prices;
|
|
31
33
|
export declare function livePricing(c: PromigenceClient, opts?: {
|
|
32
34
|
newAccount?: boolean;
|
|
33
35
|
strict?: boolean;
|
|
34
36
|
}): Promise<Prices>;
|
|
35
|
-
export
|
|
37
|
+
export interface BuildPrice {
|
|
38
|
+
most_usd: number;
|
|
39
|
+
per_second_usd: number;
|
|
40
|
+
max_s: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function buildPriceIn(p: Prices, tier?: string): BuildPrice;
|
|
43
|
+
export declare function buildPrice(c: PromigenceClient, tier?: string): Promise<BuildPrice>;
|
|
44
|
+
export declare function buildLimit(s: number): string;
|
|
45
|
+
export declare function buildPriceWords(p: BuildPrice, tier?: string): string;
|
|
36
46
|
export declare function quickClient(ctx: Ctx): PromigenceClient;
|
|
37
47
|
export declare function rate(n: number): string;
|
|
38
48
|
export declare function pricingLines(p: Prices, opts?: {
|
package/dist/pricing.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PromigenceClient } from "./sdk/index.js";
|
|
2
2
|
import { bool, clientOpts, nexts } from "./commands.js";
|
|
3
|
-
import {
|
|
3
|
+
import { BUILD_MAX_S, PRICING, TIERS } from "./generated.js";
|
|
4
4
|
import { emit, okEnvelope, recordNotice } from "./io.js";
|
|
5
5
|
import { usd } from "./money.js";
|
|
6
6
|
export const COMPILED_NOTE = "list prices at build time; could not reach the API";
|
|
@@ -9,10 +9,12 @@ const nano = (n) => Math.round(n * 1e9) / 1e9;
|
|
|
9
9
|
export function publishedPrices() {
|
|
10
10
|
return { ...compiledPrices(PUBLISHED_NOTE), source: "published" };
|
|
11
11
|
}
|
|
12
|
+
export const DEFAULT_BUILD_TIER = "small";
|
|
12
13
|
export function compiledPrices(note = COMPILED_NOTE, reason) {
|
|
13
14
|
const perSecond = (v, g) => (t) => nano(t.vcpus * v + t.mem_gib * g);
|
|
14
15
|
const od = perSecond(PRICING.vcpu_second_usd, PRICING.gib_second_usd);
|
|
15
16
|
const pe = perSecond(PRICING.preemptible_vcpu_second_usd, PRICING.preemptible_gib_second_usd);
|
|
17
|
+
const buildTier = TIERS.find((t) => t.name === DEFAULT_BUILD_TIER);
|
|
16
18
|
return {
|
|
17
19
|
source: "compiled",
|
|
18
20
|
note,
|
|
@@ -21,7 +23,8 @@ export function compiledPrices(note = COMPILED_NOTE, reason) {
|
|
|
21
23
|
gib_second_usd: PRICING.gib_second_usd,
|
|
22
24
|
preemptible_vcpu_second_usd: PRICING.preemptible_vcpu_second_usd,
|
|
23
25
|
preemptible_gib_second_usd: PRICING.preemptible_gib_second_usd,
|
|
24
|
-
build_usd:
|
|
26
|
+
build_usd: buildTier ? nano(od(buildTier) * BUILD_MAX_S) : 0,
|
|
27
|
+
build_max_s: BUILD_MAX_S,
|
|
25
28
|
granularity_seconds: PRICING.granularity_seconds,
|
|
26
29
|
tiers: TIERS.filter((t) => t.sellable)
|
|
27
30
|
.slice()
|
|
@@ -41,6 +44,7 @@ export function pricesFrom(view) {
|
|
|
41
44
|
preemptible_vcpu_second_usd: view.rates.preemptible_vcpu_second_usd,
|
|
42
45
|
preemptible_gib_second_usd: view.rates.preemptible_gib_second_usd,
|
|
43
46
|
build_usd: view.rates.build_usd,
|
|
47
|
+
build_max_s: view.limits?.build_max_s ?? BUILD_MAX_S,
|
|
44
48
|
granularity_seconds: view.granularity_seconds,
|
|
45
49
|
tiers: view.tiers.map((t) => ({ ...t })),
|
|
46
50
|
};
|
|
@@ -72,8 +76,22 @@ export async function livePricing(c, opts = {}) {
|
|
|
72
76
|
return compiledPrices(COMPILED_NOTE, err?.message ?? String(e));
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
|
-
export
|
|
76
|
-
|
|
79
|
+
export function buildPriceIn(p, tier) {
|
|
80
|
+
const t = tier === undefined ? undefined : p.tiers.find((x) => x.name === tier);
|
|
81
|
+
const max = p.build_max_s > 0 ? p.build_max_s : BUILD_MAX_S;
|
|
82
|
+
if (t !== undefined) {
|
|
83
|
+
return { most_usd: nano(t.per_second_usd * max), per_second_usd: t.per_second_usd, max_s: max };
|
|
84
|
+
}
|
|
85
|
+
return { most_usd: p.build_usd, per_second_usd: nano(p.build_usd / max), max_s: max };
|
|
86
|
+
}
|
|
87
|
+
export async function buildPrice(c, tier) {
|
|
88
|
+
return buildPriceIn(await livePricing(c), tier);
|
|
89
|
+
}
|
|
90
|
+
export function buildLimit(s) {
|
|
91
|
+
return hours(s);
|
|
92
|
+
}
|
|
93
|
+
export function buildPriceWords(p, tier) {
|
|
94
|
+
return `up to ${usd(p.most_usd)} (${tier === undefined ? "" : `tier ${tier}, `}${rate(p.per_second_usd)}/s for at most ${buildLimit(p.max_s)})`;
|
|
77
95
|
}
|
|
78
96
|
export function quickClient(ctx) {
|
|
79
97
|
const opts = clientOpts(ctx);
|
|
@@ -97,7 +115,7 @@ export function pricingLines(p, opts = {}) {
|
|
|
97
115
|
head,
|
|
98
116
|
` rates ${rate(p.vcpu_second_usd)} per vCPU-second + ${rate(p.gib_second_usd)} per GiB-second, billed per second (rounded up to ${p.granularity_seconds} s)`,
|
|
99
117
|
` preempt. ${rate(p.preemptible_vcpu_second_usd)} per vCPU-second + ${rate(p.preemptible_gib_second_usd)} per GiB-second (preemptible runs)`,
|
|
100
|
-
` build ${usd(p.build_usd)}
|
|
118
|
+
` build a new snapshot build is billed per second at its tier's rate while it runs, at most ${hours(p.build_max_s)} (up to ${usd(p.build_usd)} at ${DEFAULT_BUILD_TIER}); not billed when it fails on our side`,
|
|
101
119
|
];
|
|
102
120
|
if (v) {
|
|
103
121
|
const eg = v.rates.egress_gb_usd;
|
package/dist/runners-spec.js
CHANGED
|
@@ -4,9 +4,11 @@ export const ACTIONS_RUNNER_SHA256 = "70920811a4f8ad4328818682bca5c6469c1c942fab
|
|
|
4
4
|
export const GITLAB_RUNNER_VERSION = "19.4.1";
|
|
5
5
|
export const GITLAB_RUNNER_SHA256 = "5a159b36c5ff54fda9248e00bb0cdfb62c1112ab3741f5282c04a41e94da1306";
|
|
6
6
|
export const GITLAB_RUNNER_DEB_SHA256 = "47d2fd991e0b17af6f85af8fe3cc0cc429aae782d4f9982724c1f11a4de4a47c";
|
|
7
|
+
export const RUNNER_NODE_VERSION = "v24.21.0";
|
|
8
|
+
export const RUNNER_NODE_SHA256 = "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6";
|
|
7
9
|
export const GITLAB_HELPER_IMAGE = `registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:x86_64-v${GITLAB_RUNNER_VERSION}`;
|
|
8
10
|
export const GITLAB_DEFAULT_JOB_IMAGE = "ubuntu:24.04";
|
|
9
|
-
export const RUNNER_SPEC_REV = { github:
|
|
11
|
+
export const RUNNER_SPEC_REV = { github: 5, gitlab: 4 };
|
|
10
12
|
export const RUNNER_TIERS = ["small", "medium", "large"];
|
|
11
13
|
export const GITHUB_CONFIG_PATH = "/root/.ci-runner-config";
|
|
12
14
|
export const GITLAB_CONFIG_PATH = "/etc/gitlab-runner/config.toml";
|
|
@@ -79,8 +81,12 @@ export function runnerBuildSteps(s) {
|
|
|
79
81
|
export function runnerImageSpec(kind, tier) {
|
|
80
82
|
if (kind === "github") {
|
|
81
83
|
const url = `https://github.com/actions/runner/releases/download/v${ACTIONS_RUNNER_VERSION}/actions-runner-linux-x64-${ACTIONS_RUNNER_VERSION}.tar.gz`;
|
|
84
|
+
const nodeUrl = `https://nodejs.org/dist/${RUNNER_NODE_VERSION}/node-${RUNNER_NODE_VERSION}-linux-x64.tar.xz`;
|
|
82
85
|
const setup = [
|
|
83
86
|
APT_STEP,
|
|
87
|
+
`set -eu; curl -fsSL --retry 3 -o /tmp/node.tar.xz ${nodeUrl}; sha256sum /tmp/node.tar.xz; ` +
|
|
88
|
+
`echo '${RUNNER_NODE_SHA256} /tmp/node.tar.xz' | sha256sum -c -; ` +
|
|
89
|
+
"tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 --no-same-owner; rm -f /tmp/node.tar.xz",
|
|
84
90
|
"set -eu; id -u runner >/dev/null 2>&1 || useradd --create-home --shell /bin/bash --uid 1001 runner; " +
|
|
85
91
|
"groupadd -f docker; usermod -aG docker runner; " +
|
|
86
92
|
"printf 'runner ALL=(ALL) NOPASSWD:ALL\\nDefaults:runner !requiretty\\n' > /etc/sudoers.d/runner; " +
|
|
@@ -95,6 +101,8 @@ export function runnerImageSpec(kind, tier) {
|
|
|
95
101
|
];
|
|
96
102
|
const verify = `set -eu; cd ${GITHUB_RUNNER_ROOT}; setpriv --reuid=runner --regid=runner --init-groups env HOME=/home/runner ./config.sh --version; ` +
|
|
97
103
|
"git --version; jq --version; python3 --version; lsb_release -rs; " +
|
|
104
|
+
`test "$(node --version)" = ${RUNNER_NODE_VERSION}; ` +
|
|
105
|
+
"setpriv --reuid=runner --regid=runner --init-groups env HOME=/home/runner sh -c 'node --version && npm --version && npx --version'; " +
|
|
98
106
|
"setpriv --reuid=runner --regid=runner --init-groups env HOME=/home/runner sh -c 'docker --version && docker compose version && docker buildx version'; " +
|
|
99
107
|
`sudo -n -u runner true; id runner; test -x ${GITHUB_LAUNCHER}`;
|
|
100
108
|
const platform_setup = [
|
package/dist/runners.js
CHANGED
|
@@ -3,10 +3,10 @@ import { chmodSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, wr
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { configDir, PromigenceError } from "./sdk/index.js";
|
|
6
|
-
import { bool, client, nexts, str } from "./commands.js";
|
|
6
|
+
import { bool, buildPriceOf, client, nexts, str } from "./commands.js";
|
|
7
7
|
import { fitLine } from "./capacity.js";
|
|
8
8
|
import { TIERS } from "./generated.js";
|
|
9
|
-
import { COMPILED_NOTE, livePricing } from "./pricing.js";
|
|
9
|
+
import { buildPriceIn, buildPriceWords, COMPILED_NOTE, livePricing } from "./pricing.js";
|
|
10
10
|
import { emit, errorEnvelope, errorTextLines, okEnvelope, parseCount, parseDuration, parseUsd, usageError, write } from "./io.js";
|
|
11
11
|
import { escapeLine, jsonLine, sh } from "./term.js";
|
|
12
12
|
import { GITHUB_CONFIG_PATH, GITHUB_LAUNCHER, GITLAB_CONFIG_PATH, GITLAB_LAUNCHER, RUNNER_TIERS, isRunnerTier, runnerAlias, runnerBuildSteps, runnerImageSpec, } from "./runners-spec.js";
|
|
@@ -900,7 +900,8 @@ async function ensureSnapshot(ctx, c, kind, tier, mid) {
|
|
|
900
900
|
throw e;
|
|
901
901
|
}
|
|
902
902
|
const prices = await livePricing(c);
|
|
903
|
-
const
|
|
903
|
+
const most = buildPriceIn(prices, tier);
|
|
904
|
+
const price = `up to ${usd(most.most_usd)}, billed per second while it builds`;
|
|
904
905
|
const listed = prices.source === "compiled" ? ` (${COMPILED_NOTE})` : "";
|
|
905
906
|
const yes = bool(ctx, "--yes");
|
|
906
907
|
const what = `the ${kind === "github" ? "GitHub Actions" : "GitLab"} runner snapshot for ${tier} (${shape(tier)})`;
|
|
@@ -909,17 +910,21 @@ async function ensureSnapshot(ctx, c, kind, tier, mid) {
|
|
|
909
910
|
if (!interactive) {
|
|
910
911
|
if (mid)
|
|
911
912
|
return undefined;
|
|
912
|
-
throw usageError(`${what} is not built yet: building it costs ${price}
|
|
913
|
+
throw usageError(`${what} is not built yet: building it once costs ${price} (about two minutes)${listed}`, `Pass --yes to build it, or build it first: ${sh `promigence runners ${kind} --prepare --tier ${tier} --yes`}`);
|
|
913
914
|
}
|
|
914
915
|
const ok = await confirm(`${what} is not built yet. Build it now? One time, ${price}${listed}, about two minutes. [y/N] `);
|
|
915
916
|
if (!ok)
|
|
916
917
|
return undefined;
|
|
917
918
|
}
|
|
918
919
|
const b = await c.snapshots.create({ image: s.image, setup: runnerBuildSteps(s), verify: { cmd: s.verify }, tier, alias: s.alias });
|
|
919
|
-
const charged = b.deduped || b.joined ? 0 : typeof b.build_price === "number" ? b.build_price :
|
|
920
|
+
const charged = b.deduped || b.joined ? 0 : typeof b.build_price === "number" ? b.build_price : most.most_usd;
|
|
921
|
+
const said = buildPriceOf(b);
|
|
920
922
|
await write(process.stderr, ctx.mode === "json"
|
|
921
|
-
? jsonLine(process.stderr, {
|
|
922
|
-
|
|
923
|
+
? jsonLine(process.stderr, {
|
|
924
|
+
hint: "build_price", build_price: charged, unit: "USD", tier, alias: s.alias, build_id: b.build_id,
|
|
925
|
+
...(charged > 0 && said ? { per_second_usd: said.per_second_usd, max_seconds: said.max_s } : {}),
|
|
926
|
+
})
|
|
927
|
+
: `building ${s.alias}: ${charged === 0 ? `${usd(0)} (an identical build exists)` : said ? buildPriceWords(said) : usd(charged)}, build ${b.build_id}; jobs start once it is verified\n`);
|
|
923
928
|
let build = b;
|
|
924
929
|
if (!(b.deduped || b.state === "verified")) {
|
|
925
930
|
const w = await c.snapshots.waitBuild(b.build_id, { waitTimeoutMs: BUILD_WAIT_MS });
|
|
@@ -933,7 +938,7 @@ async function ensureSnapshot(ctx, c, kind, tier, mid) {
|
|
|
933
938
|
throw new PromigenceError({
|
|
934
939
|
code: "env_failed",
|
|
935
940
|
message: `the runner snapshot build ${b.build_id} ended ${build.state}${build.error?.message ? `: ${build.error.message}` : ""}${log.length ? ` (last log line: ${log[log.length - 1]})` : ""}`,
|
|
936
|
-
fix: `Nothing was
|
|
941
|
+
fix: `${typeof build.cost_usd === "number" && build.cost_usd > 0 ? `Nothing was stored; the time it ran was billed (${usd(build.cost_usd)}).` : "Nothing was stored or charged."} Read ${sh `promigence snapshot status ${b.build_id} --log 80`}, and tell support@promigence.ai if it is not your network.`,
|
|
937
942
|
exitCode: 3,
|
|
938
943
|
});
|
|
939
944
|
}
|
|
@@ -1208,7 +1213,7 @@ const runnersGithub = async (ctx) => {
|
|
|
1208
1213
|
if (snapshots.has(t))
|
|
1209
1214
|
return snapshots.get(t);
|
|
1210
1215
|
if (!bool(ctx, "--yes")) {
|
|
1211
|
-
poolRef?.note(`a job asks for promigence-${t}: its runner snapshot is not built (${sh `promigence runners github --prepare --tier ${t} --yes`}, ${usd(setup.prices.
|
|
1216
|
+
poolRef?.note(`a job asks for promigence-${t}: its runner snapshot is not built (${sh `promigence runners github --prepare --tier ${t} --yes`}, once: up to ${usd(buildPriceIn(setup.prices, t).most_usd)}, billed per second)`);
|
|
1212
1217
|
return undefined;
|
|
1213
1218
|
}
|
|
1214
1219
|
if (building.has(t))
|
package/dist/sdk/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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
1964
|
-
*
|
|
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
|
|
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/sdk/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
|
-
|
|
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
|
-
|
|
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/sdk/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.
|
|
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
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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 =
|
|
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/sdk/core.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
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 =
|
|
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/sdk/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 (
|
|
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/sdk/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 (
|
|
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;
|
package/dist/sdk/models.gen.d.ts
CHANGED
|
@@ -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
|
|
350
|
-
* `
|
|
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
|
-
*
|
|
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
|
|
416
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
567
|
-
*
|
|
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
|
-
*
|
|
3165
|
-
*
|
|
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,
|
|
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
|
|
5215
|
-
*
|
|
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/sdk/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
|
|
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
|
-
*
|
|
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
|
|
168
|
-
* `joined` when nothing new is charged. Without it,
|
|
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`
|
|
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
|
|
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/sdk/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"
|
|
69
|
-
|
|
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
|
|
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/dist/surface.js
CHANGED
|
@@ -975,7 +975,7 @@ export const COMMANDS = [
|
|
|
975
975
|
{ name: "--idle-timeout", arg: "DUR", summary: "stop an on-demand runner no job took (90s)" },
|
|
976
976
|
{ name: "--kill-on-exit", summary: "Ctrl-C cancels running jobs instead of waiting" },
|
|
977
977
|
{ name: "--prepare", summary: "build the --tier runner snapshot, then exit" },
|
|
978
|
-
{ name: "--yes", summary: "build a missing runner snapshot (
|
|
978
|
+
{ name: "--yes", summary: "build a missing runner snapshot (billed per second, per size) without asking" },
|
|
979
979
|
{ name: "--github-api", arg: "URL", summary: "GitHub Enterprise Server's API URL" },
|
|
980
980
|
{ name: "--poll", arg: "DUR", summary: "how often GitHub is asked for queued jobs", hidden: true },
|
|
981
981
|
json,
|
|
@@ -1001,7 +1001,7 @@ export const COMMANDS = [
|
|
|
1001
1001
|
{ name: "--idle-timeout", arg: "DUR", summary: "with --api-token-env: a runner no job took exits (90s)" },
|
|
1002
1002
|
{ name: "--kill-on-exit", summary: "Ctrl-C cancels running jobs instead of waiting" },
|
|
1003
1003
|
{ name: "--prepare", summary: "build the --tier runner snapshot, then exit" },
|
|
1004
|
-
{ name: "--yes", summary: "build a missing runner snapshot (
|
|
1004
|
+
{ name: "--yes", summary: "build a missing runner snapshot (billed per second) without asking" },
|
|
1005
1005
|
{ name: "--poll", arg: "DUR", summary: "how often GitLab is asked for pending jobs", hidden: true },
|
|
1006
1006
|
json,
|
|
1007
1007
|
],
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Promigence — promigence@0.1.0-beta.
|
|
1
|
+
# Promigence — promigence@0.1.0-beta.9
|
|
2
2
|
|
|
3
3
|
> Verified, reproducible, forkable execution environments for agents. The CLI is the
|
|
4
4
|
> `promigence` command (package `promigence`, which is also the TypeScript SDK).
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
|
|
128
128
|
---
|
|
129
129
|
|
|
130
|
-
# Promigence — agent guide (promigence@0.1.0-beta.
|
|
130
|
+
# Promigence — agent guide (promigence@0.1.0-beta.9)
|
|
131
131
|
|
|
132
132
|
Promigence is an **environment runtime**: verified, reproducible, forkable execution
|
|
133
133
|
environments, billed per second (vCPU + memory). The model is one line:
|
|
@@ -135,7 +135,7 @@ environments, billed per second (vCPU + memory). The model is one line:
|
|
|
135
135
|
verify → snapshot → fork(N) → exec → validity report
|
|
136
136
|
|
|
137
137
|
- **snapshot** — an image (pinned) + ordered `setup` steps + a `verify` command that, if given, must exit 0.
|
|
138
|
-
If verify fails nothing is stored
|
|
138
|
+
If verify fails nothing is stored; the build's own time is billed. A branch is not a pin.
|
|
139
139
|
- **fork** — a warm copy of that workspace, with its `start` service ready;
|
|
140
140
|
N at once, each checked before you get it.
|
|
141
141
|
- **validity report** — per sandbox, and this split is the product: **env** failures are ours
|
|
@@ -205,5 +205,5 @@ Highest-precedence code wins: 2 > 5 > 6 > 7/8/9/10 > 11 > 3 > 12 > 4 > 1 > 0.
|
|
|
205
205
|
· `promigence <command> --help` · https://promigence.ai
|
|
206
206
|
Same surface as MCP tools: `promigence mcp` (stdio; `claude mcp add promigence -- promigence mcp`).
|
|
207
207
|
|
|
208
|
-
Live in v0.1.0-beta.
|
|
208
|
+
Live in v0.1.0-beta.9: login · signup · logout · auth login · keys list/create/revoke · org list/create/switch/members/invite/invites/uninvite/resend/role/remove/leave/join/audit/rename/delete · project list/create/rename/delete/cap/move · me · billing card/usage/history/portal · doctor · ping · init · examples · templates +show · agent-guide · mcp · snapshot create/status/list/inspect/verify/export/delete/builds/alias · image resolve · github connect/status/disconnect/untrack · repos · gitlab connect/status/disconnect/untrack · run +quote/report/status/cancel/kill/replay/open/captures/list · fork · exec +output/ps/stdin/kill · files put/get/ls/search/mkdir/mv/cp/chmod/rm · agent start · sandbox kill/network/shell/ssh/list/pause/resume/snapshot/info/extend/autopause/probe · secrets set/list/delete · bench burst · runners github/gitlab · status · referrals +apply · pricing · credits +claim
|
|
209
209
|
Not on the hosted service yet: port expose/list/revoke (preview URLs).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promigence",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.9",
|
|
4
4
|
"description": "The fastest sandboxes for AI agents and code. Snapshot an environment once, fork it as often as you need, run commands in every copy, and get a report that tells environment failures from task failures. Billed per second, with a quote before a run and a hard spend cap. One package: the promigence command and the TypeScript SDK.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.promigence.ai",
|