@promigence/sdk 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +54 -0
- package/dist/blake3.d.ts +10 -0
- package/dist/blake3.js +111 -0
- package/dist/bundle.d.ts +96 -0
- package/dist/bundle.js +227 -0
- package/dist/ca-guard.d.ts +1 -0
- package/dist/ca-guard.js +166 -0
- package/dist/caller.d.ts +73 -0
- package/dist/caller.js +47 -0
- package/dist/cjs/blake3.d.ts +10 -0
- package/dist/cjs/blake3.js +116 -0
- package/dist/cjs/bundle.d.ts +96 -0
- package/dist/cjs/bundle.js +242 -0
- package/dist/cjs/ca-guard.d.ts +1 -0
- package/dist/cjs/ca-guard.js +174 -0
- package/dist/cjs/caller.d.ts +73 -0
- package/dist/cjs/caller.js +57 -0
- package/dist/cjs/client.d.ts +3015 -0
- package/dist/cjs/client.js +2617 -0
- package/dist/cjs/command-errors.d.ts +41 -0
- package/dist/cjs/command-errors.js +43 -0
- package/dist/cjs/context.d.ts +119 -0
- package/dist/cjs/context.js +598 -0
- package/dist/cjs/core.d.ts +285 -0
- package/dist/cjs/core.js +290 -0
- package/dist/cjs/credstore.d.ts +90 -0
- package/dist/cjs/credstore.js +219 -0
- package/dist/cjs/dispatcher.d.ts +58 -0
- package/dist/cjs/dispatcher.js +91 -0
- package/dist/cjs/github.d.ts +158 -0
- package/dist/cjs/github.js +84 -0
- package/dist/cjs/gitlab.d.ts +104 -0
- package/dist/cjs/gitlab.js +24 -0
- package/dist/cjs/handles.d.ts +82 -0
- package/dist/cjs/handles.js +139 -0
- package/dist/cjs/hint.d.ts +1 -0
- package/dist/cjs/hint.js +10 -0
- package/dist/cjs/http2.d.ts +206 -0
- package/dist/cjs/http2.js +1932 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/keyed.gen.d.ts +1 -0
- package/dist/cjs/keyed.gen.js +10 -0
- package/dist/cjs/models.gen.d.ts +6162 -0
- package/dist/cjs/models.gen.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/proxy.d.ts +40 -0
- package/dist/cjs/proxy.js +257 -0
- package/dist/cjs/pty.d.ts +171 -0
- package/dist/cjs/pty.js +504 -0
- package/dist/cjs/sandbox.d.ts +537 -0
- package/dist/cjs/sandbox.js +736 -0
- package/dist/cjs/ssh.d.ts +25 -0
- package/dist/cjs/ssh.js +117 -0
- package/dist/cjs/trace.d.ts +33 -0
- package/dist/cjs/trace.js +88 -0
- package/dist/cjs/transport-worker.d.ts +1 -0
- package/dist/cjs/transport-worker.js +468 -0
- package/dist/cjs/warm.d.ts +1 -0
- package/dist/cjs/warm.js +43 -0
- package/dist/client.d.ts +3015 -0
- package/dist/client.js +2583 -0
- package/dist/command-errors.d.ts +41 -0
- package/dist/command-errors.js +36 -0
- package/dist/context.d.ts +119 -0
- package/dist/context.js +588 -0
- package/dist/core.d.ts +285 -0
- package/dist/core.js +266 -0
- package/dist/credstore.d.ts +90 -0
- package/dist/credstore.js +206 -0
- package/dist/dispatcher.d.ts +58 -0
- package/dist/dispatcher.js +86 -0
- package/dist/github.d.ts +158 -0
- package/dist/github.js +78 -0
- package/dist/gitlab.d.ts +104 -0
- package/dist/gitlab.js +20 -0
- package/dist/handles.d.ts +82 -0
- package/dist/handles.js +135 -0
- package/dist/hint.d.ts +1 -0
- package/dist/hint.js +7 -0
- package/dist/http2.d.ts +206 -0
- package/dist/http2.js +1921 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +15 -0
- package/dist/keyed.gen.d.ts +1 -0
- package/dist/keyed.gen.js +7 -0
- package/dist/models.gen.d.ts +6162 -0
- package/dist/models.gen.js +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.js +249 -0
- package/dist/pty.d.ts +171 -0
- package/dist/pty.js +496 -0
- package/dist/sandbox.d.ts +537 -0
- package/dist/sandbox.js +725 -0
- package/dist/ssh.d.ts +25 -0
- package/dist/ssh.js +114 -0
- package/dist/trace.d.ts +33 -0
- package/dist/trace.js +80 -0
- package/dist/transport-worker.d.ts +1 -0
- package/dist/transport-worker.js +466 -0
- package/dist/warm.d.ts +1 -0
- package/dist/warm.js +40 -0
- package/package.json +54 -0
|
@@ -0,0 +1,3015 @@
|
|
|
1
|
+
/** `PromigenceClient` — the native client for the Promigence API. */
|
|
2
|
+
import { inspect, type InspectOptions } from "node:util";
|
|
3
|
+
import { PromigenceError, type CommandResult, type NetworkMode, type OptimizeFor, type Tier } from "./core.js";
|
|
4
|
+
import { type Bundle } from "./bundle.js";
|
|
5
|
+
import { type PtyExit, type PtySession } from "./pty.js";
|
|
6
|
+
import { type SshTunnel } from "./ssh.js";
|
|
7
|
+
import type * as M from "./models.gen.js";
|
|
8
|
+
import { type ConnectRequest, type ConnectTicket, type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
|
|
9
|
+
import { type GitlabConnect, type GitlabDisconnected, type GitlabRepos, type GitlabStatus, type GitlabUntracked } from "./gitlab.js";
|
|
10
|
+
export type FetchLike = typeof globalThis.fetch;
|
|
11
|
+
/**
|
|
12
|
+
* Default deadline for one `files get|put`.
|
|
13
|
+
*
|
|
14
|
+
* A transfer is bounded by the artefact's size, not by the 30 s that is generous for a control
|
|
15
|
+
* message: a 2 GB trace over a shaped link is minutes, and timing it out at 30 s would make the
|
|
16
|
+
* route useless for exactly the payload it exists to carry.
|
|
17
|
+
*/
|
|
18
|
+
export declare const FILE_TIMEOUT_MS: number;
|
|
19
|
+
export type RunState = M.RunState;
|
|
20
|
+
/**
|
|
21
|
+
* `queued` and `awaiting_capacity` are both waiting, and the difference is the whole point.
|
|
22
|
+
*
|
|
23
|
+
* `queued` = waiting for capacity that **exists**; typically milliseconds to a few seconds.
|
|
24
|
+
* `awaiting_capacity` = there is no free capacity and more is being **provisioned for you**; the
|
|
25
|
+
* run's `capacity` block carries an ETA. A waiting sandbox may also say why it waits: see
|
|
26
|
+
* {@link QueuedInfo}.
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
export type SandboxState = "queued" | "awaiting_capacity" | "ready" | "running" | "paused" | "ended";
|
|
30
|
+
/** A sandbox is still waiting for capacity, for either reason. */
|
|
31
|
+
export declare const isWaiting: (s: SandboxState | string) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Why a waiting sandbox is waiting.
|
|
34
|
+
*
|
|
35
|
+
* `capacity_arriving` — more capacity is being added for the run; `eta_s` says when it is
|
|
36
|
+
* expected, and `one_time` whether it covers everything waiting. `snapshot_held` — the sandbox can
|
|
37
|
+
* only start where its snapshot is held, and there is no room there yet; it starts when a sandbox
|
|
38
|
+
* there ends. `capacity_limit` — the service is at its capacity limit; it starts when room frees.
|
|
39
|
+
* `preparing` — the snapshot is being made ready to start; its first start takes a few seconds.
|
|
40
|
+
*/
|
|
41
|
+
export type QueuedReason = M.QueuedReason;
|
|
42
|
+
/**
|
|
43
|
+
* Why a waiting sandbox is waiting and how long that should take — on every sandbox record a run
|
|
44
|
+
* or sandbox route returns, and on the validity report's rows. All four fields are present
|
|
45
|
+
* together, or none: absent while the sandbox is not waiting, and while nothing more is known
|
|
46
|
+
* than that it is queued.
|
|
47
|
+
*/
|
|
48
|
+
export interface QueuedInfo {
|
|
49
|
+
queued_reason?: QueuedReason;
|
|
50
|
+
/**
|
|
51
|
+
* Seconds until the capacity it waits for is expected to be ready, with `capacity_arriving`.
|
|
52
|
+
* `null` with the other reasons, and once the sandbox has waited past the estimate it was first
|
|
53
|
+
* given (`queued_message` then says it is taking longer than expected).
|
|
54
|
+
*/
|
|
55
|
+
eta_s?: number | null;
|
|
56
|
+
/**
|
|
57
|
+
* A one-time wait: the capacity being added covers what is waiting, so once it is ready the
|
|
58
|
+
* rest of the run starts at once. An agent can decide to wait on this.
|
|
59
|
+
*/
|
|
60
|
+
one_time?: boolean;
|
|
61
|
+
/** The same, in words, ready to show a person. */
|
|
62
|
+
queued_message?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Capacity while this run waits. Attached to `GET /runs/:id` and `GET /runs/:id/sandboxes`, so
|
|
66
|
+
* the request a client is already long-polling carries the answer.
|
|
67
|
+
*/
|
|
68
|
+
export interface Capacity {
|
|
69
|
+
/** `ok` there is free capacity · `arriving` more is on its way for you · `blocked` no more is available right now. */
|
|
70
|
+
state: "ok" | "arriving" | "blocked";
|
|
71
|
+
awaiting_capacity: number;
|
|
72
|
+
/** Soonest a sandbox can start, in seconds. `null` when nothing is on its way. */
|
|
73
|
+
eta_s: number | null;
|
|
74
|
+
/** Why no more capacity is available right now. Not an ETA. */
|
|
75
|
+
blocked?: {
|
|
76
|
+
code: string;
|
|
77
|
+
reason: string;
|
|
78
|
+
at: string;
|
|
79
|
+
} | null;
|
|
80
|
+
/** Why this run's waiting sandboxes are waiting: the `queued_reason` they carry. */
|
|
81
|
+
queued_reason?: QueuedReason;
|
|
82
|
+
/** The same, in words, with the time it should take: the `queued_message` they carry. */
|
|
83
|
+
queued_message?: string;
|
|
84
|
+
}
|
|
85
|
+
export type Outcome = M.Outcome;
|
|
86
|
+
/** `GET /me/outcomes`: what the organization got this month, measured. */
|
|
87
|
+
export type Outcomes = M.Outcomes;
|
|
88
|
+
export interface Percentiles {
|
|
89
|
+
p50: number;
|
|
90
|
+
p95: number;
|
|
91
|
+
p99?: number;
|
|
92
|
+
}
|
|
93
|
+
/** Reply to `sandboxes.pause`. `bytes.allocated` is the disk space the paused sandbox occupies. */
|
|
94
|
+
export interface PauseResult {
|
|
95
|
+
ok: boolean;
|
|
96
|
+
sandbox_id: string;
|
|
97
|
+
state: "paused";
|
|
98
|
+
/** An opaque name for this pause. Resume by the sandbox id. */
|
|
99
|
+
paused_id: string;
|
|
100
|
+
/** Milliseconds the pause took, from the request to the answer. */
|
|
101
|
+
took_ms?: number;
|
|
102
|
+
bytes?: {
|
|
103
|
+
allocated: number;
|
|
104
|
+
};
|
|
105
|
+
/** False if the pause happened but could not be written down; worth retrying. */
|
|
106
|
+
recorded?: boolean;
|
|
107
|
+
/** True when the paused sandbox is stored so that it survives a hardware failure. */
|
|
108
|
+
durable?: boolean;
|
|
109
|
+
/** The pause was a no-op: it was already parked. */
|
|
110
|
+
already_paused?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Background execs (started with `background: true`) this pause ended. Their exec ids answer
|
|
113
|
+
* not found after resume; a process a foreground command detached is kept.
|
|
114
|
+
*/
|
|
115
|
+
background_execs_killed?: number;
|
|
116
|
+
/**
|
|
117
|
+
* `code: sentence` lines, present only when there is something to say. Today:
|
|
118
|
+
* `secrets_in_saved_state` — the sandbox was given the value of a secret, so its saved state
|
|
119
|
+
* can contain that value while it is paused.
|
|
120
|
+
*/
|
|
121
|
+
warnings?: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reply to `sandboxes.snapshot` — a snapshot taken from a sandbox that is still running.
|
|
125
|
+
*
|
|
126
|
+
* `still_running` is sent explicitly rather than implied by the absence of an error, because it
|
|
127
|
+
* is the whole difference between this call and `pause`.
|
|
128
|
+
*/
|
|
129
|
+
export interface SandboxSnapshotResult {
|
|
130
|
+
ok: boolean;
|
|
131
|
+
/** An ordinary snapshot id. Fork it like any other. */
|
|
132
|
+
snapshot_id: string;
|
|
133
|
+
sandbox_id: string;
|
|
134
|
+
alias?: string | null;
|
|
135
|
+
tier: string;
|
|
136
|
+
/** What the image occupies on disk. */
|
|
137
|
+
bytes: {
|
|
138
|
+
allocated: number;
|
|
139
|
+
};
|
|
140
|
+
/** Milliseconds the snapshot took, from the request to the answer. */
|
|
141
|
+
took_ms?: number;
|
|
142
|
+
/** Background processes ended to take the snapshot. */
|
|
143
|
+
background_execs_killed: number;
|
|
144
|
+
still_running: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Present only when there is something to say. Today: `secrets_in_saved_state` on a snapshot
|
|
147
|
+
* taken with `force` from a sandbox that was given a secret — the snapshot, and every sandbox
|
|
148
|
+
* started from it, can contain that value.
|
|
149
|
+
*/
|
|
150
|
+
warnings?: string[];
|
|
151
|
+
}
|
|
152
|
+
/** Reply to `sandboxes.resume`. `took_ms` is the time it took. */
|
|
153
|
+
export interface ResumeResult {
|
|
154
|
+
ok: boolean;
|
|
155
|
+
sandbox_id: string;
|
|
156
|
+
state: "ready";
|
|
157
|
+
/** The `paused_id` of the pause this restored. */
|
|
158
|
+
paused_id?: string;
|
|
159
|
+
/** Milliseconds the resume took, from the request to the answer. */
|
|
160
|
+
took_ms?: number;
|
|
161
|
+
recorded?: boolean;
|
|
162
|
+
}
|
|
163
|
+
export interface SandboxRef extends QueuedInfo {
|
|
164
|
+
sandbox_id: string;
|
|
165
|
+
state: SandboxState;
|
|
166
|
+
/**
|
|
167
|
+
* `runs.get` only: the verdict on the sandbox's terminal command when the sandbox outlived it.
|
|
168
|
+
* `completed` | `task_failed` with `killOnExit: false`: the command is over, the sandbox is live
|
|
169
|
+
* and billed until it is killed or expires. `cap_killed`: the run's spend cap (or the trial's
|
|
170
|
+
* credits) stopped the command and the sandbox was paused with its files and state; it goes on
|
|
171
|
+
* once the cap is raised (`sandboxes.resume(id, { spendCap })`) or credits are added. Absent
|
|
172
|
+
* otherwise.
|
|
173
|
+
*/
|
|
174
|
+
command_outcome?: "completed" | "task_failed" | "cap_killed";
|
|
175
|
+
[extra: string]: unknown;
|
|
176
|
+
}
|
|
177
|
+
/** How a grader's result is read. */
|
|
178
|
+
export type ScoreMode = M.ScoreMode;
|
|
179
|
+
/**
|
|
180
|
+
* Score the agent's work after the episode, in a clean copy of the snapshot with the agent's
|
|
181
|
+
* changes applied. `holdout_cmd` is your own second test set; the report carries the gap
|
|
182
|
+
* between the two pass rates. It runs in a copy of the same snapshot, so any file it reads from
|
|
183
|
+
* the snapshot was readable by the agent too — keep held-out tests out of the snapshot.
|
|
184
|
+
*
|
|
185
|
+
* The answer is a verdict and a score. What the grader prints is kept only when the run asks for
|
|
186
|
+
* it (`gradeOutput`): then its last 16 KiB come back in the report (`ReportSandbox.grade`).
|
|
187
|
+
*/
|
|
188
|
+
export interface GradeSpec {
|
|
189
|
+
cmd: string;
|
|
190
|
+
holdout_cmd?: string;
|
|
191
|
+
timeout_ms?: number;
|
|
192
|
+
score?: ScoreMode;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* What a sandbox's grader printed, in the run report when the run asked for it (`gradeOutput`):
|
|
196
|
+
* the end of its output, stdout and stderr together, at most its last 16 KiB. Never a held-out
|
|
197
|
+
* grader's.
|
|
198
|
+
*/
|
|
199
|
+
export interface GradeOutput {
|
|
200
|
+
output: string;
|
|
201
|
+
/** The grader printed more than `output` holds; the beginning was left out. */
|
|
202
|
+
output_truncated: boolean;
|
|
203
|
+
}
|
|
204
|
+
/** Per run: the two pass rates and the gap between them, in points. */
|
|
205
|
+
export interface GradeStats {
|
|
206
|
+
graded: number;
|
|
207
|
+
passed: number;
|
|
208
|
+
pass_pct: number;
|
|
209
|
+
holdout_graded?: number;
|
|
210
|
+
holdout_passed?: number;
|
|
211
|
+
holdout_pass_pct?: number;
|
|
212
|
+
/** `pass_pct − holdout_pass_pct`. Absent without a held-out set. */
|
|
213
|
+
gap_pct?: number;
|
|
214
|
+
grader_failed?: number;
|
|
215
|
+
}
|
|
216
|
+
export interface RunStartResponse {
|
|
217
|
+
run_id: string;
|
|
218
|
+
sandboxes: SandboxRef[];
|
|
219
|
+
/** Set by the server when an `Idempotency-Key` replayed an existing run. */
|
|
220
|
+
idempotent_replay?: boolean;
|
|
221
|
+
/** With `idempotent_replay`: how many seconds ago that run's answer was first given. */
|
|
222
|
+
idempotent_replay_age_s?: number;
|
|
223
|
+
[extra: string]: unknown;
|
|
224
|
+
}
|
|
225
|
+
export interface RunRecord {
|
|
226
|
+
run_id: string;
|
|
227
|
+
state: RunState;
|
|
228
|
+
snapshot_id?: string;
|
|
229
|
+
sandboxes?: SandboxRef[];
|
|
230
|
+
[extra: string]: unknown;
|
|
231
|
+
}
|
|
232
|
+
export interface ForkStats {
|
|
233
|
+
n: number;
|
|
234
|
+
queued_ms?: Percentiles;
|
|
235
|
+
ready_ms?: Percentiles;
|
|
236
|
+
e2e_ms?: Percentiles;
|
|
237
|
+
verify_each_ms?: Percentiles;
|
|
238
|
+
warm_retries: number;
|
|
239
|
+
first_attempt_success_pct: number;
|
|
240
|
+
final_success_pct: number;
|
|
241
|
+
percentile_method?: string;
|
|
242
|
+
replay?: {
|
|
243
|
+
n: number;
|
|
244
|
+
success_pct: number;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export interface Totals {
|
|
248
|
+
env_verified: number;
|
|
249
|
+
completed: number;
|
|
250
|
+
task_failed: number;
|
|
251
|
+
env_failed: number;
|
|
252
|
+
/** The grade could not be computed. Never counted as a task failure. */
|
|
253
|
+
grader_failed?: number;
|
|
254
|
+
snapshot_failed: number;
|
|
255
|
+
aborted_by_client: number;
|
|
256
|
+
/**
|
|
257
|
+
* Sandboxes Promigence support stopped (outcome `stopped_by_support`), each with its reason in its
|
|
258
|
+
* failure's message. Never counted as your own kill (`aborted_by_client`). Absent from an older API.
|
|
259
|
+
*/
|
|
260
|
+
stopped_by_support?: number;
|
|
261
|
+
expired: number;
|
|
262
|
+
cap_killed: number;
|
|
263
|
+
not_started: number;
|
|
264
|
+
evidence_missing: number;
|
|
265
|
+
}
|
|
266
|
+
export interface ExecRecord {
|
|
267
|
+
exec_id: string;
|
|
268
|
+
argv: string[];
|
|
269
|
+
exit_code?: number;
|
|
270
|
+
signal?: number;
|
|
271
|
+
wall_ms?: number;
|
|
272
|
+
cpu_ms?: number;
|
|
273
|
+
stdout_ref?: string;
|
|
274
|
+
stderr_ref?: string;
|
|
275
|
+
truncated?: boolean;
|
|
276
|
+
[extra: string]: unknown;
|
|
277
|
+
}
|
|
278
|
+
export interface ReportSandbox extends QueuedInfo {
|
|
279
|
+
sandbox_id: string;
|
|
280
|
+
state: SandboxState;
|
|
281
|
+
attempt?: number;
|
|
282
|
+
queued_ms?: number;
|
|
283
|
+
ready_ms?: number;
|
|
284
|
+
e2e_ms?: number;
|
|
285
|
+
env_verified?: boolean;
|
|
286
|
+
execs?: ExecRecord[];
|
|
287
|
+
outcome: Outcome;
|
|
288
|
+
/** The grader ran in a clean environment and produced a verdict. */
|
|
289
|
+
graded?: boolean;
|
|
290
|
+
/** The visible test set's verdict. This one decides the sandbox's outcome. */
|
|
291
|
+
passed?: boolean;
|
|
292
|
+
score?: number;
|
|
293
|
+
/** The held-out set, run in its own clean environment. Reported, never attributed. */
|
|
294
|
+
holdout_passed?: boolean;
|
|
295
|
+
holdout_score?: number;
|
|
296
|
+
/** What the grader printed, when the run asked for it (`gradeOutput`). Absent otherwise. */
|
|
297
|
+
grade?: GradeOutput;
|
|
298
|
+
failure?: {
|
|
299
|
+
category: "env" | "task" | "snapshot" | "grader" | "cap";
|
|
300
|
+
code: string;
|
|
301
|
+
evidence?: Record<string, unknown>;
|
|
302
|
+
};
|
|
303
|
+
warnings?: string[];
|
|
304
|
+
/** Seconds from hand-over to end minus paused time, rounded up — whatever the outcome. */
|
|
305
|
+
used_seconds?: number;
|
|
306
|
+
/** Seconds this sandbox bills: `used_seconds` capped at `committed_seconds`, 0 for env failures. */
|
|
307
|
+
billable_seconds?: number;
|
|
308
|
+
/** Seconds the run's cap reserved for it (raised only by `PATCH /sandboxes/:id`). */
|
|
309
|
+
committed_seconds?: number;
|
|
310
|
+
/** USD: `billable_seconds × per_second_usd`. */
|
|
311
|
+
cost?: number;
|
|
312
|
+
[extra: string]: unknown;
|
|
313
|
+
}
|
|
314
|
+
export interface RunReport {
|
|
315
|
+
schema: string;
|
|
316
|
+
run_id: string;
|
|
317
|
+
state: RunState;
|
|
318
|
+
snapshot_id?: string;
|
|
319
|
+
alias?: string;
|
|
320
|
+
labels?: Record<string, string>;
|
|
321
|
+
requested?: {
|
|
322
|
+
count: number;
|
|
323
|
+
timeout_ms: number;
|
|
324
|
+
verify_each: boolean;
|
|
325
|
+
network: NetworkMode;
|
|
326
|
+
allow_partial: boolean;
|
|
327
|
+
secrets?: string[];
|
|
328
|
+
grade?: GradeSpec;
|
|
329
|
+
grade_output?: boolean;
|
|
330
|
+
};
|
|
331
|
+
quote?: Quote;
|
|
332
|
+
spend_cap?: number;
|
|
333
|
+
started_at?: string;
|
|
334
|
+
finished_at?: string;
|
|
335
|
+
fork: ForkStats;
|
|
336
|
+
totals: Totals;
|
|
337
|
+
cost?: {
|
|
338
|
+
quoted: number;
|
|
339
|
+
charged: number;
|
|
340
|
+
unbilled: number;
|
|
341
|
+
cap_killed: boolean;
|
|
342
|
+
billable_seconds?: number;
|
|
343
|
+
per_second_usd?: number;
|
|
344
|
+
};
|
|
345
|
+
/** The two pass rates and the gap between them. Absent when the run was not graded. */
|
|
346
|
+
grade?: GradeStats;
|
|
347
|
+
sandboxes: ReportSandbox[];
|
|
348
|
+
[extra: string]: unknown;
|
|
349
|
+
}
|
|
350
|
+
/** A build of a snapshot (`POST /snapshots`, `GET /snapshots/builds/:id`). */
|
|
351
|
+
/** `built`: finished and stored with no verification command to run; done, like `verified`. */
|
|
352
|
+
export type BuildState = "queued" | "running" | "verified" | "built" | "rejected" | "failed";
|
|
353
|
+
export interface SecretRecord {
|
|
354
|
+
name: string;
|
|
355
|
+
/** Length of the stored value, so a caller can sanity-check without reading it. */
|
|
356
|
+
bytes: number;
|
|
357
|
+
/** On `secrets.set` only: `false` when an existing secret of that name was overwritten. */
|
|
358
|
+
created?: boolean;
|
|
359
|
+
/** On `secrets.list` only. */
|
|
360
|
+
created_at?: string;
|
|
361
|
+
updated_at?: string;
|
|
362
|
+
/**
|
|
363
|
+
* The hosts the secret is bound to. Absent when it is not bound: a sandbox that names it is
|
|
364
|
+
* then given the value. When present, requests the sandbox sends to the host's address in
|
|
365
|
+
* `urls` are authenticated with the secret, a request made straight to `https://<host>/` is not,
|
|
366
|
+
* and the value stays out of the sandbox unless a run asks for it (`raw`).
|
|
367
|
+
*/
|
|
368
|
+
hosts?: string[];
|
|
369
|
+
/**
|
|
370
|
+
* For each of `hosts`, in order, the address a sandbox sends its requests to, with the
|
|
371
|
+
* request's path added. On `set` and `list` of a bound secret.
|
|
372
|
+
*/
|
|
373
|
+
urls?: string[];
|
|
374
|
+
/**
|
|
375
|
+
* The variables a sandbox that names this secret is also given, each set to its host's
|
|
376
|
+
* address in `urls`: `OPENAI_BASE_URL` for `api.openai.com`, `ANTHROPIC_BASE_URL` for
|
|
377
|
+
* `api.anthropic.com`. Absent when there is none. A variable the caller sets itself wins.
|
|
378
|
+
*/
|
|
379
|
+
env?: Record<string, string>;
|
|
380
|
+
/** The request headers the secret authenticates: on `secrets.list`, when `hosts` is present. */
|
|
381
|
+
headers?: string[];
|
|
382
|
+
/** What the variable in a sandbox that names it holds instead of the value (on `set` of a bound secret). */
|
|
383
|
+
placeholder?: string;
|
|
384
|
+
/** `refused` when the value itself can never be given to a sandbox (present when `hosts` is). */
|
|
385
|
+
raw?: "allowed" | "refused";
|
|
386
|
+
/** Whether the value is kept out of the bound hosts' answers (present when `hosts` is). */
|
|
387
|
+
scan_responses?: boolean;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
|
|
391
|
+
* the answer gives in `urls` (and to `$OPENAI_BASE_URL` / `$ANTHROPIC_BASE_URL` for those
|
|
392
|
+
* providers) are authenticated with it, one sent straight to the host is not, and the value stays
|
|
393
|
+
* out of the sandbox unless a run asks for it (`raw: "refused"` forbids that; the default allows it).
|
|
394
|
+
*
|
|
395
|
+
* Every option left out keeps what the secret already has, so setting a bound secret again with
|
|
396
|
+
* a new value (a rotation) keeps its binding. `hosts: []` removes it.
|
|
397
|
+
*/
|
|
398
|
+
export interface SecretSetOpts {
|
|
399
|
+
/** Host names, e.g. `["api.openai.com"]` or `["*.openai.azure.com"]`. Never an address. */
|
|
400
|
+
hosts?: string[];
|
|
401
|
+
/** The request headers the key is sent in. Default: authorization, x-api-key, api-key, x-goog-api-key. */
|
|
402
|
+
headers?: string[];
|
|
403
|
+
/** `refused`: the value itself can never be given to a sandbox (`{ name, mode: "raw" }` is refused). */
|
|
404
|
+
raw?: "allowed" | "refused";
|
|
405
|
+
/** Mask the value out of responses from the bound hosts (default true). */
|
|
406
|
+
scanResponses?: boolean;
|
|
407
|
+
}
|
|
408
|
+
export interface SnapshotSource {
|
|
409
|
+
kind: "image" | "repo" | (string & {});
|
|
410
|
+
image?: string;
|
|
411
|
+
digest?: string;
|
|
412
|
+
[extra: string]: unknown;
|
|
413
|
+
}
|
|
414
|
+
export interface SnapshotBuild {
|
|
415
|
+
build_id: string;
|
|
416
|
+
state: BuildState;
|
|
417
|
+
snapshot_id?: string | null;
|
|
418
|
+
/** The name the snapshot holds now; `null` once it is deleted (see `snapshot_deleted`). */
|
|
419
|
+
alias?: string | null;
|
|
420
|
+
/** `true` when the snapshot this build made has since been deleted. */
|
|
421
|
+
snapshot_deleted?: boolean;
|
|
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
|
+
*/
|
|
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;
|
|
436
|
+
repo?: string;
|
|
437
|
+
commit?: string;
|
|
438
|
+
verification?: Record<string, unknown>;
|
|
439
|
+
/** A rejected build names the guard and the measured value; nothing was stored. */
|
|
440
|
+
error?: {
|
|
441
|
+
code?: string;
|
|
442
|
+
guard?: string;
|
|
443
|
+
message?: string;
|
|
444
|
+
values?: number[];
|
|
445
|
+
stored?: boolean;
|
|
446
|
+
} | null;
|
|
447
|
+
/** Set when an identical build already existed. */
|
|
448
|
+
deduped?: boolean;
|
|
449
|
+
/**
|
|
450
|
+
* Set when an identical build was already IN PROGRESS and this answer is that build: wait on
|
|
451
|
+
* it like your own. Unlike `deduped`, nothing is finished yet.
|
|
452
|
+
*/
|
|
453
|
+
joined?: boolean;
|
|
454
|
+
/** Server notes on this answer, e.g. that a joined build's alias was not applied. */
|
|
455
|
+
warnings?: string[];
|
|
456
|
+
/**
|
|
457
|
+
* `true` when this answer repeats an earlier create's (the same `Idempotency-Key`): nothing new
|
|
458
|
+
* was built or charged, whatever `build_price` says.
|
|
459
|
+
*/
|
|
460
|
+
idempotent_replay?: boolean;
|
|
461
|
+
/** With `idempotent_replay`: how many seconds ago that first answer was given. */
|
|
462
|
+
idempotent_replay_age_s?: number;
|
|
463
|
+
/** Image or repo. */
|
|
464
|
+
source?: SnapshotSource;
|
|
465
|
+
/** `false` for an image snapshot built without `verify` — stored, but nothing was verified. */
|
|
466
|
+
verified?: boolean;
|
|
467
|
+
next?: string[];
|
|
468
|
+
[extra: string]: unknown;
|
|
469
|
+
}
|
|
470
|
+
export interface SnapshotRecord {
|
|
471
|
+
snapshot_id: string;
|
|
472
|
+
alias?: string | null;
|
|
473
|
+
visibility?: string;
|
|
474
|
+
repo?: string;
|
|
475
|
+
commit?: string;
|
|
476
|
+
setup?: string[];
|
|
477
|
+
verify?: {
|
|
478
|
+
cmd: string;
|
|
479
|
+
allow_fast?: boolean;
|
|
480
|
+
};
|
|
481
|
+
tier?: string;
|
|
482
|
+
tier_assigned?: string;
|
|
483
|
+
build_id?: string;
|
|
484
|
+
verification?: Record<string, unknown>;
|
|
485
|
+
/** Image or repo. */
|
|
486
|
+
source?: SnapshotSource;
|
|
487
|
+
/** `false` for an image snapshot built without `verify` — stored, but nothing was verified. */
|
|
488
|
+
verified?: boolean;
|
|
489
|
+
created_at?: string;
|
|
490
|
+
/**
|
|
491
|
+
* Set when the snapshot can no longer be started, saying why (it was lost before it could be
|
|
492
|
+
* kept); a fork of it is refused `snapshot_unavailable`. Absent while it can be started.
|
|
493
|
+
*/
|
|
494
|
+
unavailable?: string;
|
|
495
|
+
/**
|
|
496
|
+
* The user commands, terminals and SSH sessions in its sandboxes run as when they name none:
|
|
497
|
+
* its image's own `USER`, or the `user` it was created with. Absent: root.
|
|
498
|
+
*/
|
|
499
|
+
user?: string | null;
|
|
500
|
+
[extra: string]: unknown;
|
|
501
|
+
}
|
|
502
|
+
/** `GET /snapshots/:id/manifest`: the snapshot's manifest, sealed (`buildBundle` turns it into a `.promigence` bundle). */
|
|
503
|
+
export interface SnapshotManifestResponse {
|
|
504
|
+
snapshot_id: string;
|
|
505
|
+
layers?: number;
|
|
506
|
+
apparent_bytes?: number;
|
|
507
|
+
transferable_bytes?: number;
|
|
508
|
+
cached_at?: string;
|
|
509
|
+
/**
|
|
510
|
+
* What a scrub of these bytes can verify. `verifiable: false` means
|
|
511
|
+
* the manifest records no digest for at least one artefact — the snapshot was built before
|
|
512
|
+
* digests were recorded, and no scrub can tell rot from the real bytes. `summary` is the line
|
|
513
|
+
* `promigence snapshot inspect` prints.
|
|
514
|
+
*/
|
|
515
|
+
integrity?: {
|
|
516
|
+
verifiable?: boolean;
|
|
517
|
+
chunked?: string[];
|
|
518
|
+
whole_file?: string[];
|
|
519
|
+
unverifiable?: string[];
|
|
520
|
+
bytes?: number;
|
|
521
|
+
summary?: string;
|
|
522
|
+
};
|
|
523
|
+
manifest: Record<string, unknown>;
|
|
524
|
+
[extra: string]: unknown;
|
|
525
|
+
}
|
|
526
|
+
export interface ExecResponse {
|
|
527
|
+
exec_id: string;
|
|
528
|
+
/**
|
|
529
|
+
* The command's exit code. `null` only when the exit carried neither a code nor a signal: the
|
|
530
|
+
* result is unknown, which is never success (a signal is reported as `128 + signal`).
|
|
531
|
+
*/
|
|
532
|
+
exit_code: number | null;
|
|
533
|
+
stdout: string;
|
|
534
|
+
stderr: string;
|
|
535
|
+
truncated?: boolean;
|
|
536
|
+
/** True when the command was killed at its own timeout. */
|
|
537
|
+
timed_out?: boolean;
|
|
538
|
+
/** True when the command was killed for running out of memory. */
|
|
539
|
+
oom_killed?: boolean;
|
|
540
|
+
/** Why it ended without an exit code of its own: `timeout`, `out_of_memory` or `signal`. */
|
|
541
|
+
reason?: "timeout" | "out_of_memory" | "signal";
|
|
542
|
+
/** With `encoding: "base64"`: the exact bytes of standard output (see `execOutputBytes`). */
|
|
543
|
+
stdout_b64?: string;
|
|
544
|
+
/** With `encoding: "base64"`: the exact bytes of standard error. */
|
|
545
|
+
stderr_b64?: string;
|
|
546
|
+
[extra: string]: unknown;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* A command's output as bytes: the exact bytes when it was asked for with `encoding: "base64"`,
|
|
550
|
+
* otherwise the UTF-8 of its text.
|
|
551
|
+
*/
|
|
552
|
+
export declare function execOutputBytes(r: ExecResponse, stream?: "stdout" | "stderr"): Uint8Array;
|
|
553
|
+
export interface SandboxRecord extends QueuedInfo {
|
|
554
|
+
sandbox_id: string;
|
|
555
|
+
state: SandboxState;
|
|
556
|
+
/**
|
|
557
|
+
* Why it is paused, when a limit paused it rather than a request: `"spend_cap"` (its run
|
|
558
|
+
* reached its spend cap; resume with `spendCap` raised) or `"credits_exhausted"` (the
|
|
559
|
+
* organization's credits ran out; add a card, then resume). Absent otherwise.
|
|
560
|
+
*/
|
|
561
|
+
pause_reason?: "spend_cap" | "credits_exhausted";
|
|
562
|
+
run_id?: string;
|
|
563
|
+
/**
|
|
564
|
+
* The snapshot it was started from, as its run names it (`runs.get(runId).snapshot_id`): the
|
|
565
|
+
* snapshot's id, or `promigence/base` for the built-in base.
|
|
566
|
+
*/
|
|
567
|
+
snapshot_id?: string;
|
|
568
|
+
/** When the sandbox was created, RFC 3339. */
|
|
569
|
+
created_at?: string;
|
|
570
|
+
outcome?: Outcome;
|
|
571
|
+
/**
|
|
572
|
+
* The labels this sandbox inherited from its run — the same map `runs.start({ labels })` took.
|
|
573
|
+
*
|
|
574
|
+
* Reported so that a caller who found a sandbox with `list({ labels })` can see what it
|
|
575
|
+
* matched, and so that one who did not can learn what a sandbox is tagged with at all.
|
|
576
|
+
*/
|
|
577
|
+
labels?: Record<string, string>;
|
|
578
|
+
/**
|
|
579
|
+
* Seconds of idleness after which this sandbox pauses itself; `0` when it never does.
|
|
580
|
+
* Set it with `sandboxes.patch(id, { autoPauseS })`.
|
|
581
|
+
*/
|
|
582
|
+
auto_pause_s?: number;
|
|
583
|
+
/**
|
|
584
|
+
* What counts as idle: `"requests"` (the default) or `"activity"`. Set it with
|
|
585
|
+
* `sandboxes.patch(id, { autoPauseMode })`.
|
|
586
|
+
*/
|
|
587
|
+
auto_pause_mode?: AutoPauseMode;
|
|
588
|
+
/** What this sandbox asks for on every start and resume (`runs.start({ optimizeFor })`). */
|
|
589
|
+
optimize_for?: OptimizeFor;
|
|
590
|
+
/**
|
|
591
|
+
* What its latest start or resume actually got; `null` before the first. It can differ from
|
|
592
|
+
* `optimize_for`, and the next resume asks again.
|
|
593
|
+
*/
|
|
594
|
+
optimize_for_applied?: OptimizeFor | null;
|
|
595
|
+
/**
|
|
596
|
+
* The lifetime this sandbox is bound to, in milliseconds of running time: time spent paused
|
|
597
|
+
* does not count. `setTimeout` / `sandboxes.patch(id, { timeoutMs })` extend it.
|
|
598
|
+
*/
|
|
599
|
+
timeout_ms?: number | null;
|
|
600
|
+
/**
|
|
601
|
+
* Milliseconds of running time since hand-over, paused time excluded: what `timeout_ms` is
|
|
602
|
+
* measured against, so `timeout_ms - elapsed_ms` is the time left. `null` before hand-over and
|
|
603
|
+
* once it has ended.
|
|
604
|
+
*/
|
|
605
|
+
elapsed_ms?: number | null;
|
|
606
|
+
[extra: string]: unknown;
|
|
607
|
+
}
|
|
608
|
+
/** `POST /images/resolve`. */
|
|
609
|
+
export interface ResolvedImage {
|
|
610
|
+
/** The pinned reference: your own name with `@sha256:…` on the end. Pass it to `snapshots.create`. */
|
|
611
|
+
image: string;
|
|
612
|
+
/** The digest `image` carries — the **index**'s, for a multi-arch tag. */
|
|
613
|
+
digest: string;
|
|
614
|
+
registry: string;
|
|
615
|
+
repository: string;
|
|
616
|
+
tag: string | null;
|
|
617
|
+
/** The media type at `digest`. Empty when the reference was already pinned. */
|
|
618
|
+
media_type: string;
|
|
619
|
+
/** Always `linux/amd64`: the one platform a sandbox runs. */
|
|
620
|
+
platform: string;
|
|
621
|
+
/** The `linux/amd64` child of an index; `null` for a single-platform manifest. */
|
|
622
|
+
platform_digest: string | null;
|
|
623
|
+
/** Every `os/arch` an index offered. Empty for a single manifest. */
|
|
624
|
+
platforms: string[];
|
|
625
|
+
/** `true` when the reference was already a digest and no registry was contacted. */
|
|
626
|
+
already_pinned: boolean;
|
|
627
|
+
ms?: number;
|
|
628
|
+
}
|
|
629
|
+
/** Options for {@link PromigenceClient.sandboxes}`.list`. */
|
|
630
|
+
export interface SandboxListOptions {
|
|
631
|
+
limit?: number;
|
|
632
|
+
/**
|
|
633
|
+
* Only sandboxes whose run carries **every** one of these labels.
|
|
634
|
+
*
|
|
635
|
+
* This is a server-side filter, and that is the point: `limit` bounds the rows the server
|
|
636
|
+
* *reads*, so filtering in your own code cannot find a sandbox older than the last page.
|
|
637
|
+
* It is how two replicas of one service converge on a single sandbox instead of each
|
|
638
|
+
* forking its own.
|
|
639
|
+
*/
|
|
640
|
+
labels?: Record<string, string>;
|
|
641
|
+
/** States to keep, server-side: `["running", "ready"]`. Empty = every state. */
|
|
642
|
+
state?: string[];
|
|
643
|
+
/** A `nextCursor` from an earlier page. Opaque: a client only ever echoes it back. */
|
|
644
|
+
cursor?: string;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* One page of a list route. `nextCursor` is `null` on the last page, **and that is the only
|
|
648
|
+
* reliable stop signal**: a short page is not one, because a server-side filter can empty a page
|
|
649
|
+
* that still has successors.
|
|
650
|
+
*/
|
|
651
|
+
export interface Page<T> {
|
|
652
|
+
items: T[];
|
|
653
|
+
nextCursor: string | null;
|
|
654
|
+
/** What the server actually used, which is not always what was asked for (it caps at 500). */
|
|
655
|
+
limit: number;
|
|
656
|
+
}
|
|
657
|
+
/** `GET /health` — unauthenticated: `ok`, `version`, `serving` and `location`; `build_sha` to a caller with a key. */
|
|
658
|
+
export interface HealthResponse {
|
|
659
|
+
ok?: boolean;
|
|
660
|
+
version?: string;
|
|
661
|
+
build_sha?: string;
|
|
662
|
+
/** Whether the service can start new sandboxes right now. */
|
|
663
|
+
serving?: boolean;
|
|
664
|
+
/** Where sandboxes run, in plain words (for example "US East Coast"); absent when not said. */
|
|
665
|
+
location?: string;
|
|
666
|
+
[extra: string]: unknown;
|
|
667
|
+
}
|
|
668
|
+
/** One service's row in `GET /status` (`api`, `sandboxes`): `up`, `degraded` or `down`. */
|
|
669
|
+
export interface ServiceStatus {
|
|
670
|
+
name: string;
|
|
671
|
+
state?: string;
|
|
672
|
+
last_health_at?: string | null;
|
|
673
|
+
[extra: string]: unknown;
|
|
674
|
+
}
|
|
675
|
+
/** `GET /status` — unauthenticated; the synthetic check window plus one row per service. */
|
|
676
|
+
export interface StatusResponse {
|
|
677
|
+
checks?: unknown;
|
|
678
|
+
services?: ServiceStatus[];
|
|
679
|
+
[extra: string]: unknown;
|
|
680
|
+
}
|
|
681
|
+
/** One API key (`KeyView` in the API reference). `api_key` is present only in the answer that minted it. */
|
|
682
|
+
export interface KeyRecord {
|
|
683
|
+
key_id: string;
|
|
684
|
+
prefix: string;
|
|
685
|
+
name: string;
|
|
686
|
+
created_at: string;
|
|
687
|
+
created_by?: string | null;
|
|
688
|
+
last_used_at?: string | null;
|
|
689
|
+
revoked?: boolean | null;
|
|
690
|
+
revoked_at?: string | null;
|
|
691
|
+
/** When it stops working; `null` or absent = never. */
|
|
692
|
+
expires_at?: string | null;
|
|
693
|
+
/** The key that minted it; revoking that key revokes this one. */
|
|
694
|
+
minted_by?: string | null;
|
|
695
|
+
/** True for the key making this call. */
|
|
696
|
+
current?: boolean | null;
|
|
697
|
+
/** The secret, in the create answer only; never retrievable again. */
|
|
698
|
+
api_key?: string | null;
|
|
699
|
+
/** On a revoke: the keys this one minted, revoked with it. */
|
|
700
|
+
also_revoked?: string[] | null;
|
|
701
|
+
}
|
|
702
|
+
/** A key minted in an organization's project (`OrgKeyView`). `api_key` is shown once. */
|
|
703
|
+
export interface OrgKeyRecord {
|
|
704
|
+
key_id: string;
|
|
705
|
+
prefix: string;
|
|
706
|
+
name: string;
|
|
707
|
+
org_id: string;
|
|
708
|
+
project_id: string;
|
|
709
|
+
service: boolean;
|
|
710
|
+
created_at: string;
|
|
711
|
+
created_by?: string | null;
|
|
712
|
+
expires_at?: string | null;
|
|
713
|
+
api_key: string;
|
|
714
|
+
}
|
|
715
|
+
/** A key's lifetime: days from now, or a date (`2027-01-31`) / UTC time ending in `Z`. Neither = never expires. */
|
|
716
|
+
export interface KeyExpiry {
|
|
717
|
+
expiresInDays?: number;
|
|
718
|
+
expiresAt?: string;
|
|
719
|
+
}
|
|
720
|
+
export interface OrgRecord {
|
|
721
|
+
org_id: string;
|
|
722
|
+
name: string;
|
|
723
|
+
role: string;
|
|
724
|
+
default: boolean;
|
|
725
|
+
card_on_file: boolean;
|
|
726
|
+
projects: number;
|
|
727
|
+
members: number;
|
|
728
|
+
created_at: string;
|
|
729
|
+
}
|
|
730
|
+
export interface ProjectRecord {
|
|
731
|
+
project_id: string;
|
|
732
|
+
org_id: string;
|
|
733
|
+
name: string;
|
|
734
|
+
default: boolean;
|
|
735
|
+
keys: number;
|
|
736
|
+
/** The most the project may bill in a calendar month (UTC); `null` = no cap. */
|
|
737
|
+
monthly_cap_usd?: number | null;
|
|
738
|
+
created_at: string;
|
|
739
|
+
}
|
|
740
|
+
export interface MemberRecord {
|
|
741
|
+
user_id: string;
|
|
742
|
+
email: string;
|
|
743
|
+
role: string;
|
|
744
|
+
joined_at: string;
|
|
745
|
+
/** True for the person this call is made as. */
|
|
746
|
+
you: boolean;
|
|
747
|
+
}
|
|
748
|
+
export interface InviteRecord {
|
|
749
|
+
invite_id: string;
|
|
750
|
+
org_id: string;
|
|
751
|
+
org_name: string;
|
|
752
|
+
email: string;
|
|
753
|
+
role: string;
|
|
754
|
+
invited_by: string;
|
|
755
|
+
created_at: string;
|
|
756
|
+
}
|
|
757
|
+
/** `GET /credits/offer`: the account's free credit. It is claimed in a browser, never by an API call. */
|
|
758
|
+
export interface CreditOffer {
|
|
759
|
+
/** `claimable` | `claimed` | `pending_review` | `needs_card` | `not_eligible` | `offer_ended` | `none` */
|
|
760
|
+
state: string;
|
|
761
|
+
credit_usd: number;
|
|
762
|
+
card_bonus_usd: number;
|
|
763
|
+
/** USD charged to confirm Apple Pay, Google Pay or Amazon Pay when one is added (added to the credit). */
|
|
764
|
+
method_check_usd?: number;
|
|
765
|
+
expires_at?: string | null;
|
|
766
|
+
campaign?: string | null;
|
|
767
|
+
message: string;
|
|
768
|
+
}
|
|
769
|
+
/** `GET /billing` — the customer's own month to date (`promigence billing usage`). */
|
|
770
|
+
/** `GET /referrals` (`ReferralStatus` in the API reference). */
|
|
771
|
+
export interface ReferralStatus {
|
|
772
|
+
/** `"on"` while an offer runs, else `"off"`. */
|
|
773
|
+
program: "on" | "off" | string;
|
|
774
|
+
/** Your code; `null` while the program is off. */
|
|
775
|
+
code: string | null;
|
|
776
|
+
offer: {
|
|
777
|
+
/** E.g. `"Give $50, get $100"`. */
|
|
778
|
+
headline: string;
|
|
779
|
+
give_usd: number;
|
|
780
|
+
get_usd: number;
|
|
781
|
+
credit_expiry_days: number | null;
|
|
782
|
+
hold_days: number;
|
|
783
|
+
qualifying_plans: string[];
|
|
784
|
+
ends_at: string | null;
|
|
785
|
+
} | null;
|
|
786
|
+
referrals: {
|
|
787
|
+
pending: number;
|
|
788
|
+
held: number;
|
|
789
|
+
rewarded: number;
|
|
790
|
+
under_review: number;
|
|
791
|
+
other: number;
|
|
792
|
+
};
|
|
793
|
+
earned_usd: number;
|
|
794
|
+
referred_by: {
|
|
795
|
+
status: string;
|
|
796
|
+
reward_usd: number;
|
|
797
|
+
rewarded_at: string | null;
|
|
798
|
+
} | null;
|
|
799
|
+
expiring_credits: Array<{
|
|
800
|
+
remaining_usd: number;
|
|
801
|
+
granted_usd: number;
|
|
802
|
+
expires_at: string;
|
|
803
|
+
}>;
|
|
804
|
+
}
|
|
805
|
+
/** `POST /referrals/apply` (`ReferralApplied`), and a first sign-in's `referral`. */
|
|
806
|
+
export interface ReferralApplied {
|
|
807
|
+
/** `pending` | `under_review` | `not_applied` (sign-in only). */
|
|
808
|
+
status: string;
|
|
809
|
+
detail?: string;
|
|
810
|
+
code: string;
|
|
811
|
+
reward_usd: number;
|
|
812
|
+
qualifying_plans: string[];
|
|
813
|
+
}
|
|
814
|
+
/** One sandbox shape and its price in a {@link PricingView}. */
|
|
815
|
+
export interface PricingTier {
|
|
816
|
+
/** The name `--tier` takes. */
|
|
817
|
+
name: string;
|
|
818
|
+
vcpus: number;
|
|
819
|
+
mem_gib: number;
|
|
820
|
+
disk_gib: number;
|
|
821
|
+
/** USD per second, on demand. */
|
|
822
|
+
per_second_usd: number;
|
|
823
|
+
/** USD per hour, on demand. */
|
|
824
|
+
per_hour_usd: number;
|
|
825
|
+
/** USD per second for a run started `preemptible`. */
|
|
826
|
+
preemptible_per_second_usd: number;
|
|
827
|
+
/** Within the account's largest sandbox shape (`limits.max_sandbox_vcpus`). */
|
|
828
|
+
available: boolean;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* `GET /pricing` — the price list, read live on every call from what billing charges. Without a
|
|
832
|
+
* key: what a new account gets (`scope: "new_account"`). With one: what that key's organization
|
|
833
|
+
* pays (`scope: "organization"`). A run is billed at the rates in force when it was created.
|
|
834
|
+
*/
|
|
835
|
+
export interface PricingView {
|
|
836
|
+
/** `promigence.pricing/v1`. */
|
|
837
|
+
schema: string;
|
|
838
|
+
/** When these values were read (RFC 3339): read them again rather than keeping a copy. */
|
|
839
|
+
as_of: string;
|
|
840
|
+
/** `usd`. */
|
|
841
|
+
currency: string;
|
|
842
|
+
scope: "new_account" | "organization" | string;
|
|
843
|
+
/** `null` for `new_account`. */
|
|
844
|
+
org_id: string | null;
|
|
845
|
+
plan: {
|
|
846
|
+
plan_id: string;
|
|
847
|
+
display_name: string;
|
|
848
|
+
/** `null` when no plan is assigned and the standard values apply. */
|
|
849
|
+
version: number | null;
|
|
850
|
+
/** Work is paid from free credit, within the trial's limits, until a card is added. */
|
|
851
|
+
on_trial: boolean;
|
|
852
|
+
};
|
|
853
|
+
/** `per_second`. */
|
|
854
|
+
billing: string;
|
|
855
|
+
granularity_seconds: number;
|
|
856
|
+
min_billable_seconds: number;
|
|
857
|
+
rates: {
|
|
858
|
+
vcpu_second_usd: number;
|
|
859
|
+
gib_second_usd: number;
|
|
860
|
+
preemptible_vcpu_second_usd: number;
|
|
861
|
+
preemptible_gib_second_usd: number;
|
|
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
|
+
*/
|
|
866
|
+
build_usd: number;
|
|
867
|
+
/** Per GiB of data out above the month's allowance; `null` when not priced. */
|
|
868
|
+
egress_gb_usd: number | null;
|
|
869
|
+
/** Per GiB-month of stored snapshot data; `null` while not priced. */
|
|
870
|
+
storage_gb_month_usd: number | null;
|
|
871
|
+
};
|
|
872
|
+
/** Every shape on sale, smallest first. */
|
|
873
|
+
tiers: PricingTier[];
|
|
874
|
+
limits: {
|
|
875
|
+
/** `null` = no limit. */
|
|
876
|
+
max_concurrency: number | null;
|
|
877
|
+
/** The largest sandbox shape, in vCPU; `null` = no limit. */
|
|
878
|
+
max_sandbox_vcpus: number | null;
|
|
879
|
+
/** The longest a sandbox may live, extensions included. */
|
|
880
|
+
max_session_s: number;
|
|
881
|
+
/** A sandbox's timeout when its run names none. */
|
|
882
|
+
default_timeout_s: number;
|
|
883
|
+
max_count_per_run: number;
|
|
884
|
+
/** Data out included each month, GiB; `null` = none included. */
|
|
885
|
+
egress_gb_month: number | null;
|
|
886
|
+
/** Where data out stops each month, GiB; `null` = it does not stop. */
|
|
887
|
+
max_egress_gb_month: number | null;
|
|
888
|
+
/** Outbound mail ports open. */
|
|
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;
|
|
892
|
+
};
|
|
893
|
+
/** The free credit a new account gets (an account's own: `GET /credits/offer`). */
|
|
894
|
+
free_credit: {
|
|
895
|
+
/** Claimed once, in the browser; 0 once the offer has ended. */
|
|
896
|
+
signup_credit_usd: number;
|
|
897
|
+
/** More when a card is first added; 0 once the offer has ended. */
|
|
898
|
+
card_bonus_usd: number;
|
|
899
|
+
expiry_days_after_claim: number | null;
|
|
900
|
+
/** No free credit outlives this instant; `null` = no fixed end. */
|
|
901
|
+
expires_by: string | null;
|
|
902
|
+
terms_url: string;
|
|
903
|
+
/** New accounts need an invite code; a code may carry its own credit. */
|
|
904
|
+
invite_required: boolean;
|
|
905
|
+
};
|
|
906
|
+
monthly_fee_usd: number;
|
|
907
|
+
/** Usage the monthly fee includes each month, USD. */
|
|
908
|
+
included_credits_usd: number;
|
|
909
|
+
monthly_minimum_usd: number;
|
|
910
|
+
}
|
|
911
|
+
export interface BillingUsage {
|
|
912
|
+
org_id?: string;
|
|
913
|
+
/**
|
|
914
|
+
* `false` when this deployment takes no card: `billing.checkout()` / `portal()` then answer
|
|
915
|
+
* `billing_unavailable`, so nothing should send a customer to them.
|
|
916
|
+
*/
|
|
917
|
+
billing_enabled?: boolean;
|
|
918
|
+
/**
|
|
919
|
+
* The payment state (`free` with no subscription, `payg` with one). The plan the limits come
|
|
920
|
+
* from — the name the refusals use — is `me().plan`.
|
|
921
|
+
*/
|
|
922
|
+
plan?: string;
|
|
923
|
+
card_on_file?: boolean;
|
|
924
|
+
card?: {
|
|
925
|
+
brand?: string;
|
|
926
|
+
last4?: string;
|
|
927
|
+
} | null;
|
|
928
|
+
/** Set when the card on file does not unlock paid limits for this organization (it already pays for another one,
|
|
929
|
+
* it is being confirmed with a $1 charge, or that charge did not go through). */
|
|
930
|
+
card_note?: string | null;
|
|
931
|
+
/** The $1 that confirms Apple Pay, Google Pay or Amazon Pay on file: `confirming`, `paid` or `failed`. */
|
|
932
|
+
method_check?: {
|
|
933
|
+
method: string;
|
|
934
|
+
status: string;
|
|
935
|
+
amount_usd: number;
|
|
936
|
+
bonus_usd?: number | null;
|
|
937
|
+
} | null;
|
|
938
|
+
suspended?: boolean;
|
|
939
|
+
suspend_reason?: string | null;
|
|
940
|
+
period?: {
|
|
941
|
+
start?: string;
|
|
942
|
+
end?: string;
|
|
943
|
+
basis?: string;
|
|
944
|
+
};
|
|
945
|
+
usage?: {
|
|
946
|
+
total?: number;
|
|
947
|
+
[extra: string]: unknown;
|
|
948
|
+
};
|
|
949
|
+
trial?: {
|
|
950
|
+
credits_remaining?: number;
|
|
951
|
+
[extra: string]: unknown;
|
|
952
|
+
} | null;
|
|
953
|
+
upcoming_invoice?: {
|
|
954
|
+
amount_due?: number | null;
|
|
955
|
+
currency?: string;
|
|
956
|
+
period_end?: string | null;
|
|
957
|
+
} | null;
|
|
958
|
+
upcoming_invoice_error?: string | null;
|
|
959
|
+
[extra: string]: unknown;
|
|
960
|
+
}
|
|
961
|
+
/** One row of `GET /billing/history`: a credit the organization received, or a payment. */
|
|
962
|
+
export type BillingHistoryEntry = M.BillingHistoryEntry;
|
|
963
|
+
/** What `billing.historyPage()` lists: everything, only credits, or only payments. */
|
|
964
|
+
export type BillingHistoryKind = "all" | "credits" | "payments";
|
|
965
|
+
/** One metered row of `GET /me/ledger`. */
|
|
966
|
+
export interface LedgerEntry {
|
|
967
|
+
entry_id: string;
|
|
968
|
+
kind: string;
|
|
969
|
+
run_id?: string | null;
|
|
970
|
+
subject?: string;
|
|
971
|
+
tier?: string;
|
|
972
|
+
seconds?: number;
|
|
973
|
+
sandbox_id?: string | null;
|
|
974
|
+
interim?: boolean;
|
|
975
|
+
amount?: number;
|
|
976
|
+
detail?: unknown;
|
|
977
|
+
created_at?: string;
|
|
978
|
+
[extra: string]: unknown;
|
|
979
|
+
}
|
|
980
|
+
/** One exec's stored output, resolved by `exec_id` (`promigence exec output`). */
|
|
981
|
+
export interface ExecOutput {
|
|
982
|
+
exec_id: string;
|
|
983
|
+
sandbox_id?: string;
|
|
984
|
+
run_id?: string;
|
|
985
|
+
argv?: string[];
|
|
986
|
+
exit_code?: number | null;
|
|
987
|
+
stdout: string;
|
|
988
|
+
stderr: string;
|
|
989
|
+
truncated?: boolean;
|
|
990
|
+
/** Bytes actually returned after `range`, and the full size before it. */
|
|
991
|
+
bytes: number;
|
|
992
|
+
total_bytes: number;
|
|
993
|
+
range?: {
|
|
994
|
+
from: number;
|
|
995
|
+
to: number;
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/** One line of a quote: N sandboxes at `per_second_usd` for up to `seconds_per_sandbox` each. */
|
|
999
|
+
export interface QuoteLine {
|
|
1000
|
+
sandbox_count: number;
|
|
1001
|
+
per_second_usd: number;
|
|
1002
|
+
seconds_per_sandbox: number;
|
|
1003
|
+
}
|
|
1004
|
+
/** The quote: per-second billing, `max_cost = Σ sandbox_count × seconds_per_sandbox × per_second_usd`. */
|
|
1005
|
+
export interface Quote {
|
|
1006
|
+
max_cost: number;
|
|
1007
|
+
/** `vcpus × vcpu_second_usd + mem_gib × gib_second_usd`. */
|
|
1008
|
+
per_second_usd: number;
|
|
1009
|
+
vcpu_second_usd: number;
|
|
1010
|
+
gib_second_usd: number;
|
|
1011
|
+
vcpus: number;
|
|
1012
|
+
mem_gib: number;
|
|
1013
|
+
/** `ceil(timeout_ms / 1000)`: the most one sandbox can bill. */
|
|
1014
|
+
seconds_per_sandbox: number;
|
|
1015
|
+
breakdown: QuoteLine[];
|
|
1016
|
+
}
|
|
1017
|
+
/** `POST /runs/quote` — the bill before the run starts. */
|
|
1018
|
+
export interface RunQuote {
|
|
1019
|
+
snapshot: string;
|
|
1020
|
+
snapshot_id?: string | null;
|
|
1021
|
+
count: number;
|
|
1022
|
+
timeout_ms: number;
|
|
1023
|
+
tier: string;
|
|
1024
|
+
billing: "per_second";
|
|
1025
|
+
granularity_seconds: number;
|
|
1026
|
+
min_billable_seconds: number;
|
|
1027
|
+
vcpus: number;
|
|
1028
|
+
mem_gib: number;
|
|
1029
|
+
vcpu_second_usd: number;
|
|
1030
|
+
gib_second_usd: number;
|
|
1031
|
+
per_second_usd: number;
|
|
1032
|
+
per_hour_usd: number;
|
|
1033
|
+
seconds_per_sandbox: number;
|
|
1034
|
+
/** Rounded to cents; `quote.max_cost` carries full precision. */
|
|
1035
|
+
max_cost: number;
|
|
1036
|
+
breakdown: QuoteLine[];
|
|
1037
|
+
quote: Quote;
|
|
1038
|
+
spend_cap: number;
|
|
1039
|
+
/** Trial orgs: credits not already reserved by live sandboxes. `null` with a card. */
|
|
1040
|
+
credits_available?: number | null;
|
|
1041
|
+
/** Things that will surprise the invoice if unread: a free bench tier, a quote already over
|
|
1042
|
+
* the cap, a quote the trial credits do not cover. */
|
|
1043
|
+
warnings: string[];
|
|
1044
|
+
}
|
|
1045
|
+
/** `POST /runs/:id/replay` — client-triggered, never automatic. */
|
|
1046
|
+
export interface ReplayResponse {
|
|
1047
|
+
ok: boolean;
|
|
1048
|
+
run_id: string;
|
|
1049
|
+
replayed: number;
|
|
1050
|
+
sandboxes: Array<{
|
|
1051
|
+
sandbox_id: string;
|
|
1052
|
+
replay_of: string;
|
|
1053
|
+
state: string;
|
|
1054
|
+
}>;
|
|
1055
|
+
detail?: string;
|
|
1056
|
+
}
|
|
1057
|
+
export interface Capture {
|
|
1058
|
+
snapshot_id: string;
|
|
1059
|
+
run_id: string;
|
|
1060
|
+
sandbox_id: string;
|
|
1061
|
+
outcome: string;
|
|
1062
|
+
code: string;
|
|
1063
|
+
/** What keeping it costs — and what deleting it gives back. */
|
|
1064
|
+
bytes: number;
|
|
1065
|
+
captured_at: string;
|
|
1066
|
+
/** When it goes at the latest; it can stop being openable sooner (`available`). */
|
|
1067
|
+
expires_at: string;
|
|
1068
|
+
/** Whether `runs.open` can still open it; `false` once it was lost before it expired. */
|
|
1069
|
+
available?: boolean;
|
|
1070
|
+
}
|
|
1071
|
+
/** `GET /runs/:id/captures`. */
|
|
1072
|
+
export interface CapturesResponse {
|
|
1073
|
+
ok: boolean;
|
|
1074
|
+
run_id: string;
|
|
1075
|
+
captures: Capture[];
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* `POST /runs/:id/open` — a NEW live sandbox forked from a failure, with its own id and its own
|
|
1079
|
+
* billing clock. It is not the dead sandbox resurrected.
|
|
1080
|
+
*/
|
|
1081
|
+
export interface OpenResponse {
|
|
1082
|
+
ok: boolean;
|
|
1083
|
+
run_id: string;
|
|
1084
|
+
/** The new sandbox. Exec against this one. */
|
|
1085
|
+
sandbox_id: string;
|
|
1086
|
+
state: string;
|
|
1087
|
+
/** The sandbox whose failure it was forked from. */
|
|
1088
|
+
opened_from: string;
|
|
1089
|
+
snapshot_id: string;
|
|
1090
|
+
/** When that sandbox failed, and how. */
|
|
1091
|
+
failed_at: string;
|
|
1092
|
+
outcome: string;
|
|
1093
|
+
code: string;
|
|
1094
|
+
expires_at: string;
|
|
1095
|
+
committed_seconds: number;
|
|
1096
|
+
timeout_ms: number;
|
|
1097
|
+
max_cost: number;
|
|
1098
|
+
ready_ms?: number;
|
|
1099
|
+
detail?: string;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* `PATCH /sandboxes/:id` — extend one live sandbox inside the cap, and/or set its idle rule.
|
|
1103
|
+
*
|
|
1104
|
+
* Everything but `ok` and `sandbox_id` is optional because either field may be sent on its own:
|
|
1105
|
+
* a call that only set `autoPauseS` answers with the idle window and nothing about the cap,
|
|
1106
|
+
* since nothing about the cap changed.
|
|
1107
|
+
*/
|
|
1108
|
+
export interface SandboxPatchResponse {
|
|
1109
|
+
ok: boolean;
|
|
1110
|
+
sandbox_id: string;
|
|
1111
|
+
run_id?: string;
|
|
1112
|
+
timeout_ms?: number;
|
|
1113
|
+
committed_seconds?: number;
|
|
1114
|
+
cost?: {
|
|
1115
|
+
added: number;
|
|
1116
|
+
charged: number;
|
|
1117
|
+
live_commitment: number;
|
|
1118
|
+
ceiling: number;
|
|
1119
|
+
remaining: number;
|
|
1120
|
+
};
|
|
1121
|
+
/** Seconds of idleness after which the sandbox pauses itself; `0` when it never does. */
|
|
1122
|
+
auto_pause_s?: number;
|
|
1123
|
+
/** What counts as idle: `"requests"` or `"activity"`. */
|
|
1124
|
+
auto_pause_mode?: AutoPauseMode;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* What the idle rule counts as idle. `"requests"` (the default): nobody has called the sandbox
|
|
1128
|
+
* for the window and no command, terminal or SSH session is open. `"activity"`: all of that, and
|
|
1129
|
+
* for the whole window nothing inside used CPU or sent or received a byte, and no network
|
|
1130
|
+
* connection to the outside is open — for an agent that runs inside the sandbox on its own.
|
|
1131
|
+
*/
|
|
1132
|
+
export type AutoPauseMode = M.AutoPauseMode;
|
|
1133
|
+
/** `POST /snapshots/:id/verify` — re-run the recorded `verify` in a fresh fork. */
|
|
1134
|
+
export interface SnapshotVerifyResponse {
|
|
1135
|
+
run_id: string;
|
|
1136
|
+
snapshot_id: string;
|
|
1137
|
+
alias?: string | null;
|
|
1138
|
+
verify: string;
|
|
1139
|
+
workspace: string;
|
|
1140
|
+
/** The most the fork can cost (`seconds × per_second_usd`) — printed before waiting, like `build_price`. */
|
|
1141
|
+
price: number;
|
|
1142
|
+
seconds: number;
|
|
1143
|
+
per_second_usd: number;
|
|
1144
|
+
tier: string;
|
|
1145
|
+
count: number;
|
|
1146
|
+
last_verified_at?: string | null;
|
|
1147
|
+
sandboxes: Array<{
|
|
1148
|
+
sandbox_id: string;
|
|
1149
|
+
state: string;
|
|
1150
|
+
}>;
|
|
1151
|
+
}
|
|
1152
|
+
/** `GET /sandboxes/:id/files/stat`. */
|
|
1153
|
+
export interface FileStat {
|
|
1154
|
+
path: string;
|
|
1155
|
+
size: number;
|
|
1156
|
+
/** Octal, as a string — `"644"`. */
|
|
1157
|
+
mode: string;
|
|
1158
|
+
mtime_unix_ns: number;
|
|
1159
|
+
is_dir: boolean;
|
|
1160
|
+
is_symlink: boolean;
|
|
1161
|
+
}
|
|
1162
|
+
export interface DirEntry {
|
|
1163
|
+
name: string;
|
|
1164
|
+
path: string;
|
|
1165
|
+
size: number;
|
|
1166
|
+
/** Octal, as a string — `"100644"`. */
|
|
1167
|
+
mode: string;
|
|
1168
|
+
/**
|
|
1169
|
+
* `null` when the listing could give names only. A missing owner is reported as missing
|
|
1170
|
+
* rather than as root, because `0` is a real uid somebody may act on.
|
|
1171
|
+
*/
|
|
1172
|
+
uid?: number | null;
|
|
1173
|
+
gid?: number | null;
|
|
1174
|
+
mtime_unix_ns: number;
|
|
1175
|
+
is_dir: boolean;
|
|
1176
|
+
is_symlink: boolean;
|
|
1177
|
+
}
|
|
1178
|
+
/** An opaque label of how a file request was answered, passed through for support. */
|
|
1179
|
+
export type ServedBy = string;
|
|
1180
|
+
/** `GET /sandboxes/:id/files/list?path=`. */
|
|
1181
|
+
export interface DirListing {
|
|
1182
|
+
sandbox_id: string;
|
|
1183
|
+
path: string;
|
|
1184
|
+
entries: DirEntry[];
|
|
1185
|
+
/** More than 10,000 entries, or a listing too large for one exec: this is a prefix. */
|
|
1186
|
+
truncated: boolean;
|
|
1187
|
+
served_by?: ServedBy;
|
|
1188
|
+
}
|
|
1189
|
+
/** `?recursive=&depth=&limit=&hidden=` on `files/list`. */
|
|
1190
|
+
export interface DirListOpts {
|
|
1191
|
+
recursive?: boolean;
|
|
1192
|
+
/** With `recursive`: how many levels below `path`. Unset = no bound. */
|
|
1193
|
+
depth?: number;
|
|
1194
|
+
limit?: number;
|
|
1195
|
+
/** Dotfiles are listed by default; `false` omits them. */
|
|
1196
|
+
hidden?: boolean;
|
|
1197
|
+
}
|
|
1198
|
+
export interface FileInfo {
|
|
1199
|
+
sandbox_id: string;
|
|
1200
|
+
path: string;
|
|
1201
|
+
size: number;
|
|
1202
|
+
/** Octal, as a string — `"40755"` for a directory. */
|
|
1203
|
+
mode: string;
|
|
1204
|
+
mtime_unix_ns: number;
|
|
1205
|
+
is_dir: boolean;
|
|
1206
|
+
is_symlink: boolean;
|
|
1207
|
+
served_by?: ServedBy;
|
|
1208
|
+
}
|
|
1209
|
+
/** One hit of `GET /sandboxes/:id/files/search`. `line`/`text` only for a `contains` search. */
|
|
1210
|
+
export interface FileMatch {
|
|
1211
|
+
path: string;
|
|
1212
|
+
line?: number | null;
|
|
1213
|
+
text?: string | null;
|
|
1214
|
+
}
|
|
1215
|
+
export interface SearchResult {
|
|
1216
|
+
sandbox_id: string;
|
|
1217
|
+
path: string;
|
|
1218
|
+
matches: FileMatch[];
|
|
1219
|
+
/** `limit` was reached: this is a prefix of the hits, not all of them. */
|
|
1220
|
+
truncated: boolean;
|
|
1221
|
+
served_by?: ServedBy;
|
|
1222
|
+
}
|
|
1223
|
+
/** `pattern` is a name glob (`*.py`); `contains` is literal text looked for inside the files. */
|
|
1224
|
+
export interface SearchOpts {
|
|
1225
|
+
pattern?: string;
|
|
1226
|
+
contains?: string;
|
|
1227
|
+
ignoreCase?: boolean;
|
|
1228
|
+
limit?: number;
|
|
1229
|
+
hidden?: boolean;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* `POST /sandboxes/:id/files/archive` — where the tar landed **inside the sandbox** and how big
|
|
1233
|
+
* it is. The bytes stay in the sandbox on purpose; see `files.downloadDir`.
|
|
1234
|
+
*/
|
|
1235
|
+
export interface ArchiveResult {
|
|
1236
|
+
sandbox_id: string;
|
|
1237
|
+
/** Where the tar landed inside the sandbox. */
|
|
1238
|
+
archive_path: string;
|
|
1239
|
+
/** Its size in bytes. */
|
|
1240
|
+
size: number;
|
|
1241
|
+
/** The directory that was packed. */
|
|
1242
|
+
source: string;
|
|
1243
|
+
served_by?: ServedBy;
|
|
1244
|
+
}
|
|
1245
|
+
/** `POST /sandboxes/:id/files/extract`. `entries` is `null` on the fallback path, which cannot
|
|
1246
|
+
* count them without a second pass — a number invented there would be worse than none. */
|
|
1247
|
+
export interface ExtractResult {
|
|
1248
|
+
sandbox_id: string;
|
|
1249
|
+
archive: string;
|
|
1250
|
+
into: string;
|
|
1251
|
+
entries: number | null;
|
|
1252
|
+
served_by?: ServedBy;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* A background exec (`POST /sandboxes/:id/execs {background: true}`) as the API reports it.
|
|
1256
|
+
* `exec_id` is scoped to its sandbox and only resolves under the sandbox that started it.
|
|
1257
|
+
*/
|
|
1258
|
+
export interface BackgroundExecRecord {
|
|
1259
|
+
exec_id: string;
|
|
1260
|
+
sandbox_id: string;
|
|
1261
|
+
state: "running" | "exited" | "failed";
|
|
1262
|
+
/** The command as started (`sh` or `argv` joined). */
|
|
1263
|
+
argv: string;
|
|
1264
|
+
/** Started with `stdin: true`, so `execs.stdin` can write to it. A child's stdin cannot be
|
|
1265
|
+
* opened after it has been spawned, which is why this is a property of the start and not a
|
|
1266
|
+
* thing to try. */
|
|
1267
|
+
stdin_open?: boolean;
|
|
1268
|
+
started_unix_ns?: number;
|
|
1269
|
+
/** Bytes buffered (≤ `max_output_bytes` per stream) and bytes written in total. */
|
|
1270
|
+
stdout_bytes: number;
|
|
1271
|
+
stderr_bytes: number;
|
|
1272
|
+
stdout_total?: number;
|
|
1273
|
+
stderr_total?: number;
|
|
1274
|
+
truncated?: boolean;
|
|
1275
|
+
/** Started with `follow`: the newest output is kept, and the handle goes 10 minutes after it ends. */
|
|
1276
|
+
follow?: boolean;
|
|
1277
|
+
/** A range read of a `follow` command: bytes dropped between the asked offset and the first held. */
|
|
1278
|
+
skipped?: number;
|
|
1279
|
+
exit?: {
|
|
1280
|
+
exit_code: number | null;
|
|
1281
|
+
signal: number | null;
|
|
1282
|
+
wall_ms?: number;
|
|
1283
|
+
cpu_ms?: number;
|
|
1284
|
+
max_rss_kib?: number;
|
|
1285
|
+
truncated?: boolean;
|
|
1286
|
+
oom_killed?: boolean;
|
|
1287
|
+
};
|
|
1288
|
+
error?: {
|
|
1289
|
+
code: string;
|
|
1290
|
+
msg: string;
|
|
1291
|
+
};
|
|
1292
|
+
/** With `waitMs`: how long the API parked the request. */
|
|
1293
|
+
waited_ms?: number;
|
|
1294
|
+
/**
|
|
1295
|
+
* Present when the sandbox pauses itself when idle: after the command ends, the idle rule waits
|
|
1296
|
+
* up to this many seconds for its result to be read. `forget` the handle to release it at once.
|
|
1297
|
+
*/
|
|
1298
|
+
idle_hold_s?: number;
|
|
1299
|
+
}
|
|
1300
|
+
/** A published port (`POST /sandboxes/:id/ports`). The `url` is live until revoked or killed. */
|
|
1301
|
+
/** What `POST /sandboxes/:id/ssh` answers. */
|
|
1302
|
+
export interface SshAccess {
|
|
1303
|
+
sandbox_id: string;
|
|
1304
|
+
key_id: string;
|
|
1305
|
+
fingerprint: string;
|
|
1306
|
+
/** The login name. It is the sandbox id: a key is authorised for one name only. */
|
|
1307
|
+
user: string;
|
|
1308
|
+
host: string;
|
|
1309
|
+
/** The access credential. Shown once, expires, reaches this sandbox and nothing else. */
|
|
1310
|
+
token: string;
|
|
1311
|
+
expires_at: string;
|
|
1312
|
+
idle_timeout_s: number;
|
|
1313
|
+
/** A `known_hosts` line body for this sandbox. Pin it. */
|
|
1314
|
+
host_key: string;
|
|
1315
|
+
host_key_fingerprint: string;
|
|
1316
|
+
proxy_command: string;
|
|
1317
|
+
ssh_command: string;
|
|
1318
|
+
}
|
|
1319
|
+
export interface SshKeyRecord {
|
|
1320
|
+
key_id: string;
|
|
1321
|
+
fingerprint: string;
|
|
1322
|
+
user: string;
|
|
1323
|
+
expires_at: string;
|
|
1324
|
+
created_at: string;
|
|
1325
|
+
token_active: boolean;
|
|
1326
|
+
}
|
|
1327
|
+
export interface SshAccessList {
|
|
1328
|
+
sandbox_id: string;
|
|
1329
|
+
keys: SshKeyRecord[];
|
|
1330
|
+
idle_timeout_s: number;
|
|
1331
|
+
host_key?: string;
|
|
1332
|
+
host_key_fingerprint?: string;
|
|
1333
|
+
}
|
|
1334
|
+
export interface SshRevoked {
|
|
1335
|
+
sandbox_id: string;
|
|
1336
|
+
key_id: string;
|
|
1337
|
+
revoked: boolean;
|
|
1338
|
+
}
|
|
1339
|
+
export interface PortRecord {
|
|
1340
|
+
sandbox_id: string;
|
|
1341
|
+
port: number;
|
|
1342
|
+
/** 256-bit capability; anyone with the URL reaches the port. */
|
|
1343
|
+
token: string;
|
|
1344
|
+
url: string;
|
|
1345
|
+
/** The same token as a DNS label, for a wildcard-DNS deployment (`<label>.<suffix>`). */
|
|
1346
|
+
host_url_label?: string;
|
|
1347
|
+
created_unix_ns?: number;
|
|
1348
|
+
}
|
|
1349
|
+
/** A key named without its secret (`/me`'s `key`, the key lists). */
|
|
1350
|
+
export interface KeyRef {
|
|
1351
|
+
key_id: string;
|
|
1352
|
+
name?: string;
|
|
1353
|
+
/** The first few characters, enough to recognise a key without holding it. */
|
|
1354
|
+
prefix?: string;
|
|
1355
|
+
/** A project's service key: it outlives its minter's membership. */
|
|
1356
|
+
service?: boolean;
|
|
1357
|
+
}
|
|
1358
|
+
export interface MeResponse {
|
|
1359
|
+
org_id?: string;
|
|
1360
|
+
/** The organization's name. */
|
|
1361
|
+
org?: string;
|
|
1362
|
+
/** The key this request was made with. */
|
|
1363
|
+
key?: KeyRef | null;
|
|
1364
|
+
user?: {
|
|
1365
|
+
user_id: string;
|
|
1366
|
+
email?: string;
|
|
1367
|
+
provider?: string;
|
|
1368
|
+
} | null;
|
|
1369
|
+
/**
|
|
1370
|
+
* The plan the organisation's limits and prices come from: `hobby` for a trial organisation
|
|
1371
|
+
* with no plan assigned. The name `tier_over_plan` and the other refusals use.
|
|
1372
|
+
*/
|
|
1373
|
+
plan?: string;
|
|
1374
|
+
limits?: {
|
|
1375
|
+
max_inflight?: number;
|
|
1376
|
+
max_concurrency?: number;
|
|
1377
|
+
max_session_s?: number | null;
|
|
1378
|
+
max_sandbox_vcpus?: number | null;
|
|
1379
|
+
[extra: string]: number | null | undefined;
|
|
1380
|
+
};
|
|
1381
|
+
/**
|
|
1382
|
+
* Sandboxes holding capacity now (queued, starting, ready or running; paused ones hold none)
|
|
1383
|
+
* against the limit.
|
|
1384
|
+
*/
|
|
1385
|
+
concurrency?: {
|
|
1386
|
+
limit?: number;
|
|
1387
|
+
in_use?: number;
|
|
1388
|
+
};
|
|
1389
|
+
/**
|
|
1390
|
+
* What the organisation has spent, in USD. Absent (with `spend_detail`, `trial` and `notice`)
|
|
1391
|
+
* for a key that may not see the organisation's money: only an owner's key of the default
|
|
1392
|
+
* project does.
|
|
1393
|
+
*/
|
|
1394
|
+
spend?: number;
|
|
1395
|
+
spend_detail?: {
|
|
1396
|
+
total?: number;
|
|
1397
|
+
currency?: string;
|
|
1398
|
+
billable_seconds?: number;
|
|
1399
|
+
by_kind?: Record<string, number>;
|
|
1400
|
+
};
|
|
1401
|
+
/** The cap a run gets when it names none, in USD; `0` names none (the run is capped at its quote). */
|
|
1402
|
+
default_spend_cap?: number;
|
|
1403
|
+
card_on_file?: boolean;
|
|
1404
|
+
/** The free trial: credits, data out and the limits that apply until a card is on file. */
|
|
1405
|
+
trial?: TrialStatus;
|
|
1406
|
+
/** Something about the account to act on (the card-required switch), or null. */
|
|
1407
|
+
notice?: AccountNotice | null;
|
|
1408
|
+
[extra: string]: unknown;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* `Promigence-Notice` on any response, and `notice` on `me()` / `GET /billing`: an announced or
|
|
1412
|
+
* active card requirement for an org on the free trial. `code` is `card_required_soon` before
|
|
1413
|
+
* `effective_at`, `card_required` after it. Credits are never taken away.
|
|
1414
|
+
*/
|
|
1415
|
+
export interface AccountNotice {
|
|
1416
|
+
code: "card_required_soon" | "card_required" | (string & {});
|
|
1417
|
+
effective_at?: string;
|
|
1418
|
+
credits_remaining?: number;
|
|
1419
|
+
message?: string;
|
|
1420
|
+
fix?: string;
|
|
1421
|
+
docs?: string;
|
|
1422
|
+
[extra: string]: unknown;
|
|
1423
|
+
}
|
|
1424
|
+
export interface TrialStatus {
|
|
1425
|
+
on_trial: boolean;
|
|
1426
|
+
card_on_file: boolean;
|
|
1427
|
+
credits_granted: number;
|
|
1428
|
+
credits_remaining: number;
|
|
1429
|
+
credits_reserved?: number;
|
|
1430
|
+
credits_available?: number;
|
|
1431
|
+
egress_gb_used: number;
|
|
1432
|
+
egress_gb_cap: number | null;
|
|
1433
|
+
limits?: {
|
|
1434
|
+
max_concurrency?: number | null;
|
|
1435
|
+
max_session_s?: number | null;
|
|
1436
|
+
egress_gb_month?: number | null;
|
|
1437
|
+
smtp?: boolean;
|
|
1438
|
+
};
|
|
1439
|
+
policy?: {
|
|
1440
|
+
mode: string;
|
|
1441
|
+
card_required_from?: string | null;
|
|
1442
|
+
notice_from?: string | null;
|
|
1443
|
+
card_required_now?: boolean;
|
|
1444
|
+
};
|
|
1445
|
+
[extra: string]: unknown;
|
|
1446
|
+
}
|
|
1447
|
+
/** The response header an account notice rides on. */
|
|
1448
|
+
export declare const NOTICE_HEADER = "promigence-notice";
|
|
1449
|
+
export interface PromigenceClientOpts {
|
|
1450
|
+
apiKey?: string;
|
|
1451
|
+
apiUrl?: string;
|
|
1452
|
+
/** Injected for tests; defaults to global `fetch`. An injected `fetch` carries every request. */
|
|
1453
|
+
fetch?: FetchLike;
|
|
1454
|
+
/**
|
|
1455
|
+
* Send API calls over one shared HTTP/2 connection when the API offers it (Node.js only;
|
|
1456
|
+
* default `true`). `false`, or `PROMIGENCE_HTTP2=0` in the environment, sends every request
|
|
1457
|
+
* through `fetch`. Anything HTTP/2 cannot do safely falls back to `fetch` on its own.
|
|
1458
|
+
*/
|
|
1459
|
+
http2?: boolean;
|
|
1460
|
+
/**
|
|
1461
|
+
* Per-request timeout (default 30 s). Waits are bounded separately, by `waitTimeoutMs`, and a
|
|
1462
|
+
* wait is not ended by one request hitting this — see `waitLoop`.
|
|
1463
|
+
*
|
|
1464
|
+
* From the CLI: `promigence --request-timeout DUR` or `PROMIGENCE_REQUEST_TIMEOUT`.
|
|
1465
|
+
*/
|
|
1466
|
+
requestTimeoutMs?: number;
|
|
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
|
+
*/
|
|
1472
|
+
maxRetries?: number;
|
|
1473
|
+
/** Backoff base when the server sends no `Retry-After` (default 250 ms). */
|
|
1474
|
+
retryBaseMs?: number;
|
|
1475
|
+
userAgent?: string;
|
|
1476
|
+
/**
|
|
1477
|
+
* The framework, harness or adapter driving this client: `"harbor"`, `"my-harness/2.1.0"`.
|
|
1478
|
+
* Highest precedence; see `setCaller` for the rest of the order, the validation
|
|
1479
|
+
* and why nothing is ever auto-detected.
|
|
1480
|
+
*/
|
|
1481
|
+
caller?: string;
|
|
1482
|
+
/**
|
|
1483
|
+
* Called once per distinct account notice (`Promigence-Notice`), e.g. a card requirement that
|
|
1484
|
+
* starts on a date. Default: one `process.emitWarning` per distinct notice. The latest is also
|
|
1485
|
+
* on `client.notice`.
|
|
1486
|
+
*/
|
|
1487
|
+
onNotice?: (notice: AccountNotice) => void;
|
|
1488
|
+
/**
|
|
1489
|
+
* Called with every answer's timing ([`Timing`]): the round trip this process saw and how long
|
|
1490
|
+
* the answer took on Promigence's side. A CLI uses it to say how far away the caller is.
|
|
1491
|
+
*/
|
|
1492
|
+
onTiming?: (timing: Timing) => void;
|
|
1493
|
+
}
|
|
1494
|
+
/** `GET /sandboxes/:id/network`. */
|
|
1495
|
+
export interface SandboxNetwork {
|
|
1496
|
+
sandbox_id: string;
|
|
1497
|
+
/** What was asked for: `"egress"`, `"none"`, or `{ allow: [...] }`. */
|
|
1498
|
+
network: NetworkMode;
|
|
1499
|
+
state: string;
|
|
1500
|
+
/** Hosts this sandbox looked up and was refused, newest first. */
|
|
1501
|
+
refused_hosts?: {
|
|
1502
|
+
host: string;
|
|
1503
|
+
count?: number;
|
|
1504
|
+
last_unix_ns?: number;
|
|
1505
|
+
}[];
|
|
1506
|
+
/** The hosts this sandbox may reach for its host-bound secrets, whatever `network` says. */
|
|
1507
|
+
via_secret?: {
|
|
1508
|
+
secret: string;
|
|
1509
|
+
hosts: string[];
|
|
1510
|
+
}[];
|
|
1511
|
+
/** Total refused lookups, including hosts past the list above. */
|
|
1512
|
+
refused_lookups?: number;
|
|
1513
|
+
/** Connections refused to an address that was never resolved for an allowed host. */
|
|
1514
|
+
refused_connections?: number;
|
|
1515
|
+
}
|
|
1516
|
+
/** `POST /sandboxes/:id/network {network: "none"}`. */
|
|
1517
|
+
export interface SandboxNetworkCut {
|
|
1518
|
+
sandbox_id: string;
|
|
1519
|
+
network: {
|
|
1520
|
+
mode: "none";
|
|
1521
|
+
};
|
|
1522
|
+
/** Whether anything changed; `false` means egress was already cut. */
|
|
1523
|
+
applied: boolean;
|
|
1524
|
+
state: string;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* One stored secret, bound to the environment variable it arrives as.
|
|
1528
|
+
*
|
|
1529
|
+
* A plain string is the common case — the secret's name is also the variable's. The object form
|
|
1530
|
+
* is what makes a per-session credential possible: ten stored secrets, ten sandboxes, **one**
|
|
1531
|
+
* variable name, at the same time.
|
|
1532
|
+
*
|
|
1533
|
+
* ```ts
|
|
1534
|
+
* secrets: ["OPENAI_API_KEY", { name: "work_secret_7", as: "ANTHROPIC_WORK_SECRET" }]
|
|
1535
|
+
* ```
|
|
1536
|
+
*
|
|
1537
|
+
* A secret bound to hosts (`secrets.set(name, value, { hosts })`) does not put its value in the
|
|
1538
|
+
* sandbox; `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
|
|
1539
|
+
*/
|
|
1540
|
+
export type SecretBinding = string | {
|
|
1541
|
+
name: string;
|
|
1542
|
+
as?: string;
|
|
1543
|
+
mode?: "raw";
|
|
1544
|
+
};
|
|
1545
|
+
export interface RunStartOpts {
|
|
1546
|
+
snapshot: string;
|
|
1547
|
+
count: number;
|
|
1548
|
+
timeoutMs?: number;
|
|
1549
|
+
spendCap?: number;
|
|
1550
|
+
allowPartial?: boolean;
|
|
1551
|
+
verifyEach?: boolean;
|
|
1552
|
+
network?: NetworkMode;
|
|
1553
|
+
optimizeFor?: OptimizeFor;
|
|
1554
|
+
/**
|
|
1555
|
+
* Secrets for every sandbox of this run. An `exec` is no longer required: a run that names
|
|
1556
|
+
* its secrets here gives them to every command run against every one of its sandboxes,
|
|
1557
|
+
* foreground or background, so a long-lived credentialed worker does not have to hold a
|
|
1558
|
+
* streaming exec open for its whole life.
|
|
1559
|
+
*/
|
|
1560
|
+
secrets?: SecretBinding[];
|
|
1561
|
+
labels?: Record<string, string>;
|
|
1562
|
+
/** Terminal exec: run in every sandbox after hand-over; `killOnExit` ends the sandbox. */
|
|
1563
|
+
exec?: {
|
|
1564
|
+
argv?: string[];
|
|
1565
|
+
sh?: string;
|
|
1566
|
+
cwd?: string;
|
|
1567
|
+
env?: Record<string, string>;
|
|
1568
|
+
killOnExit?: boolean;
|
|
1569
|
+
timeoutMs?: number;
|
|
1570
|
+
maxOutputBytes?: number;
|
|
1571
|
+
};
|
|
1572
|
+
/**
|
|
1573
|
+
* Score the work after the episode, in a clean copy of the snapshot. Needs an `exec` — there
|
|
1574
|
+
* is no episode to grade without a command. A snapshot may carry a default grader, in which
|
|
1575
|
+
* case this can be left off.
|
|
1576
|
+
*/
|
|
1577
|
+
grade?: {
|
|
1578
|
+
cmd: string;
|
|
1579
|
+
holdoutCmd?: string;
|
|
1580
|
+
timeoutMs?: number;
|
|
1581
|
+
score?: ScoreMode;
|
|
1582
|
+
};
|
|
1583
|
+
/**
|
|
1584
|
+
* Keep what the grader prints: its last 16 KiB come back in each graded sandbox's
|
|
1585
|
+
* `grade.output` in the run report. Never a held-out grader's. Unset: the snapshot's default
|
|
1586
|
+
* grader's own setting when the run uses it, otherwise off.
|
|
1587
|
+
*/
|
|
1588
|
+
gradeOutput?: boolean;
|
|
1589
|
+
idempotencyKey?: string;
|
|
1590
|
+
/**
|
|
1591
|
+
* Abort the request. Whether the run was started is then unknown: send the same create again
|
|
1592
|
+
* under the same `idempotencyKey` to get its answer (the API never starts it twice), then end it.
|
|
1593
|
+
*/
|
|
1594
|
+
signal?: AbortSignal;
|
|
1595
|
+
/**
|
|
1596
|
+
* Hold `POST /runs` until every sandbox has left `queued`, for at most this long
|
|
1597
|
+
* (`?wait_ms=`, capped at 30 s). The answer's `sandboxes[]` then already carries `ready` rows,
|
|
1598
|
+
* so creating a sandbox is one round trip instead of two. A row still `queued` in the answer
|
|
1599
|
+
* (the budget was spent) is the caller's cue to fall back to `runs.waitReady`.
|
|
1600
|
+
*/
|
|
1601
|
+
waitMs?: number;
|
|
1602
|
+
}
|
|
1603
|
+
export interface ExecOpts {
|
|
1604
|
+
argv?: string[];
|
|
1605
|
+
sh?: string;
|
|
1606
|
+
cwd?: string;
|
|
1607
|
+
env?: Record<string, string>;
|
|
1608
|
+
user?: string;
|
|
1609
|
+
/**
|
|
1610
|
+
* Stored secrets to deliver as environment variables. Names, never values.
|
|
1611
|
+
*
|
|
1612
|
+
* A string binds a secret to a variable of the same name; `{ name, as }` binds it to a
|
|
1613
|
+
* different one, which is how two sandboxes hold two values for one variable at once.
|
|
1614
|
+
*/
|
|
1615
|
+
secrets?: SecretBinding[];
|
|
1616
|
+
/**
|
|
1617
|
+
* Cancel an in-flight exec, streamed or not: the client stops waiting and the request is closed,
|
|
1618
|
+
* which ends the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by
|
|
1619
|
+
* the caller's signal"); a streamed one rejects with the signal's `AbortError`.
|
|
1620
|
+
*/
|
|
1621
|
+
signal?: AbortSignal;
|
|
1622
|
+
/**
|
|
1623
|
+
* Close the sandbox on this command's verdict, exactly as `POST /runs {exec}` does.
|
|
1624
|
+
*
|
|
1625
|
+
* This is what `promigence run --stream` uses: the streaming composite has to own the exec (you
|
|
1626
|
+
* cannot attach to a command the scheduler is already running), and without it the sandbox
|
|
1627
|
+
* would be closed by the client's kill and reported `aborted_by_client` — a green run turned
|
|
1628
|
+
* red by the act of watching it.
|
|
1629
|
+
*/
|
|
1630
|
+
terminal?: boolean;
|
|
1631
|
+
/** With `terminal`, kill the sandbox when the command exits. Defaults to `terminal`. */
|
|
1632
|
+
killOnExit?: boolean;
|
|
1633
|
+
timeoutMs?: number;
|
|
1634
|
+
/**
|
|
1635
|
+
* Output kept per stream, default 64 KiB. Past it the answer keeps the last bytes and says
|
|
1636
|
+
* `truncated`; the rest cannot be fetched afterwards. A background exec keeps all of it.
|
|
1637
|
+
*/
|
|
1638
|
+
maxOutputBytes?: number;
|
|
1639
|
+
/**
|
|
1640
|
+
* `"base64"`: the answer carries the output as its exact bytes (`stdout_b64` / `stderr_b64`,
|
|
1641
|
+
* read them with `execOutputBytes`), for output that is not text. `exec` only: `execStream`
|
|
1642
|
+
* already hands over bytes.
|
|
1643
|
+
*/
|
|
1644
|
+
encoding?: "text" | "base64";
|
|
1645
|
+
/**
|
|
1646
|
+
* Sent as `Idempotency-Key` only when you pass one; the SDK never makes one up for a command.
|
|
1647
|
+
* The API runs a buffered or background command once per key (24 h, per org) and answers a
|
|
1648
|
+
* repeat with the first answer (at most 1 MiB of output kept for it; a longer one comes back cut,
|
|
1649
|
+
* `replay_truncated: true`). A streamed command (`execStream`) cannot be replayed and refuses a
|
|
1650
|
+
* key. The SDK itself never sends an exec that may have started a second time.
|
|
1651
|
+
*/
|
|
1652
|
+
idempotencyKey?: string;
|
|
1653
|
+
}
|
|
1654
|
+
/** `verify: "bun typecheck"` is shorthand for `{ cmd: "bun typecheck" }`. */
|
|
1655
|
+
export type VerifySpec = string | {
|
|
1656
|
+
cmd: string;
|
|
1657
|
+
allowFast?: boolean;
|
|
1658
|
+
};
|
|
1659
|
+
/** `verify` is optional for the image forms: without it the snapshot is stored `verified: false`. */
|
|
1660
|
+
/**
|
|
1661
|
+
* One **additional** pinned repository a world is built from, cloned before `setup` runs.
|
|
1662
|
+
*
|
|
1663
|
+
* `path` is optional and defaults to the repository's own name beside the workspace
|
|
1664
|
+
* (`https://github.com/org/lib` → `/work/lib`). A relative path is relative to the workspace
|
|
1665
|
+
* (`vendor/lib` → `/work/repo/vendor/lib`), which is what an app-plus-library layout wants.
|
|
1666
|
+
*/
|
|
1667
|
+
export interface ExtraSource {
|
|
1668
|
+
repo: string;
|
|
1669
|
+
/** 40 hex. A branch is not a pin. */
|
|
1670
|
+
commit: string;
|
|
1671
|
+
path?: string;
|
|
1672
|
+
}
|
|
1673
|
+
/** The most extra sources one snapshot may name. */
|
|
1674
|
+
export declare const MAX_SOURCES = 8;
|
|
1675
|
+
export interface SnapshotCreateOpts {
|
|
1676
|
+
/**
|
|
1677
|
+
* A public git URL (with `commit`), or `github:owner/name`: a repository the organization's
|
|
1678
|
+
* GitHub connection can read (`github.repos()`), built at `ref` (default: its default branch).
|
|
1679
|
+
*/
|
|
1680
|
+
repo?: string;
|
|
1681
|
+
commit?: string;
|
|
1682
|
+
/** `github:` repositories: a branch, a tag or a sha, resolved to a commit when the build is accepted. */
|
|
1683
|
+
ref?: string;
|
|
1684
|
+
/** `github:` repositories: the snapshot the build starts from (id or alias; default `promigence/base`). */
|
|
1685
|
+
from?: string;
|
|
1686
|
+
/**
|
|
1687
|
+
* `github:` repositories with a branch and an `alias`: every push to the branch builds the
|
|
1688
|
+
* snapshot again and moves the alias to it. Each automatic build is a normal build, charged.
|
|
1689
|
+
*/
|
|
1690
|
+
track?: boolean;
|
|
1691
|
+
/**
|
|
1692
|
+
* Extra repositories for the `repo` form, cloned in this order into their paths before
|
|
1693
|
+
* `setup` runs: a harness repo beside a task repo, an app beside the library being changed
|
|
1694
|
+
* with it. Each lands in the manifest's `provenance.sources[]` with its commit, which a repo
|
|
1695
|
+
* cloned inside a `setup` step never did — so a world built this way is rebuildable, and one
|
|
1696
|
+
* built the old way only looked it.
|
|
1697
|
+
*/
|
|
1698
|
+
sources?: ExtraSource[];
|
|
1699
|
+
image?: string;
|
|
1700
|
+
dockerfile?: {
|
|
1701
|
+
context: string;
|
|
1702
|
+
path?: string;
|
|
1703
|
+
};
|
|
1704
|
+
setup?: string[];
|
|
1705
|
+
verify?: VerifySpec;
|
|
1706
|
+
/** Image forms: a background process started before the snapshot, so forks inherit it running. */
|
|
1707
|
+
start?: string | string[];
|
|
1708
|
+
/**
|
|
1709
|
+
* Image and Dockerfile forms: environment variables for every command, terminal and SSH
|
|
1710
|
+
* session in this snapshot's sandboxes (and its own setup, verify and start), set over the
|
|
1711
|
+
* image's `ENV` as `docker run -e` sets them. A value may build on what the image sets with
|
|
1712
|
+
* `${containerEnv:NAME}` (or `${containerEnv:NAME:default}`):
|
|
1713
|
+
* `{ PATH: "${containerEnv:PATH}:/opt/tools/bin" }`.
|
|
1714
|
+
*/
|
|
1715
|
+
env?: Record<string, string>;
|
|
1716
|
+
tier?: Tier;
|
|
1717
|
+
/** Image forms: the name of a stored secret holding the registry credentials. */
|
|
1718
|
+
registrySecret?: string;
|
|
1719
|
+
/**
|
|
1720
|
+
* Image and Dockerfile forms: the user commands, terminals and SSH sessions in its sandboxes
|
|
1721
|
+
* run as when they name none. A user name or uid, with an optional `:group` or `:gid`, as an
|
|
1722
|
+
* image's `USER` takes it; `root` for root. Default: the image's own `USER`, as `docker run`
|
|
1723
|
+
* does (root when it sets none).
|
|
1724
|
+
*/
|
|
1725
|
+
user?: string;
|
|
1726
|
+
/**
|
|
1727
|
+
* Image and Dockerfile forms: the user its `start`, `setup` and `verify` run as (for example
|
|
1728
|
+
* the snapshot's own `user`). Default: `root`.
|
|
1729
|
+
*/
|
|
1730
|
+
setupUser?: string;
|
|
1731
|
+
alias?: string;
|
|
1732
|
+
/**
|
|
1733
|
+
* A DEFAULT grader for this snapshot: a run on it with no `grade` of its own uses this one.
|
|
1734
|
+
* The same shape as `runs.start({ grade })`.
|
|
1735
|
+
*/
|
|
1736
|
+
grade?: {
|
|
1737
|
+
cmd: string;
|
|
1738
|
+
holdoutCmd?: string;
|
|
1739
|
+
timeoutMs?: number;
|
|
1740
|
+
score?: ScoreMode;
|
|
1741
|
+
};
|
|
1742
|
+
/** Runs that use this default grader keep what it prints (`runs.start({ gradeOutput })`). Needs `grade`. */
|
|
1743
|
+
gradeOutput?: boolean;
|
|
1744
|
+
idempotencyKey?: string;
|
|
1745
|
+
}
|
|
1746
|
+
export interface WaitOpts {
|
|
1747
|
+
/** Bound on the wait (default 100 s). */
|
|
1748
|
+
waitTimeoutMs?: number;
|
|
1749
|
+
/**
|
|
1750
|
+
* Force a fixed client-side poll interval. Setting this opts out of the server-side long-poll
|
|
1751
|
+
* (`?wait_ms=`) entirely — use it only to pin a cadence in a test.
|
|
1752
|
+
*/
|
|
1753
|
+
pollMs?: number;
|
|
1754
|
+
signal?: AbortSignal;
|
|
1755
|
+
/**
|
|
1756
|
+
* Fires on every read with the run's `capacity` block, so a caller can render a waiting state
|
|
1757
|
+
* that **moves** ("capacity arriving, ~28 s" -> running) instead of a
|
|
1758
|
+
* spinner over a word that never changes.
|
|
1759
|
+
*
|
|
1760
|
+
* Passing it caps each server-side park at `PROGRESS_POLL_MS`, exactly like `onReady`: the park
|
|
1761
|
+
* only ends when *every* sandbox is ready, and the point of this callback is the time before
|
|
1762
|
+
* that.
|
|
1763
|
+
*/
|
|
1764
|
+
onCapacity?: (c: Capacity) => void;
|
|
1765
|
+
}
|
|
1766
|
+
/** Per-request knobs the wait helpers pass down to `runs.get` / `runs.sandboxes`. */
|
|
1767
|
+
export interface ReadOpts {
|
|
1768
|
+
/**
|
|
1769
|
+
* Long-poll: ask the API to hold the request until the run reaches the endpoint's condition
|
|
1770
|
+
* (all sandboxes out of `queued` / run finished) or `waitMs` elapse. A server that ignores the
|
|
1771
|
+
* parameter answers immediately — see `waitReady`'s fallback.
|
|
1772
|
+
*/
|
|
1773
|
+
waitMs?: number;
|
|
1774
|
+
onHeaders?: (headers: Headers) => void;
|
|
1775
|
+
signal?: AbortSignal;
|
|
1776
|
+
}
|
|
1777
|
+
export interface WaitReadyResult {
|
|
1778
|
+
sandboxes: SandboxRef[];
|
|
1779
|
+
timedOut: boolean;
|
|
1780
|
+
waitedMs: number;
|
|
1781
|
+
}
|
|
1782
|
+
export interface WaitRunResult {
|
|
1783
|
+
run: RunRecord;
|
|
1784
|
+
timedOut: boolean;
|
|
1785
|
+
waitedMs: number;
|
|
1786
|
+
}
|
|
1787
|
+
export type { CredentialStore } from "./credstore.js";
|
|
1788
|
+
export interface Credentials {
|
|
1789
|
+
api_key?: string;
|
|
1790
|
+
api_url?: string;
|
|
1791
|
+
}
|
|
1792
|
+
export declare function configDir(env?: NodeJS.ProcessEnv): string;
|
|
1793
|
+
export declare function credentialsPath(env?: NodeJS.ProcessEnv): string;
|
|
1794
|
+
/**
|
|
1795
|
+
* Why the credentials file (or its directory) is not this user's alone, or `undefined` when it
|
|
1796
|
+
* is. The CLI refuses to WRITE into a directory another user owns; the readers refuse to trust
|
|
1797
|
+
* one, since a planted file names both the key and the URL every request goes to. Owned by
|
|
1798
|
+
* another user, or writable by others, is refused; group-writable only when the group is not
|
|
1799
|
+
* this user's own (a umask 002 system gives every user a private group, and a file the user made
|
|
1800
|
+
* by hand there must keep working). No POSIX owner (Windows): nothing to check.
|
|
1801
|
+
*/
|
|
1802
|
+
export declare function credentialsTrustProblem(path: string): string | undefined;
|
|
1803
|
+
/**
|
|
1804
|
+
* Why the credentials FILE can be read by someone else, or `undefined` when only its owner can.
|
|
1805
|
+
* `promigence login` always writes it 0600, so a looser mode is
|
|
1806
|
+
* a file made or copied by hand. It is still used, as it always was — refusing would break a working
|
|
1807
|
+
* setup — but a secret others can read is named once, with the fix. No POSIX mode (Windows): nothing.
|
|
1808
|
+
*/
|
|
1809
|
+
export declare function credentialsExposure(path: string): string | undefined;
|
|
1810
|
+
/**
|
|
1811
|
+
* The stored sign-in: JSON `{api_key, api_url}` (a bare key on the first line is accepted too) from
|
|
1812
|
+
* the credentials file, or from the OS keychain item `promigence login` stored when there is no
|
|
1813
|
+
* file. A file another user could have written is ignored, with one warning per process (see
|
|
1814
|
+
* `credentialsTrustProblem`).
|
|
1815
|
+
*/
|
|
1816
|
+
export declare function readCredentials(env?: NodeJS.ProcessEnv): Credentials;
|
|
1817
|
+
export declare function apiUrlFromCompatUrl(url: string | undefined): string | undefined;
|
|
1818
|
+
export declare function apiUrlFromCompatDomain(domain: string | undefined): string | undefined;
|
|
1819
|
+
export declare function compatApiKey(key: string | undefined): string | undefined;
|
|
1820
|
+
/** Which variable a key came from, for `promigence doctor`. */
|
|
1821
|
+
export declare function keyOrigin(env?: NodeJS.ProcessEnv): string | undefined;
|
|
1822
|
+
/** `name@sha256:<64 hex>`, optionally with a tag before the `@`: a reference already pinned. */
|
|
1823
|
+
export declare const IMAGE_DIGEST_RE: RegExp;
|
|
1824
|
+
/**
|
|
1825
|
+
* Why `image` is not a reference the API can pin, or `undefined` when it is.
|
|
1826
|
+
*
|
|
1827
|
+
* The same checks, in the same order, as the API makes (and the Python SDK's
|
|
1828
|
+
* `image_ref_problem`), so a reference is refused here exactly when the server would refuse it
|
|
1829
|
+
* — and a tag is accepted, because the server pins it: `POST /snapshots` resolves `python:3.12`
|
|
1830
|
+
* to `python@sha256:…` itself.
|
|
1831
|
+
*/
|
|
1832
|
+
export declare function imageRefProblem(image: unknown): string | undefined;
|
|
1833
|
+
/**
|
|
1834
|
+
* Refuse a malformed image reference before a round trip is spent on it. A tag is fine: the
|
|
1835
|
+
* server pins it to a digest when the snapshot is created.
|
|
1836
|
+
*/
|
|
1837
|
+
export declare function checkImageRef(image: unknown): asserts image is string;
|
|
1838
|
+
/**
|
|
1839
|
+
* Whether a well-formed image reference names its version: a tag (`python:3.12`) or a digest
|
|
1840
|
+
* (`python@sha256:…`). `python` alone names neither, and is pinned to whatever `python:latest`
|
|
1841
|
+
* is on the day it is used.
|
|
1842
|
+
*/
|
|
1843
|
+
export declare function imageRefNamesVersion(image: string): boolean;
|
|
1844
|
+
/**
|
|
1845
|
+
* Refuse an image reference with neither a tag nor a digest, which would silently build
|
|
1846
|
+
* whatever `:latest` is today. `:latest` written out is a choice and is accepted.
|
|
1847
|
+
*/
|
|
1848
|
+
export declare function checkImageVersioned(image: string): void;
|
|
1849
|
+
/** A git context, pinned: `https://github.com/org/app@<40 hex>`. A branch is not a pin. */
|
|
1850
|
+
export declare const CONTEXT_GIT_RE: RegExp;
|
|
1851
|
+
/**
|
|
1852
|
+
* What in a repository URL can carry a credential: a user name, password or token (`https://TOKEN@…`,
|
|
1853
|
+
* `https://user:pass@…`, `:token@`, a percent-encoded `@`, any `@` in a scheme URL, the scp form's
|
|
1854
|
+
* `git@host:…`), a query string or a fragment. The server refuses every such URL with
|
|
1855
|
+
* `repo_credentials_not_supported`; this is the same rule, so the URL is refused before it
|
|
1856
|
+
* leaves this process. A public
|
|
1857
|
+
* `https://host/org/repo` is `undefined`.
|
|
1858
|
+
*/
|
|
1859
|
+
export declare function repoUrlCredential(url: string): "userinfo" | "query" | "fragment" | undefined;
|
|
1860
|
+
/** `URL@<pin>` → `URL`; a last `@` followed by a path is not a pin, and the whole string is the URL. */
|
|
1861
|
+
export declare function withoutPin(s: string): string;
|
|
1862
|
+
export declare const REPO_CREDENTIALS_FIX: string;
|
|
1863
|
+
/**
|
|
1864
|
+
* Refuse a repository URL that carries a credential, naming `field` and never the URL — the
|
|
1865
|
+
* server's own answer, `repo_credentials_not_supported` (exit 2), raised before anything is sent.
|
|
1866
|
+
*/
|
|
1867
|
+
export declare function checkRepoUrl(url: unknown, field: string): void;
|
|
1868
|
+
/** Refuse a context that is neither a pinned git URL nor a packed directory. */
|
|
1869
|
+
export declare function checkDockerfileContext(context: unknown): asserts context is string;
|
|
1870
|
+
/**
|
|
1871
|
+
* The `POST /snapshots` body for one of its three forms; throws `InvalidArgumentError` before
|
|
1872
|
+
* anything is sent. Kept field-for-field with `sdk-py` (`_ops.snapshot_create_body`) so the two
|
|
1873
|
+
* SDKs send the same bytes for the same call.
|
|
1874
|
+
*/
|
|
1875
|
+
export declare function snapshotCreateBody(opts: SnapshotCreateOpts): Record<string, unknown>;
|
|
1876
|
+
/** Build the right `PromigenceError` subclass for an API error `{code, message, fix, docs}`. */
|
|
1877
|
+
export declare function errorFor(code: string, message: string, fix: string, extra?: {
|
|
1878
|
+
docs?: string;
|
|
1879
|
+
status?: number;
|
|
1880
|
+
requestId?: string;
|
|
1881
|
+
retryAfterMs?: number;
|
|
1882
|
+
category?: string;
|
|
1883
|
+
retryable?: boolean;
|
|
1884
|
+
}): PromigenceError;
|
|
1885
|
+
/**
|
|
1886
|
+
* Is `method path` a create that takes an `Idempotency-Key`? `path` is relative to `/v1` and may
|
|
1887
|
+
* carry a query string. The list is the API reference's (generated from the operations that
|
|
1888
|
+
* document the header), so a create the API keys is never sent unkeyed by this client.
|
|
1889
|
+
*/
|
|
1890
|
+
export declare function isKeyedCreate(method: string, path: string): boolean;
|
|
1891
|
+
/** A sandbox id as a request names it: a non-empty string, or refused before anything is sent. */
|
|
1892
|
+
export declare function checkSandboxId(sandboxId: unknown, call: string): asserts sandboxId is string;
|
|
1893
|
+
/** A spend cap in USD: a finite number, 0 or more. A negative one used to be sent and echoed back as "$-1.00". */
|
|
1894
|
+
export declare function checkSpendCap(spendCap: number): number;
|
|
1895
|
+
/** A key for ONE logical create: made once, then sent on every attempt of that create. */
|
|
1896
|
+
export declare function newIdempotencyKey(): string;
|
|
1897
|
+
/** Budget handed to one long-poll request. Shorter than the 100 s bound so a dead connection,
|
|
1898
|
+
* a redeploy or a proxy idle-timeout costs one round trip, not the whole wait. */
|
|
1899
|
+
export declare const LONG_POLL_MS = 5000;
|
|
1900
|
+
interface RequestOpts {
|
|
1901
|
+
method: "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
|
|
1902
|
+
path: string;
|
|
1903
|
+
body?: unknown;
|
|
1904
|
+
idempotencyKey?: string;
|
|
1905
|
+
timeoutMs?: number;
|
|
1906
|
+
signal?: AbortSignal;
|
|
1907
|
+
/** Skip the Authorization header (unauthenticated routes). */
|
|
1908
|
+
anonymous?: boolean;
|
|
1909
|
+
/** Called with the response headers of the attempt that answered (not of one that was retried). */
|
|
1910
|
+
onHeaders?: (headers: Headers) => void;
|
|
1911
|
+
/** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
|
|
1912
|
+
headers?: Record<string, string>;
|
|
1913
|
+
}
|
|
1914
|
+
/** One answer's timing ([`PromigenceClient.lastTiming`]). */
|
|
1915
|
+
export interface Timing {
|
|
1916
|
+
method: string;
|
|
1917
|
+
path: string;
|
|
1918
|
+
/** The round trip as the client saw it, in ms, until the answer's headers arrived. */
|
|
1919
|
+
totalMs: number;
|
|
1920
|
+
/** How long the answer took on Promigence's side, in ms; `null` when the service did not say. */
|
|
1921
|
+
ourMs: number | null;
|
|
1922
|
+
}
|
|
1923
|
+
/** The `app` span of a `Server-Timing` header (`app;dur=12.35`), in ms; `null` without one. */
|
|
1924
|
+
export declare function serverAppMs(header: string | null | undefined): number | null;
|
|
1925
|
+
export declare class PromigenceClient {
|
|
1926
|
+
#private;
|
|
1927
|
+
readonly apiUrl: string;
|
|
1928
|
+
private readonly fetchImpl;
|
|
1929
|
+
private readonly ownFetch;
|
|
1930
|
+
/** Set when control calls may travel over HTTP/2; each falls back to `fetchImpl` on its own. */
|
|
1931
|
+
private readonly h2;
|
|
1932
|
+
private readonly requestTimeoutMs;
|
|
1933
|
+
private readonly maxRetries;
|
|
1934
|
+
private readonly retryBaseMs;
|
|
1935
|
+
private readonly userAgent;
|
|
1936
|
+
/** See `PromigenceClientOpts.cliErrors`. */
|
|
1937
|
+
private readonly cliErrors;
|
|
1938
|
+
/**
|
|
1939
|
+
* The framework label this client sends (`promigence-caller`), or `undefined` when nobody set one.
|
|
1940
|
+
* Resolved once, at construction; see `setCaller`.
|
|
1941
|
+
*/
|
|
1942
|
+
readonly caller: string | undefined;
|
|
1943
|
+
/** Requests attempted, retries performed — used by tests and `promigence doctor`. */
|
|
1944
|
+
readonly stats: {
|
|
1945
|
+
requests: number;
|
|
1946
|
+
retries: number;
|
|
1947
|
+
};
|
|
1948
|
+
/**
|
|
1949
|
+
* The most recent answer's timing: `totalMs` is the whole round trip as this process saw it,
|
|
1950
|
+
* until the answer's headers arrived; `ourMs` is how long the answer took on Promigence's side
|
|
1951
|
+
* (`null` when the service did not say). `totalMs − ourMs` is the network between you and us.
|
|
1952
|
+
*/
|
|
1953
|
+
lastTiming: Timing | null;
|
|
1954
|
+
/** The latest account notice any response carried (`Promigence-Notice`), if one did. */
|
|
1955
|
+
notice: AccountNotice | undefined;
|
|
1956
|
+
private readonly onNotice;
|
|
1957
|
+
private readonly onTiming;
|
|
1958
|
+
private readonly noticesSeen;
|
|
1959
|
+
constructor(opts?: PromigenceClientOpts);
|
|
1960
|
+
/** Read `Promigence-Notice` off a response (its raw value); tell the caller once per distinct notice. */
|
|
1961
|
+
private observeNotice;
|
|
1962
|
+
/** The key this client sends, if one was resolved. Read on purpose; never printed. */
|
|
1963
|
+
get apiKey(): string | undefined;
|
|
1964
|
+
/** True when a key was resolved from opts / env / credentials file. */
|
|
1965
|
+
get authenticated(): boolean;
|
|
1966
|
+
/** What `JSON.stringify` shows: where the client points and whether it has a key — not the key. */
|
|
1967
|
+
toJSON(): {
|
|
1968
|
+
apiUrl: string;
|
|
1969
|
+
authenticated: boolean;
|
|
1970
|
+
};
|
|
1971
|
+
/** What `console.log` / `util.inspect` show: the same as `toJSON`. */
|
|
1972
|
+
[inspect.custom](_depth: number, opts: InspectOptions): string;
|
|
1973
|
+
/**
|
|
1974
|
+
* Why no key was used although one was found: it belongs to another API URL than this
|
|
1975
|
+
* client's, or it holds a character a header cannot carry. `undefined` when the client has a
|
|
1976
|
+
* key, or when none was found at all.
|
|
1977
|
+
*/
|
|
1978
|
+
get credentialsProblem(): PromigenceError | undefined;
|
|
1979
|
+
/**
|
|
1980
|
+
* Start connecting to the API now, so the first request does not wait for the connection.
|
|
1981
|
+
*
|
|
1982
|
+
* Never throws and never waits for the network: the one-time TLS setup runs here, and the
|
|
1983
|
+
* address lookup and the handshake go on in the background. Nothing is sent: the first request
|
|
1984
|
+
* is still the next call you make, and it uses this connection when it is ready (or waits for it
|
|
1985
|
+
* to finish opening). Calling it again, or from other clients for the same API, opens nothing
|
|
1986
|
+
* more. An unused connection closes after a few seconds and does not keep the process alive;
|
|
1987
|
+
* the next request then connects as usual. Does nothing where requests do not travel over the
|
|
1988
|
+
* SDK's own connection: an injected `fetch`, `http2: false`, a proxy, or a runtime other than
|
|
1989
|
+
* Node.js.
|
|
1990
|
+
*/
|
|
1991
|
+
preconnect(): void;
|
|
1992
|
+
private requireKey;
|
|
1993
|
+
private bearer;
|
|
1994
|
+
/**
|
|
1995
|
+
* How long to wait before sending a refused request again, or `undefined` to give up — the
|
|
1996
|
+
* rule `request()` applies, for the calls that cannot go through it (byte transfers, the
|
|
1997
|
+
* opening of an exec stream, the terminal handshake). Retried: a 429, and a 503 that says
|
|
1998
|
+
* nothing started — any 503 for a request safe to send twice, else only one carrying a
|
|
1999
|
+
* `Retry-After` (every refusal at the door sets one) or a refusal code — and, for a request
|
|
2000
|
+
* safe to send twice, a 502 or 504 that is not the API's own answer (it carries no error
|
|
2001
|
+
* code). At most `maxRetries` times, and never a `Retry-After` longer than
|
|
2002
|
+
* MAX_RETRY_SLEEP_MS: that one is the caller's.
|
|
2003
|
+
*/
|
|
2004
|
+
private refusalBackoff;
|
|
2005
|
+
/** A long-poll request must outlive its own `wait_ms`; everything else keeps the default. */
|
|
2006
|
+
private readExtras;
|
|
2007
|
+
private guardDispatcher;
|
|
2008
|
+
private dispatcherFor;
|
|
2009
|
+
request<T>(opts: RequestOpts): Promise<T>;
|
|
2010
|
+
/**
|
|
2011
|
+
* A request whose body — in either direction — is **bytes, not JSON** (`files get|put`).
|
|
2012
|
+
*
|
|
2013
|
+
* `request()` does `await res.text()`, which is exactly the buffering-and-decoding an eval
|
|
2014
|
+
* artefact must not go through: it would corrupt binary and cost a second copy of a file that
|
|
2015
|
+
* can be gigabytes. This reads the body as an `ArrayBuffer` and hands it back untouched.
|
|
2016
|
+
* Errors still arrive as JSON, so a non-2xx answer is decoded and mapped the same way.
|
|
2017
|
+
*/
|
|
2018
|
+
private bytes;
|
|
2019
|
+
/**
|
|
2020
|
+
* `GET` a byte body **without holding it**: each chunk goes to `onChunk` as it arrives, and
|
|
2021
|
+
* the next is not read until `onChunk` has finished with it, so a 10 GiB artefact costs one
|
|
2022
|
+
* chunk of memory. Errors arrive
|
|
2023
|
+
* as JSON and are mapped exactly as [`bytes`] maps them; a door refusal is sent again.
|
|
2024
|
+
*/
|
|
2025
|
+
private stream;
|
|
2026
|
+
/**
|
|
2027
|
+
* `GET /health` — no key required, which is the point: `promigence doctor` must be able to tell
|
|
2028
|
+
* "the API is down" from "your key is wrong", and a route that needs a key cannot.
|
|
2029
|
+
*/
|
|
2030
|
+
health(opts?: {
|
|
2031
|
+
signal?: AbortSignal;
|
|
2032
|
+
timeoutMs?: number;
|
|
2033
|
+
}): Promise<HealthResponse>;
|
|
2034
|
+
/** `GET /status` — the public service view: the synthetic check window and one row per service. */
|
|
2035
|
+
status(opts?: {
|
|
2036
|
+
signal?: AbortSignal;
|
|
2037
|
+
timeoutMs?: number;
|
|
2038
|
+
}): Promise<StatusResponse>;
|
|
2039
|
+
/**
|
|
2040
|
+
* `GET /pricing` — your organization's price list, live: the per-second rates, every sandbox
|
|
2041
|
+
* shape on sale and its price (a snapshot build bills at its tier's), the limits, the free credit and the plan they
|
|
2042
|
+
* come from, read on every call from what billing charges. Needs a key: without one this throws
|
|
2043
|
+
* `AuthError` and sends nothing. The published list prices are on the pricing page.
|
|
2044
|
+
*
|
|
2045
|
+
* Prices change: read them when you need them rather than keeping a copy.
|
|
2046
|
+
*
|
|
2047
|
+
* @param opts.newAccount No longer supported: there is no key-less price list. Passing `true`
|
|
2048
|
+
* throws `InvalidArgumentError` and sends nothing.
|
|
2049
|
+
*/
|
|
2050
|
+
pricing(opts?: {
|
|
2051
|
+
newAccount?: boolean;
|
|
2052
|
+
signal?: AbortSignal;
|
|
2053
|
+
timeoutMs?: number;
|
|
2054
|
+
}): Promise<PricingView>;
|
|
2055
|
+
/**
|
|
2056
|
+
* The templates: ready-made starting points to fork. Neither call needs a key.
|
|
2057
|
+
* `list()` — every template, each with the snapshot to fork, its version and a count of what it
|
|
2058
|
+
* has installed. `get(name)` — one template with everything it has installed (each package and
|
|
2059
|
+
* tool with its version) and every version with what changed.
|
|
2060
|
+
*/
|
|
2061
|
+
readonly templates: {
|
|
2062
|
+
list: (opts?: {
|
|
2063
|
+
signal?: AbortSignal;
|
|
2064
|
+
}) => Promise<M.TemplateSummary[]>;
|
|
2065
|
+
get: (name: string, opts?: {
|
|
2066
|
+
signal?: AbortSignal;
|
|
2067
|
+
}) => Promise<M.TemplateDetail>;
|
|
2068
|
+
};
|
|
2069
|
+
me(opts?: {
|
|
2070
|
+
signal?: AbortSignal;
|
|
2071
|
+
}): Promise<MeResponse>;
|
|
2072
|
+
/**
|
|
2073
|
+
* `GET /me/outcomes?period=month` — what this organization (with a project's key: that project) got
|
|
2074
|
+
* this month, measured from its own runs and sandboxes. A figure with nothing behind it is `null`.
|
|
2075
|
+
*/
|
|
2076
|
+
outcomes(opts?: {
|
|
2077
|
+
signal?: AbortSignal;
|
|
2078
|
+
}): Promise<Outcomes>;
|
|
2079
|
+
readonly billing: {
|
|
2080
|
+
/** `GET /billing` — plan, card, period, usage, trial, upcoming invoice. */
|
|
2081
|
+
usage: () => Promise<BillingUsage>;
|
|
2082
|
+
/** `GET /me/ledger?limit=N` — the metered rows the month's total is the sum of (one page). */
|
|
2083
|
+
ledger: (limit?: number) => Promise<LedgerEntry[]>;
|
|
2084
|
+
/**
|
|
2085
|
+
* The same rows with the page kept: feed `nextCursor` back as `cursor` until it is `null`.
|
|
2086
|
+
* A server that issues no cursor answers one page with `nextCursor: null`.
|
|
2087
|
+
*/
|
|
2088
|
+
ledgerPage: (opts?: {
|
|
2089
|
+
limit?: number;
|
|
2090
|
+
cursor?: string;
|
|
2091
|
+
}) => Promise<Page<LedgerEntry>>;
|
|
2092
|
+
/**
|
|
2093
|
+
* `GET /billing/history` — the credits the organization received and its payments, newest
|
|
2094
|
+
* first (one page). `kind` narrows it to `credits` or `payments`; feed `nextCursor` back as
|
|
2095
|
+
* `cursor` until it is `null`. An owner's key.
|
|
2096
|
+
*/
|
|
2097
|
+
historyPage: (opts?: {
|
|
2098
|
+
kind?: BillingHistoryKind;
|
|
2099
|
+
limit?: number;
|
|
2100
|
+
cursor?: string;
|
|
2101
|
+
}) => Promise<Page<BillingHistoryEntry>>;
|
|
2102
|
+
/** `POST /billing/checkout` — the hosted page that puts a card on file. */
|
|
2103
|
+
checkout: () => Promise<{
|
|
2104
|
+
url: string;
|
|
2105
|
+
session_id?: string;
|
|
2106
|
+
expires_at?: string;
|
|
2107
|
+
}>;
|
|
2108
|
+
/** `POST /billing/portal` — the hosted page for an existing card and past invoices. */
|
|
2109
|
+
portal: () => Promise<{
|
|
2110
|
+
url: string;
|
|
2111
|
+
}>;
|
|
2112
|
+
/** `GET /referrals` — your code and its offer, your referrals by status, what they earned. */
|
|
2113
|
+
referrals: () => Promise<ReferralStatus>;
|
|
2114
|
+
/** `POST /referrals/apply` — apply a referral code (an owner's key, before the first paid invoice). */
|
|
2115
|
+
applyReferral: (code: string) => Promise<ReferralApplied>;
|
|
2116
|
+
};
|
|
2117
|
+
/**
|
|
2118
|
+
* This key's API keys. With an API key the list is the key's own project's; `all` includes
|
|
2119
|
+
* revoked ones.
|
|
2120
|
+
*/
|
|
2121
|
+
readonly keys: {
|
|
2122
|
+
/** `GET /keys[?all=1]`. */
|
|
2123
|
+
list: (opts?: {
|
|
2124
|
+
all?: boolean;
|
|
2125
|
+
}) => Promise<KeyRecord[]>;
|
|
2126
|
+
/**
|
|
2127
|
+
* `POST /keys` — a new key in this key's project. `api_key` in the answer is the only time
|
|
2128
|
+
* the secret is shown. A key minted by a key is revoked when that key is; a service key
|
|
2129
|
+
* cannot mint.
|
|
2130
|
+
*/
|
|
2131
|
+
create: (opts?: {
|
|
2132
|
+
name?: string;
|
|
2133
|
+
} & KeyExpiry) => Promise<KeyRecord>;
|
|
2134
|
+
/**
|
|
2135
|
+
* `DELETE /keys/:id` — revoke a key and every key it minted (`also_revoked` lists them).
|
|
2136
|
+
* Revoking a key in another project needs that project's own key or a signed-in owner.
|
|
2137
|
+
*/
|
|
2138
|
+
revoke: (keyId: string) => Promise<KeyRecord>;
|
|
2139
|
+
};
|
|
2140
|
+
/** The organization this key belongs to, its projects, members and pending invites. */
|
|
2141
|
+
readonly orgs: {
|
|
2142
|
+
/** `GET /orgs` — with an API key, the key's own organization. */
|
|
2143
|
+
list: () => Promise<OrgRecord[]>;
|
|
2144
|
+
/** `POST /orgs/:org/default` — make it the organization a sign-in and the CLI use when none is named. */
|
|
2145
|
+
setDefault: (org: string) => Promise<OrgRecord>;
|
|
2146
|
+
/**
|
|
2147
|
+
* `POST /orgs/:org/keys` — mint a key in this key's organization. With an API key the new key
|
|
2148
|
+
* stays in this key's project (`project` may name only that one); a service key, or a key in
|
|
2149
|
+
* another project, needs a signed-in owner.
|
|
2150
|
+
*/
|
|
2151
|
+
createKey: (org: string, opts?: {
|
|
2152
|
+
name?: string;
|
|
2153
|
+
project?: string;
|
|
2154
|
+
} & KeyExpiry) => Promise<OrgKeyRecord>;
|
|
2155
|
+
/** `GET /orgs/:org/members` — who belongs and their roles. */
|
|
2156
|
+
members: (org: string) => Promise<MemberRecord[]>;
|
|
2157
|
+
/** `GET /orgs/:org/invites` — invites nobody has accepted yet (gone after 14 days). */
|
|
2158
|
+
invites: (org: string) => Promise<InviteRecord[]>;
|
|
2159
|
+
/**
|
|
2160
|
+
* `DELETE /orgs/:org/members/me` — leave the organization. The keys you minted for yourself
|
|
2161
|
+
* there are revoked; the last owner cannot leave, and nobody can leave their only organization.
|
|
2162
|
+
*/
|
|
2163
|
+
leave: (org: string) => Promise<{
|
|
2164
|
+
org_id: string;
|
|
2165
|
+
user_id: string;
|
|
2166
|
+
removed: boolean;
|
|
2167
|
+
keys_revoked: number;
|
|
2168
|
+
}>;
|
|
2169
|
+
projects: {
|
|
2170
|
+
/** `GET /orgs/:org/projects`. */
|
|
2171
|
+
list: (org: string) => Promise<ProjectRecord[]>;
|
|
2172
|
+
/** `POST /orgs/:org/projects` — `name`: 1-40 lowercase letters, digits and dashes; not `default`. */
|
|
2173
|
+
create: (org: string, name: string) => Promise<ProjectRecord>;
|
|
2174
|
+
/** `PATCH /orgs/:org/projects/:project {name}` — rename it (the `default` project keeps its name). */
|
|
2175
|
+
rename: (org: string, project: string, name: string) => Promise<ProjectRecord>;
|
|
2176
|
+
};
|
|
2177
|
+
};
|
|
2178
|
+
readonly credits: {
|
|
2179
|
+
/** `GET /credits/offer` — claimable (how much, until when), claimed, being checked, or none. */
|
|
2180
|
+
offer: () => Promise<CreditOffer>;
|
|
2181
|
+
};
|
|
2182
|
+
/**
|
|
2183
|
+
* The GitHub connection: install the platform's GitHub App once, then build snapshots from the
|
|
2184
|
+
* repositories it can read (`snapshots.create({ repo: "github:owner/name", ref })`).
|
|
2185
|
+
*/
|
|
2186
|
+
readonly github: {
|
|
2187
|
+
/** `GET /integrations/github` — connections, installs not confirmed yet, and tracked branches. */
|
|
2188
|
+
status: () => Promise<GithubStatus>;
|
|
2189
|
+
/** `POST /integrations/github/connect` — the install link (15 minutes); open it in a browser. */
|
|
2190
|
+
connect: () => Promise<GithubConnect>;
|
|
2191
|
+
/**
|
|
2192
|
+
* `POST /integrations/github/requests` — a connection started for you in the console: open
|
|
2193
|
+
* `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
|
|
2194
|
+
* it here: use `connect()`).
|
|
2195
|
+
*/
|
|
2196
|
+
ticket: () => Promise<ConnectTicket>;
|
|
2197
|
+
/** `GET /integrations/github/requests/:id` — one of your connection requests: `pending`, or how it ended. */
|
|
2198
|
+
request: (requestId: string) => Promise<ConnectRequest>;
|
|
2199
|
+
/** `DELETE /integrations/github/installations/:id` — nothing is built from that account any more. */
|
|
2200
|
+
disconnect: (installationId: number | string) => Promise<GithubDisconnected>;
|
|
2201
|
+
/** `GET /integrations/github/repos` — one page (at most 100) across every connection. */
|
|
2202
|
+
repos: (opts?: {
|
|
2203
|
+
page?: number;
|
|
2204
|
+
perPage?: number;
|
|
2205
|
+
}) => Promise<GithubRepos>;
|
|
2206
|
+
/** `DELETE /integrations/github/tracks/:id` — the snapshot stays; pushes stop rebuilding it. */
|
|
2207
|
+
untrack: (trackId: string) => Promise<GithubUntracked>;
|
|
2208
|
+
};
|
|
2209
|
+
/**
|
|
2210
|
+
* The GitLab connection: authorize the platform's GitLab application once (read-only), have an
|
|
2211
|
+
* owner confirm it, then build snapshots from the projects it can read
|
|
2212
|
+
* (`snapshots.create({ repo: "gitlab:group/project", ref })`).
|
|
2213
|
+
*/
|
|
2214
|
+
readonly gitlab: {
|
|
2215
|
+
/** `GET /integrations/gitlab` — connections, authorizations waiting to be confirmed, tracked branches. */
|
|
2216
|
+
status: () => Promise<GitlabStatus>;
|
|
2217
|
+
/** `POST /integrations/gitlab/connect` — the authorize link (15 minutes); open it in a browser. */
|
|
2218
|
+
connect: () => Promise<GitlabConnect>;
|
|
2219
|
+
/**
|
|
2220
|
+
* `POST /integrations/gitlab/requests` — a connection started for you in the console: open
|
|
2221
|
+
* `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
|
|
2222
|
+
* it here: use `connect()`).
|
|
2223
|
+
*/
|
|
2224
|
+
ticket: () => Promise<ConnectTicket>;
|
|
2225
|
+
/** `GET /integrations/gitlab/requests/:id` — one of your connection requests: `pending`, or how it ended. */
|
|
2226
|
+
request: (requestId: string) => Promise<ConnectRequest>;
|
|
2227
|
+
/** `DELETE /integrations/gitlab/connections/:id` — revoked on GitLab; nothing is built through it any more. */
|
|
2228
|
+
disconnect: (connectionId: string) => Promise<GitlabDisconnected>;
|
|
2229
|
+
/** `GET /integrations/gitlab/repos` — one page (at most 100) of one connection's projects. */
|
|
2230
|
+
repos: (opts?: {
|
|
2231
|
+
page?: number;
|
|
2232
|
+
perPage?: number;
|
|
2233
|
+
connectionId?: string;
|
|
2234
|
+
}) => Promise<GitlabRepos>;
|
|
2235
|
+
/** `DELETE /integrations/gitlab/tracks/:id` — the snapshot stays; new commits stop rebuilding it. */
|
|
2236
|
+
untrack: (trackId: string) => Promise<GitlabUntracked>;
|
|
2237
|
+
};
|
|
2238
|
+
readonly runs: {
|
|
2239
|
+
/** `POST /runs` — returns immediately; sandboxes start `queued`. */
|
|
2240
|
+
start: (opts: RunStartOpts) => Promise<RunStartResponse>;
|
|
2241
|
+
/** `GET /runs/:id`, optionally long-polled with `{ waitMs }`. */
|
|
2242
|
+
get: (runId: string, opts?: ReadOpts) => Promise<RunRecord>;
|
|
2243
|
+
/** `GET /runs?limit=N` — this org's runs, newest first (one page). */
|
|
2244
|
+
list: (limit?: number) => Promise<RunRecord[]>;
|
|
2245
|
+
/** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
|
|
2246
|
+
listPage: (opts?: {
|
|
2247
|
+
limit?: number;
|
|
2248
|
+
cursor?: string;
|
|
2249
|
+
}) => Promise<Page<RunRecord>>;
|
|
2250
|
+
/** Alias of `get`. */
|
|
2251
|
+
status: (runId: string, opts?: ReadOpts) => Promise<RunRecord>;
|
|
2252
|
+
/** `GET /runs/:id/sandboxes` — array or `{sandboxes:[…]}`, normalised. */
|
|
2253
|
+
sandboxes: (runId: string, opts?: ReadOpts) => Promise<SandboxRef[]>;
|
|
2254
|
+
/**
|
|
2255
|
+
* The same read, keeping the `capacity` block the API attaches.
|
|
2256
|
+
*
|
|
2257
|
+
* A server that predates the block answers without it, and this returns `capacity:
|
|
2258
|
+
* undefined` — a client must degrade to the old "queued" rendering rather than showing an
|
|
2259
|
+
* empty progress line.
|
|
2260
|
+
*/
|
|
2261
|
+
sandboxesWithCapacity: (runId: string, opts?: ReadOpts) => Promise<{
|
|
2262
|
+
sandboxes: SandboxRef[];
|
|
2263
|
+
capacity?: Capacity;
|
|
2264
|
+
}>;
|
|
2265
|
+
/** `GET /runs/:id/report` — the validity report. */
|
|
2266
|
+
report: (runId: string) => Promise<RunReport>;
|
|
2267
|
+
/** `POST /runs/:id/cancel` — stop queued starts. */
|
|
2268
|
+
cancel: (runId: string) => Promise<unknown>;
|
|
2269
|
+
/** `DELETE /runs/:id` — kill every sandbox of the run. */
|
|
2270
|
+
kill: (runId: string) => Promise<unknown>;
|
|
2271
|
+
/**
|
|
2272
|
+
* `POST /runs/quote` — what the run will cost, before it starts.
|
|
2273
|
+
*
|
|
2274
|
+
* The only pricing promise in the pitch, and it has no side effects: no build, no fork, no
|
|
2275
|
+
* row, no charge. Prices what `runs.start` would charge, at the tier the snapshot was
|
|
2276
|
+
* verified at. `warnings` is the part a caller must not skip — a timeout over 15 minutes is
|
|
2277
|
+
* a multiplier, not an error.
|
|
2278
|
+
*/
|
|
2279
|
+
quote: (opts: Pick<RunStartOpts, "snapshot" | "count" | "timeoutMs"> & {
|
|
2280
|
+
tier?: string;
|
|
2281
|
+
spendCap?: number;
|
|
2282
|
+
}) => Promise<RunQuote>;
|
|
2283
|
+
/**
|
|
2284
|
+
* `POST /runs/:id/replay` — re-run the environment failures of a run.
|
|
2285
|
+
*
|
|
2286
|
+
* Only `env_failed` sandboxes are replayable (they are never auto-retried after hand-over;
|
|
2287
|
+
* a replay is a new sandbox, billed like any other), the new rows carry `replay_of`, and they are held out of
|
|
2288
|
+
* `first_attempt_success_pct` / `final_success_pct` and reported under `fork.replay`. A
|
|
2289
|
+
* replay never spends past what is left of `min(quote.max_cost, spend_cap)`. A command that
|
|
2290
|
+
* was started with environment variables needs a value for each again (`opts.env`).
|
|
2291
|
+
*/
|
|
2292
|
+
replay: (runId: string, sandboxIds?: string[], opts?: {
|
|
2293
|
+
env?: Record<string, string>;
|
|
2294
|
+
}) => Promise<ReplayResponse>;
|
|
2295
|
+
captures: (runId: string) => Promise<CapturesResponse>;
|
|
2296
|
+
/**
|
|
2297
|
+
* `POST /runs/:id/open` — reopen a failure as a LIVE sandbox, at the moment it broke.
|
|
2298
|
+
*
|
|
2299
|
+
* **Not `replay`.** A replay re-runs the work from the snapshot the run started from; this
|
|
2300
|
+
* forks the state the sandbox was in when it failed — the half-written file, the dead
|
|
2301
|
+
* process, the database mid-migration — and hands it back running. What comes back is a new
|
|
2302
|
+
* sandbox with its own id and its own billing clock, so it costs what a fork costs, and a
|
|
2303
|
+
* finished run whose budget is spent needs a `spendCap` for the reopening.
|
|
2304
|
+
*/
|
|
2305
|
+
open: (runId: string, opts?: {
|
|
2306
|
+
sandboxId?: string;
|
|
2307
|
+
spendCap?: number;
|
|
2308
|
+
timeoutMs?: number;
|
|
2309
|
+
}) => Promise<OpenResponse>;
|
|
2310
|
+
/**
|
|
2311
|
+
* Wait until every sandbox of the run has left `queued`.
|
|
2312
|
+
*
|
|
2313
|
+
* Served by the API's `?wait_ms=` long-poll, so a run that is ready is observed when it is
|
|
2314
|
+
* ready rather than on the client's next poll tick. `onReady` fires once per sandbox, in the
|
|
2315
|
+
* order they become ready; because the server parks until *all*
|
|
2316
|
+
* of them are ready, a caller that wants per-sandbox progress caps each park at
|
|
2317
|
+
* `PROGRESS_POLL_MS` so partial readiness still streams at (at least) the old cadence.
|
|
2318
|
+
*/
|
|
2319
|
+
waitReady: (runId: string, opts?: WaitOpts & {
|
|
2320
|
+
onReady?: (s: SandboxRef) => void;
|
|
2321
|
+
}) => Promise<WaitReadyResult>;
|
|
2322
|
+
/**
|
|
2323
|
+
* Wait until the run's work is over: the run reached a terminal state, or every sandbox has
|
|
2324
|
+
* ended or outlived its command (`killOnExit: false`, `command_outcome` set). Long-polled.
|
|
2325
|
+
*
|
|
2326
|
+
* A kept sandbox does not end when its command does — it stays live until it is killed or
|
|
2327
|
+
* expires — so waiting for it to END waited out its whole timeout.
|
|
2328
|
+
*/
|
|
2329
|
+
waitDone: (runId: string, opts?: WaitOpts) => Promise<WaitRunResult>;
|
|
2330
|
+
};
|
|
2331
|
+
/**
|
|
2332
|
+
* The loop behind `waitReady` / `waitDone`.
|
|
2333
|
+
*
|
|
2334
|
+
* Each iteration hands the API a `wait_ms` budget and lets *it* hold the request until the
|
|
2335
|
+
* state we are blocked on arrives, so the client adds no poll interval of its own to the wait.
|
|
2336
|
+
* A server that ignores `wait_ms` answers immediately without the `promigence-waited-ms` marker;
|
|
2337
|
+
* after two such answers we stop asking and fall back to the adaptive ladder below, which is
|
|
2338
|
+
* also what an explicit `pollMs` selects up front.
|
|
2339
|
+
*
|
|
2340
|
+
* Bounded by `waitTimeoutMs` (default 100 s) exactly as before: on expiry the caller still gets
|
|
2341
|
+
* `timedOut: true` with the last body it saw, and nothing is killed.
|
|
2342
|
+
*/
|
|
2343
|
+
private waitLoop;
|
|
2344
|
+
readonly sandboxes: {
|
|
2345
|
+
/** `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`. */
|
|
2346
|
+
exec: (sandboxId: string, opts: ExecOpts) => Promise<ExecResponse>;
|
|
2347
|
+
/**
|
|
2348
|
+
* `POST /sandboxes/:id/execs` with `Accept: text/event-stream`.
|
|
2349
|
+
*
|
|
2350
|
+
* Calls `on` for every frame as it arrives and resolves with the same `ExecResponse` shape
|
|
2351
|
+
* the inline form returns, assembled from the stream. Output payloads are base64 because a
|
|
2352
|
+
* chunk boundary lands mid-UTF-8 as often as not; `on.stdout`/`on.stderr` are handed the
|
|
2353
|
+
* decoded bytes so the caller never has to know that.
|
|
2354
|
+
*
|
|
2355
|
+
* Uses `fetch`'s streaming body rather than `res.text()`, which is what `request()` does and
|
|
2356
|
+
* why it cannot be reused here: `await res.text()` is precisely the buffering this removes.
|
|
2357
|
+
*/
|
|
2358
|
+
execStream: (sandboxId: string, opts: ExecOpts, on?: {
|
|
2359
|
+
stdout?: (chunk: Uint8Array) => void;
|
|
2360
|
+
stderr?: (chunk: Uint8Array) => void;
|
|
2361
|
+
heartbeat?: () => void;
|
|
2362
|
+
}) => Promise<ExecResponse>;
|
|
2363
|
+
/**
|
|
2364
|
+
* `POST /sandboxes/:id/execs {background: true}` — start a command and return its handle
|
|
2365
|
+
* record immediately. Output is buffered server-side (up to `maxOutputBytes` per stream,
|
|
2366
|
+
* default 1 MiB) and read by range with `execs.read`; `execs.get(…, {waitMs})` parks until
|
|
2367
|
+
* it ends.
|
|
2368
|
+
*
|
|
2369
|
+
* `secrets` are allowed here. Background output is served by byte range, and that range is
|
|
2370
|
+
* masked before it leaves the service, so a credentialed worker can run in the
|
|
2371
|
+
* background instead of holding a foreground stream open for its whole life.
|
|
2372
|
+
*/
|
|
2373
|
+
execBackground: (sandboxId: string, opts: Omit<ExecOpts, "terminal" | "killOnExit" | "signal"> & {
|
|
2374
|
+
stdin?: boolean;
|
|
2375
|
+
follow?: boolean;
|
|
2376
|
+
}) => Promise<BackgroundExecRecord>;
|
|
2377
|
+
/** Background execs of one sandbox (`GET|POST|DELETE /sandboxes/:id/execs/:exec_id…`). */
|
|
2378
|
+
execs: {
|
|
2379
|
+
/** Every background exec this sandbox has started and not forgotten, newest first. */
|
|
2380
|
+
list: (sandboxId: string) => Promise<BackgroundExecRecord[]>;
|
|
2381
|
+
/** Status; with `waitMs` the API parks until the exec ends or the wait (≤ 30 s) is spent. */
|
|
2382
|
+
get: (sandboxId: string, execId: string, opts?: {
|
|
2383
|
+
waitMs?: number;
|
|
2384
|
+
signal?: AbortSignal;
|
|
2385
|
+
}) => Promise<BackgroundExecRecord>;
|
|
2386
|
+
/**
|
|
2387
|
+
* One range of one stream. `eof` is true only once the exec has ended **and** the range
|
|
2388
|
+
* reached the end — a reader that has caught up with a running process is not at EOF.
|
|
2389
|
+
*/
|
|
2390
|
+
read: (sandboxId: string, execId: string, stream: "stdout" | "stderr", opts?: {
|
|
2391
|
+
offset?: number;
|
|
2392
|
+
len?: number;
|
|
2393
|
+
waitMs?: number;
|
|
2394
|
+
untilOutput?: boolean;
|
|
2395
|
+
signal?: AbortSignal;
|
|
2396
|
+
}) => Promise<{
|
|
2397
|
+
data: Uint8Array;
|
|
2398
|
+
offset: number;
|
|
2399
|
+
eof: boolean;
|
|
2400
|
+
skipped: number;
|
|
2401
|
+
record: BackgroundExecRecord;
|
|
2402
|
+
}>;
|
|
2403
|
+
/**
|
|
2404
|
+
* Write to a running background exec's standard input, and/or close it.
|
|
2405
|
+
*
|
|
2406
|
+
* `data` travels base64 because the point of stdin is usually that it is **not** text: a
|
|
2407
|
+
* JSON string cannot carry an arbitrary byte, and a client that pretended otherwise would
|
|
2408
|
+
* corrupt the first binary payload anyone sent. `{ data, eof: true }` in one call is the
|
|
2409
|
+
* common "write this and close", which is what a process reading to EOF is waiting for.
|
|
2410
|
+
*
|
|
2411
|
+
* Refusals worth branching on: `no_stdin` (started without `stdin: true` — its input is
|
|
2412
|
+
* /dev/null and cannot be opened now) and the usage error for an exec that has already
|
|
2413
|
+
* ended. Neither wrote anything.
|
|
2414
|
+
*/
|
|
2415
|
+
stdin: (sandboxId: string, execId: string, opts?: {
|
|
2416
|
+
data?: string | Uint8Array;
|
|
2417
|
+
eof?: boolean;
|
|
2418
|
+
}) => Promise<{
|
|
2419
|
+
ok: boolean;
|
|
2420
|
+
sandbox_id: string;
|
|
2421
|
+
exec_id: string;
|
|
2422
|
+
written: number;
|
|
2423
|
+
eof: boolean;
|
|
2424
|
+
}>;
|
|
2425
|
+
/**
|
|
2426
|
+
* Send a signal (number or name; default SIGTERM). Resolves `delivered: false,
|
|
2427
|
+
* already_ended: true` for an exec that had already ended — the process is gone either way.
|
|
2428
|
+
*/
|
|
2429
|
+
signal: (sandboxId: string, execId: string, signal?: number | string) => Promise<{
|
|
2430
|
+
ok: boolean;
|
|
2431
|
+
exec_id: string;
|
|
2432
|
+
signal: number;
|
|
2433
|
+
delivered: boolean;
|
|
2434
|
+
already_ended?: boolean;
|
|
2435
|
+
}>;
|
|
2436
|
+
/** SIGKILL if still running, then drop the handle and its buffered output. */
|
|
2437
|
+
forget: (sandboxId: string, execId: string) => Promise<{
|
|
2438
|
+
ok: boolean;
|
|
2439
|
+
exec_id: string;
|
|
2440
|
+
}>;
|
|
2441
|
+
};
|
|
2442
|
+
/**
|
|
2443
|
+
* Preview URLs for ports inside the sandbox. A URL is a capability — anyone holding it reaches
|
|
2444
|
+
* that port — and dies with `revoke`, with a pause, or with the sandbox.
|
|
2445
|
+
*/
|
|
2446
|
+
ports: {
|
|
2447
|
+
expose: (sandboxId: string, port: number) => Promise<PortRecord>;
|
|
2448
|
+
list: (sandboxId: string) => Promise<PortRecord[]>;
|
|
2449
|
+
/** A token revokes that URL; a port number revokes every URL for that port. */
|
|
2450
|
+
revoke: (sandboxId: string, tokenOrPort: string | number) => Promise<{
|
|
2451
|
+
ok: boolean;
|
|
2452
|
+
sandbox_id: string;
|
|
2453
|
+
revoked: string[];
|
|
2454
|
+
}>;
|
|
2455
|
+
};
|
|
2456
|
+
ssh: {
|
|
2457
|
+
/**
|
|
2458
|
+
* `POST /sandboxes/:id/ssh` — authorise an `ssh-ed25519` public key and mint the
|
|
2459
|
+
* short-lived credential its tunnel authenticates with.
|
|
2460
|
+
*
|
|
2461
|
+
* Two credentials come back. `token` is what the tunnel accepts: it reaches this sandbox
|
|
2462
|
+
* only and expires, so it is safe in a `~/.ssh/config`. `host_key` is the sandbox's own,
|
|
2463
|
+
* minted for it and gone with it — pin it rather than turning host verification off.
|
|
2464
|
+
*/
|
|
2465
|
+
open: (sandboxId: string, opts: {
|
|
2466
|
+
publicKey: string;
|
|
2467
|
+
ttlS?: number;
|
|
2468
|
+
idleTimeoutS?: number;
|
|
2469
|
+
}) => Promise<SshAccess>;
|
|
2470
|
+
/** `GET /sandboxes/:id/ssh` — the keys this sandbox will accept, and for how long. */
|
|
2471
|
+
list: (sandboxId: string) => Promise<SshAccessList>;
|
|
2472
|
+
/** `DELETE /sandboxes/:id/ssh/:key_id` — by key id or fingerprint. Idempotent. */
|
|
2473
|
+
revoke: (sandboxId: string, keyIdOrFingerprint: string) => Promise<SshRevoked>;
|
|
2474
|
+
/**
|
|
2475
|
+
* `GET /sandboxes/:id/ssh/connect` — the raw tunnel, for a `ProxyCommand`.
|
|
2476
|
+
*
|
|
2477
|
+
* Almost nobody calls this directly: `promigence sandbox ssh <id> --proxy` is what an
|
|
2478
|
+
* `ssh` config points at. It is here because the SDK should be able to do everything the
|
|
2479
|
+
* CLI can.
|
|
2480
|
+
*/
|
|
2481
|
+
connect: (sandboxId: string, opts: {
|
|
2482
|
+
token?: string;
|
|
2483
|
+
onData: (data: Uint8Array) => void;
|
|
2484
|
+
onClose: () => void;
|
|
2485
|
+
timeoutMs?: number;
|
|
2486
|
+
}) => Promise<SshTunnel>;
|
|
2487
|
+
};
|
|
2488
|
+
/**
|
|
2489
|
+
* `GET /sandboxes/:id/pty` — an interactive terminal over a WebSocket. Resolves once the
|
|
2490
|
+
* terminal exists. `cmd` defaults to a login shell (`/bin/sh -l`, `TERM=xterm-256color`).
|
|
2491
|
+
*/
|
|
2492
|
+
pty: (sandboxId: string, opts?: {
|
|
2493
|
+
cols?: number;
|
|
2494
|
+
rows?: number;
|
|
2495
|
+
cmd?: string;
|
|
2496
|
+
/** Stored secret names, or `{name, as}` bindings, put into the terminal's environment
|
|
2497
|
+
* by the server. Names travel in a header; values never leave the service. */
|
|
2498
|
+
secrets?: SecretBinding[];
|
|
2499
|
+
onData?: (data: Uint8Array) => void;
|
|
2500
|
+
onExit?: (exit: PtyExit) => void;
|
|
2501
|
+
timeoutMs?: number;
|
|
2502
|
+
/** Pick the same terminal up again when its connection drops or the service is updated;
|
|
2503
|
+
* see `PtyConnectOpts.resume`. Default: on. */
|
|
2504
|
+
resume?: boolean;
|
|
2505
|
+
/** Reconnect in a new shell when the service is updated (a WebSocket `Close` 1012) and the
|
|
2506
|
+
* terminal cannot be picked up again; see `PtyConnectOpts.reconnect`. Default: on without
|
|
2507
|
+
* `cmd`, off with one. */
|
|
2508
|
+
reconnect?: boolean;
|
|
2509
|
+
/** Called after each reconnect: `resumed` says whether it is the same terminal or a new
|
|
2510
|
+
* shell. */
|
|
2511
|
+
onReconnected?: (how: {
|
|
2512
|
+
resumed: boolean;
|
|
2513
|
+
}) => void;
|
|
2514
|
+
}) => Promise<PtySession>;
|
|
2515
|
+
/** `GET /sandboxes/:id`. */
|
|
2516
|
+
get: (sandboxId: string) => Promise<SandboxRecord>;
|
|
2517
|
+
/**
|
|
2518
|
+
* `POST /sandboxes/:id/pause` — park the sandbox and stop its billing clock.
|
|
2519
|
+
*
|
|
2520
|
+
* Billing: a paused sandbox bills no seconds. The paused interval is subtracted from the
|
|
2521
|
+
* billed span, and both the spend cap's boundary and the sandbox's own `timeoutMs` move with
|
|
2522
|
+
* it, so a sandbox paused overnight is not killed two seconds after it resumes.
|
|
2523
|
+
*
|
|
2524
|
+
* A pause can take seconds on a large, busy sandbox (under a second on a small, quiet one);
|
|
2525
|
+
* this call's timeout allows for it. `took_ms` in the answer is how long it took.
|
|
2526
|
+
*
|
|
2527
|
+
* Files and memory are kept. Background execs end at the pause (`background_execs_killed`
|
|
2528
|
+
* counts them; their ids answer not found after resume); anything else you started keeps
|
|
2529
|
+
* running when the sandbox resumes.
|
|
2530
|
+
*/
|
|
2531
|
+
pause: (sandboxId: string) => Promise<PauseResult>;
|
|
2532
|
+
/**
|
|
2533
|
+
* `POST /sandboxes/:id/resume` — bring it back as it was and restart the clock.
|
|
2534
|
+
* `optimizeFor` left out keeps the sandbox's own setting. `spendCap`: for a sandbox paused at
|
|
2535
|
+
* its run's spend cap (`pause_reason: "spend_cap"`), the run's new cap in US dollars, above
|
|
2536
|
+
* the one it reached; the sandbox goes on with what the new cap leaves.
|
|
2537
|
+
*/
|
|
2538
|
+
resume: (sandboxId: string, opts?: {
|
|
2539
|
+
optimizeFor?: OptimizeFor;
|
|
2540
|
+
spendCap?: number;
|
|
2541
|
+
}) => Promise<ResumeResult>;
|
|
2542
|
+
/**
|
|
2543
|
+
* `POST /sandboxes/:id/snapshot` — photograph a running sandbox and keep it running.
|
|
2544
|
+
*
|
|
2545
|
+
* Unlike `pause`, this does not end the sandbox: you get back an ordinary snapshot id you
|
|
2546
|
+
* can fork, and the sandbox you called it on carries on. Background processes are ended to
|
|
2547
|
+
* take it, and the count comes back in `background_execs_killed` rather than silently.
|
|
2548
|
+
*
|
|
2549
|
+
* The same ten-minute budget as `pause`: on a large sandbox it can take seconds.
|
|
2550
|
+
*
|
|
2551
|
+
* A sandbox that has been given the value of a secret is refused with `secrets_in_snapshot`
|
|
2552
|
+
* (409): the snapshot, and every sandbox started from it, could contain that value. Pass
|
|
2553
|
+
* `force: true` to take it anyway; the answer then carries a `secrets_in_saved_state`
|
|
2554
|
+
* warning.
|
|
2555
|
+
*
|
|
2556
|
+
* An `alias` another snapshot already has is refused (`usage`, 400) unless `moveAlias: true`
|
|
2557
|
+
* moves it to the new snapshot; the other one stays, unnamed, reachable by id.
|
|
2558
|
+
*/
|
|
2559
|
+
snapshot: (sandboxId: string, opts?: {
|
|
2560
|
+
alias?: string;
|
|
2561
|
+
force?: boolean;
|
|
2562
|
+
moveAlias?: boolean;
|
|
2563
|
+
}) => Promise<SandboxSnapshotResult>;
|
|
2564
|
+
/**
|
|
2565
|
+
* `GET /sandboxes?limit=[&labels=][&state=][&cursor=]` — this org's sandboxes, newest run
|
|
2566
|
+
* first, as a plain array.
|
|
2567
|
+
*
|
|
2568
|
+
* `list({ labels: { session: "x" } })` narrows it **on the server**. That is the shape you
|
|
2569
|
+
* want for "find the sandbox for this session, or make one": filtering the array yourself
|
|
2570
|
+
* cannot see past `limit`. `state` is the same deal.
|
|
2571
|
+
*
|
|
2572
|
+
* One page, and the cursor that follows it is dropped. Use [`listPage`] to walk past the
|
|
2573
|
+
* first page; this form stays an array because every existing caller iterates it.
|
|
2574
|
+
*
|
|
2575
|
+
* The number form (`list(100)`) is the original signature and still works.
|
|
2576
|
+
*/
|
|
2577
|
+
list: (opts?: number | SandboxListOptions) => Promise<SandboxRecord[]>;
|
|
2578
|
+
/**
|
|
2579
|
+
* The same route with the page kept whole: `{ items, nextCursor, limit }`.
|
|
2580
|
+
*
|
|
2581
|
+
* This used to be impossible to offer — the route issued no cursor, and a paginator over a
|
|
2582
|
+
* single page is a promise that has to be broken later. It issues one now, so the promise is
|
|
2583
|
+
* real: feed `nextCursor` back as `cursor` until it is `null`.
|
|
2584
|
+
*/
|
|
2585
|
+
listPage: (opts?: number | SandboxListOptions) => Promise<Page<SandboxRecord>>;
|
|
2586
|
+
/**
|
|
2587
|
+
* `POST /sandboxes/:id/probe` — is this environment still the one that was verified?
|
|
2588
|
+
*
|
|
2589
|
+
* Compares the sandbox's files under the paths its snapshot's verification recorded with what
|
|
2590
|
+
* was recorded. It is the same check a failed run gets, which decides whether the failure was
|
|
2591
|
+
* the environment's (`integrity_mismatch`, not billed) or a path the snapshot's own `verify`
|
|
2592
|
+
* never covered (`verify_did_not_cover`, billed). Asking for it up front is cheaper than
|
|
2593
|
+
* spending a run to find out.
|
|
2594
|
+
*
|
|
2595
|
+
* `first_bad_path` names the **first differing path**, which is the only part of a failed
|
|
2596
|
+
* probe anyone can act on.
|
|
2597
|
+
*/
|
|
2598
|
+
probe: (sandboxId: string, opts?: {
|
|
2599
|
+
paths?: string[];
|
|
2600
|
+
}) => Promise<{
|
|
2601
|
+
sandbox_id: string;
|
|
2602
|
+
ok: boolean;
|
|
2603
|
+
ms: number;
|
|
2604
|
+
checked: string | string[];
|
|
2605
|
+
first_bad_path?: string | null;
|
|
2606
|
+
}>;
|
|
2607
|
+
/** `DELETE /sandboxes/:id`. */
|
|
2608
|
+
kill: (sandboxId: string) => Promise<unknown>;
|
|
2609
|
+
/**
|
|
2610
|
+
* `GET /sandboxes/:id/network` — what this sandbox may reach, and every host it asked for
|
|
2611
|
+
* and was refused.
|
|
2612
|
+
*
|
|
2613
|
+
* `refused_hosts` is the field worth reading. A sandbox started with `network: {allow: [...]}`
|
|
2614
|
+
* whose build will not install anything has usually asked for one host nobody thought of;
|
|
2615
|
+
* this names it, with a count, instead of leaving a call to hang.
|
|
2616
|
+
*/
|
|
2617
|
+
network: (sandboxId: string) => Promise<SandboxNetwork>;
|
|
2618
|
+
/**
|
|
2619
|
+
* `POST /sandboxes/:id/network {network: "none"}` — cut a running sandbox's egress.
|
|
2620
|
+
*
|
|
2621
|
+
* The pattern this exists for: fork with the network on, install the dependencies, cut egress,
|
|
2622
|
+
* *then* let the agent run. Before this the policy was fixed at fork, so that sequence needed
|
|
2623
|
+
* two sandboxes or a network the agent could still reach.
|
|
2624
|
+
*
|
|
2625
|
+
* One direction. There is no call that turns egress back on — a sandbox whose network can
|
|
2626
|
+
* re-open on request is not one whose egress you can reason about.
|
|
2627
|
+
*/
|
|
2628
|
+
setNetworkNone: (sandboxId: string) => Promise<SandboxNetworkCut>;
|
|
2629
|
+
/**
|
|
2630
|
+
* `PATCH /sandboxes/:id` — extend one live sandbox, set its idle rule, or both.
|
|
2631
|
+
*
|
|
2632
|
+
* `timeoutMs` is the **total** lifetime, not an offset from now. It throws
|
|
2633
|
+
* `SpendCapError` (409, exit 5) when the added seconds (× the per-second rate) do not fit in
|
|
2634
|
+
* the cap you set (the run's `spendCap`, or the org's default cap) − charged −
|
|
2635
|
+
* live_commitment, and nothing changes. The launch quote is not a bound.
|
|
2636
|
+
*
|
|
2637
|
+
* `autoPauseS` is how long the sandbox may sit idle before it pauses itself; `0` turns that
|
|
2638
|
+
* off, and the server keeps a floor under the rest (a sandbox that spends more time pausing
|
|
2639
|
+
* than it saves is worse than no rule). A sandbox with a command still running is not idle,
|
|
2640
|
+
* so a long build is never cut short by it.
|
|
2641
|
+
*
|
|
2642
|
+
* `autoPauseMode` says what idle means (`AutoPauseMode`); unset leaves it as it is.
|
|
2643
|
+
*
|
|
2644
|
+
* The two are separate promises and either may be sent alone: the timeout is the bound the
|
|
2645
|
+
* customer committed to spend, the idle rule is what stops them spending it on nothing.
|
|
2646
|
+
*/
|
|
2647
|
+
patch: (sandboxId: string, opts: {
|
|
2648
|
+
timeoutMs?: number;
|
|
2649
|
+
autoPauseS?: number;
|
|
2650
|
+
autoPauseMode?: AutoPauseMode;
|
|
2651
|
+
}) => Promise<SandboxPatchResponse>;
|
|
2652
|
+
/**
|
|
2653
|
+
* `get` streams: the body is handed back as bytes and never parsed, because an eval
|
|
2654
|
+
* artefact, a trace or a run log is exactly what this is for. `put` sends the file as the
|
|
2655
|
+
* body rather than base64 inside JSON.
|
|
2656
|
+
*/
|
|
2657
|
+
files: {
|
|
2658
|
+
/**
|
|
2659
|
+
* A directory, each entry as `lstat` reports it, in one request. `recursive` walks below it,
|
|
2660
|
+
* `depth` bounds that walk, `hidden: false` drops dotfiles; `truncated` past `limit`
|
|
2661
|
+
* (10,000 by default).
|
|
2662
|
+
*/
|
|
2663
|
+
list: (sandboxId: string, path?: string, opts?: DirListOpts) => Promise<DirListing>;
|
|
2664
|
+
/**
|
|
2665
|
+
* Find files by name glob (`pattern`), by literal content (`contains`), or both.
|
|
2666
|
+
*
|
|
2667
|
+
* At least one of the two is required, and that is checked here rather than after a round
|
|
2668
|
+
* trip: a search with neither is a full walk of the tree that answers nothing.
|
|
2669
|
+
*/
|
|
2670
|
+
search: (sandboxId: string, path: string, opts?: SearchOpts) => Promise<SearchResult>;
|
|
2671
|
+
/**
|
|
2672
|
+
* `mkdir`. `parents` is `mkdir -p`: the intermediate directories, and no error if it is
|
|
2673
|
+
* already there.
|
|
2674
|
+
*/
|
|
2675
|
+
mkdir: (sandboxId: string, path: string, opts?: {
|
|
2676
|
+
mode?: number | string;
|
|
2677
|
+
parents?: boolean;
|
|
2678
|
+
}) => Promise<FileInfo>;
|
|
2679
|
+
/** Rename or move, inside the sandbox. `overwrite` replaces an existing `to`. */
|
|
2680
|
+
move: (sandboxId: string, from: string, to: string, opts?: {
|
|
2681
|
+
overwrite?: boolean;
|
|
2682
|
+
}) => Promise<FileInfo>;
|
|
2683
|
+
/** Copy, inside the sandbox. `recursive` for a directory. */
|
|
2684
|
+
copy: (sandboxId: string, from: string, to: string, opts?: {
|
|
2685
|
+
recursive?: boolean;
|
|
2686
|
+
overwrite?: boolean;
|
|
2687
|
+
}) => Promise<FileInfo>;
|
|
2688
|
+
/**
|
|
2689
|
+
* Mode and/or ownership. At least one of `mode`, `uid`, `gid` is required — a chmod that
|
|
2690
|
+
* changes nothing is a round trip that answers nothing, and the API refuses it.
|
|
2691
|
+
*/
|
|
2692
|
+
chmod: (sandboxId: string, path: string, opts: {
|
|
2693
|
+
mode?: number | string;
|
|
2694
|
+
uid?: number;
|
|
2695
|
+
gid?: number;
|
|
2696
|
+
recursive?: boolean;
|
|
2697
|
+
}) => Promise<FileInfo>;
|
|
2698
|
+
/**
|
|
2699
|
+
* Tar a directory **into the sandbox** and answer with the tar's path and size.
|
|
2700
|
+
*
|
|
2701
|
+
* The archive deliberately does not cross the wire here: the transfer is the ordinary
|
|
2702
|
+
* `get`/`put` byte path, which has no size ceiling, and a route that returned the bytes
|
|
2703
|
+
* would have one. `exclude` is a list of name globs (`[".git", "node_modules"]`). The tar
|
|
2704
|
+
* carries the source directory under its own name, so an `extract` of it wants `strip: 1`.
|
|
2705
|
+
*/
|
|
2706
|
+
archive: (sandboxId: string, path: string, opts?: {
|
|
2707
|
+
out?: string;
|
|
2708
|
+
exclude?: string[];
|
|
2709
|
+
}) => Promise<ArchiveResult>;
|
|
2710
|
+
/** Unpack a tar that is **already inside** the sandbox. `strip` drops leading path components. */
|
|
2711
|
+
extract: (sandboxId: string, archive: string, into: string, opts?: {
|
|
2712
|
+
strip?: number;
|
|
2713
|
+
}) => Promise<ExtractResult>;
|
|
2714
|
+
stat: (sandboxId: string, path: string) => Promise<{
|
|
2715
|
+
sandbox_id: string;
|
|
2716
|
+
path: string;
|
|
2717
|
+
stat: FileStat;
|
|
2718
|
+
}>;
|
|
2719
|
+
get: (sandboxId: string, path: string, opts?: {
|
|
2720
|
+
signal?: AbortSignal;
|
|
2721
|
+
timeoutMs?: number;
|
|
2722
|
+
}) => Promise<Uint8Array>;
|
|
2723
|
+
/**
|
|
2724
|
+
* `get` without holding the file: every chunk goes to `onChunk` as it arrives (write it to
|
|
2725
|
+
* disk, hash it, pipe it), and the next is read only once `onChunk` resolves. Memory is one
|
|
2726
|
+
* chunk whatever the size. Resolves with the byte count.
|
|
2727
|
+
*/
|
|
2728
|
+
download: (sandboxId: string, path: string, onChunk: (chunk: Uint8Array) => void | Promise<void>, opts?: {
|
|
2729
|
+
signal?: AbortSignal;
|
|
2730
|
+
timeoutMs?: number;
|
|
2731
|
+
}) => Promise<{
|
|
2732
|
+
bytes: number;
|
|
2733
|
+
}>;
|
|
2734
|
+
/** `text` is a convenience over `get` for logs and JSON traces. */
|
|
2735
|
+
text: (sandboxId: string, path: string, opts?: {
|
|
2736
|
+
signal?: AbortSignal;
|
|
2737
|
+
timeoutMs?: number;
|
|
2738
|
+
}) => Promise<string>;
|
|
2739
|
+
/**
|
|
2740
|
+
* Write a file of **any size**, in [`FILE_PUT_CHUNK_BYTES`] requests.
|
|
2741
|
+
*
|
|
2742
|
+
* Chunk 0 goes without `?offset=`, which means "this is the file" (create, truncate);
|
|
2743
|
+
* every later chunk carries one, which appends, so a file has no size limit here.
|
|
2744
|
+
*
|
|
2745
|
+
* A failure mid-loop leaves a **prefix** in the sandbox, not a corrupt file. Re-running the
|
|
2746
|
+
* same `put` is safe: chunk 0 truncates.
|
|
2747
|
+
*
|
|
2748
|
+
* `mode` (`0o755`) sets the file's permission bits. Without it a file that already exists
|
|
2749
|
+
* keeps its mode, as a shell `>` does, and a new one is `0o644`.
|
|
2750
|
+
*/
|
|
2751
|
+
put: (sandboxId: string, path: string, data: string | Uint8Array, opts?: {
|
|
2752
|
+
mode?: number;
|
|
2753
|
+
signal?: AbortSignal;
|
|
2754
|
+
timeoutMs?: number;
|
|
2755
|
+
}) => Promise<{
|
|
2756
|
+
ok: boolean;
|
|
2757
|
+
sandbox_id: string;
|
|
2758
|
+
path: string;
|
|
2759
|
+
bytes: number;
|
|
2760
|
+
mode: string;
|
|
2761
|
+
chunks: number;
|
|
2762
|
+
}>;
|
|
2763
|
+
/**
|
|
2764
|
+
* `put` from a source read one chunk at a time — a file on disk of any size, without
|
|
2765
|
+
* holding it (a 2 GiB put was refused and a 1.5 GB one peaked at 3.3 GB RSS).
|
|
2766
|
+
* `read(offset, len)` returns up to `len` bytes at `offset`; `size` is the total.
|
|
2767
|
+
*/
|
|
2768
|
+
putFrom: (sandboxId: string, path: string, source: {
|
|
2769
|
+
size: number;
|
|
2770
|
+
read: (offset: number, len: number) => Uint8Array | Promise<Uint8Array>;
|
|
2771
|
+
}, opts?: {
|
|
2772
|
+
mode?: number;
|
|
2773
|
+
signal?: AbortSignal;
|
|
2774
|
+
timeoutMs?: number;
|
|
2775
|
+
}) => Promise<{
|
|
2776
|
+
ok: boolean;
|
|
2777
|
+
sandbox_id: string;
|
|
2778
|
+
path: string;
|
|
2779
|
+
bytes: number;
|
|
2780
|
+
mode: string;
|
|
2781
|
+
chunks: number;
|
|
2782
|
+
}>;
|
|
2783
|
+
delete: (sandboxId: string, path: string, opts?: {
|
|
2784
|
+
recursive?: boolean;
|
|
2785
|
+
}) => Promise<{
|
|
2786
|
+
ok: boolean;
|
|
2787
|
+
path: string;
|
|
2788
|
+
deleted: boolean;
|
|
2789
|
+
existed: boolean;
|
|
2790
|
+
}>;
|
|
2791
|
+
};
|
|
2792
|
+
/**
|
|
2793
|
+
* Output for one `exec_id`, the payload behind `truncated` and `stdout_ref`.
|
|
2794
|
+
*
|
|
2795
|
+
* Resolution: one `GET /execs/:id`, checked against an explicit `sandboxId` or `runId`. A
|
|
2796
|
+
* `runId` used to scan that run's sandboxes one request each: ~28 s for a 100-wide run, and
|
|
2797
|
+
* an exec of an ended sandbox could still be missed.
|
|
2798
|
+
*/
|
|
2799
|
+
output: (execId: string, opts?: {
|
|
2800
|
+
sandboxId?: string;
|
|
2801
|
+
runId?: string;
|
|
2802
|
+
range?: {
|
|
2803
|
+
from?: number;
|
|
2804
|
+
to?: number;
|
|
2805
|
+
};
|
|
2806
|
+
}) => Promise<ExecOutput>;
|
|
2807
|
+
};
|
|
2808
|
+
/** Bound helpers for one sandbox (kept from the 0.0.0 shape). */
|
|
2809
|
+
sandbox(id: string): {
|
|
2810
|
+
id: string;
|
|
2811
|
+
exec: (opts: ExecOpts) => Promise<CommandResult>;
|
|
2812
|
+
get: () => Promise<SandboxRecord>;
|
|
2813
|
+
kill: () => Promise<unknown>;
|
|
2814
|
+
pause: () => Promise<PauseResult>;
|
|
2815
|
+
resume: (opts?: {
|
|
2816
|
+
optimizeFor?: OptimizeFor;
|
|
2817
|
+
spendCap?: number;
|
|
2818
|
+
}) => Promise<ResumeResult>;
|
|
2819
|
+
patch: (opts: {
|
|
2820
|
+
timeoutMs?: number;
|
|
2821
|
+
autoPauseS?: number;
|
|
2822
|
+
autoPauseMode?: AutoPauseMode;
|
|
2823
|
+
}) => Promise<SandboxPatchResponse>;
|
|
2824
|
+
files: {
|
|
2825
|
+
get: (path: string) => Promise<Uint8Array<ArrayBufferLike>>;
|
|
2826
|
+
text: (path: string) => Promise<string>;
|
|
2827
|
+
put: (path: string, data: string | Uint8Array, opts?: {
|
|
2828
|
+
mode?: number;
|
|
2829
|
+
}) => Promise<{
|
|
2830
|
+
ok: boolean;
|
|
2831
|
+
sandbox_id: string;
|
|
2832
|
+
path: string;
|
|
2833
|
+
bytes: number;
|
|
2834
|
+
mode: string;
|
|
2835
|
+
chunks: number;
|
|
2836
|
+
}>;
|
|
2837
|
+
stat: (path: string) => Promise<{
|
|
2838
|
+
sandbox_id: string;
|
|
2839
|
+
path: string;
|
|
2840
|
+
stat: FileStat;
|
|
2841
|
+
}>;
|
|
2842
|
+
delete: (path: string) => Promise<{
|
|
2843
|
+
ok: boolean;
|
|
2844
|
+
path: string;
|
|
2845
|
+
deleted: boolean;
|
|
2846
|
+
existed: boolean;
|
|
2847
|
+
}>;
|
|
2848
|
+
};
|
|
2849
|
+
probe: (opts?: {
|
|
2850
|
+
paths?: string[];
|
|
2851
|
+
}) => Promise<{
|
|
2852
|
+
sandbox_id: string;
|
|
2853
|
+
ok: boolean;
|
|
2854
|
+
ms: number;
|
|
2855
|
+
checked: string | string[];
|
|
2856
|
+
first_bad_path?: string | null;
|
|
2857
|
+
}>;
|
|
2858
|
+
};
|
|
2859
|
+
/**
|
|
2860
|
+
* Org-scoped secrets. The value is **write-only**: nothing in this namespace —
|
|
2861
|
+
* or in the API behind it — hands one back. `list` returns names and byte counts so a caller
|
|
2862
|
+
* can tell "is this the key I meant" without being able to read it.
|
|
2863
|
+
*
|
|
2864
|
+
* Names are validated here rather than at the server: an environment variable that cannot
|
|
2865
|
+
* exist is a client-side mistake, and finding out after a round trip helps nobody.
|
|
2866
|
+
*/
|
|
2867
|
+
readonly secrets: {
|
|
2868
|
+
set: (name: string, value: string, opts?: SecretSetOpts) => Promise<SecretRecord>;
|
|
2869
|
+
list: () => Promise<{
|
|
2870
|
+
secrets: SecretRecord[];
|
|
2871
|
+
}>;
|
|
2872
|
+
delete: (name: string) => Promise<{
|
|
2873
|
+
name: string;
|
|
2874
|
+
deleted: boolean;
|
|
2875
|
+
}>;
|
|
2876
|
+
};
|
|
2877
|
+
/**
|
|
2878
|
+
* Container images, as far as we need to know about them: what digest does this tag name?
|
|
2879
|
+
*/
|
|
2880
|
+
readonly images: {
|
|
2881
|
+
/**
|
|
2882
|
+
* `POST /images/resolve` — `python:3.12` → `python@sha256:…`, resolved **by the server**.
|
|
2883
|
+
*
|
|
2884
|
+
* You need no container runtime on this machine to call it. That is the entire point:
|
|
2885
|
+
* `snapshots.create({ image })` takes a digest, and the only way to get one used to be
|
|
2886
|
+
* `docker buildx imagetools inspect` locally — a Docker install demanded of somebody who is
|
|
2887
|
+
* renting sandboxes so as not to need one.
|
|
2888
|
+
*
|
|
2889
|
+
* `image` in the answer is the pinned reference to pass straight to `snapshots.create`. For
|
|
2890
|
+
* a multi-arch tag it carries the **index** digest, which is what a pull of that tag
|
|
2891
|
+
* fetches; `platform_digest` names the `linux/amd64` child beside it, and a tag with no
|
|
2892
|
+
* `linux/amd64` build is refused here rather than at build time.
|
|
2893
|
+
*
|
|
2894
|
+
* An already-pinned reference is returned unchanged without a registry call, so this is
|
|
2895
|
+
* safe to call unconditionally.
|
|
2896
|
+
*
|
|
2897
|
+
* `registrySecret` names a stored secret (`secrets.set`) holding `user:password` for a
|
|
2898
|
+
* private registry.
|
|
2899
|
+
*/
|
|
2900
|
+
resolve: (image: string, opts?: {
|
|
2901
|
+
registrySecret?: string;
|
|
2902
|
+
}) => Promise<ResolvedImage>;
|
|
2903
|
+
};
|
|
2904
|
+
readonly captures: {
|
|
2905
|
+
/**
|
|
2906
|
+
* `DELETE /captures/:snapshot_id` — destroy a preserved failure now, before it expires.
|
|
2907
|
+
*
|
|
2908
|
+
* The bytes go; `bytes` is what was freed. A customer must be able to destroy
|
|
2909
|
+
* their own forensics on demand and be told what was destroyed.
|
|
2910
|
+
*/
|
|
2911
|
+
delete: (snapshotId: string) => Promise<{
|
|
2912
|
+
ok: boolean;
|
|
2913
|
+
snapshot_id: string;
|
|
2914
|
+
deleted: boolean;
|
|
2915
|
+
bytes: number;
|
|
2916
|
+
deleted_at: string;
|
|
2917
|
+
}>;
|
|
2918
|
+
};
|
|
2919
|
+
readonly snapshots: {
|
|
2920
|
+
/**
|
|
2921
|
+
* `POST /snapshots` — 202 with a `build_id`, or 200 with the existing snapshot when an
|
|
2922
|
+
* identical build already ran. Nothing is built synchronously: a build is
|
|
2923
|
+
* minutes, so the caller polls `status` (or `waitBuild`).
|
|
2924
|
+
*/
|
|
2925
|
+
create: (opts: SnapshotCreateOpts) => Promise<SnapshotBuild>;
|
|
2926
|
+
/** `GET /snapshots/builds/:build_id`, optionally long-polled with `{ waitMs }`. */
|
|
2927
|
+
status: (buildId: string, opts?: ReadOpts) => Promise<SnapshotBuild>;
|
|
2928
|
+
/** `GET /snapshots/builds/:build_id/build-log` — the build log, tail first. */
|
|
2929
|
+
buildLog: (buildId: string, tail?: number) => Promise<string[]>;
|
|
2930
|
+
/**
|
|
2931
|
+
* `GET /snapshots/builds?limit=N` — the build history, newest first.
|
|
2932
|
+
*
|
|
2933
|
+
* A build that was rejected keeps its row and its reason, so this is the list you go to after a build
|
|
2934
|
+
* that produced nothing.
|
|
2935
|
+
*/
|
|
2936
|
+
builds: (limit?: number) => Promise<SnapshotBuild[]>;
|
|
2937
|
+
/** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
|
|
2938
|
+
buildsPage: (opts?: {
|
|
2939
|
+
limit?: number;
|
|
2940
|
+
cursor?: string;
|
|
2941
|
+
}) => Promise<Page<SnapshotBuild>>;
|
|
2942
|
+
/** `GET /snapshots/:id` — `snap_<id>` | `<alias>` | `<alias>@<commit7>`. */
|
|
2943
|
+
get: (idOrAlias: string) => Promise<SnapshotRecord>;
|
|
2944
|
+
/** `GET /snapshots` — this org's snapshots plus every public one (public needs no key). */
|
|
2945
|
+
list: (opts?: {
|
|
2946
|
+
limit?: number;
|
|
2947
|
+
cursor?: string;
|
|
2948
|
+
}) => Promise<SnapshotRecord[]>;
|
|
2949
|
+
/** The same route with `nextCursor` kept: feed it back as `cursor` until it is `null`. */
|
|
2950
|
+
listPage: (opts?: {
|
|
2951
|
+
limit?: number;
|
|
2952
|
+
cursor?: string;
|
|
2953
|
+
}) => Promise<Page<SnapshotRecord>>;
|
|
2954
|
+
/**
|
|
2955
|
+
* `GET /snapshots` with **no** Authorization header — the list an agent with no key sees,
|
|
2956
|
+
* which is exactly what `promigence examples` must show. Filtered to `visibility: "public"`
|
|
2957
|
+
* because a server that answers the anonymous call with more than that is misconfigured and
|
|
2958
|
+
* the CLI should not repeat its mistake.
|
|
2959
|
+
*/
|
|
2960
|
+
public: (limit?: number) => Promise<SnapshotRecord[]>;
|
|
2961
|
+
/**
|
|
2962
|
+
* `DELETE /snapshots/:id` — take one of your own snapshots out of the catalogue.
|
|
2963
|
+
*
|
|
2964
|
+
* The snapshot leaves your catalogue at once; the space it used is reclaimed later.
|
|
2965
|
+
* Finished runs keep their reports. Refused (`usage`) while a sandbox forked from it is
|
|
2966
|
+
* still alive — kill those first.
|
|
2967
|
+
*/
|
|
2968
|
+
delete: (idOrAlias: string) => Promise<{
|
|
2969
|
+
snapshot_id: string;
|
|
2970
|
+
alias?: string | null;
|
|
2971
|
+
deleted: boolean;
|
|
2972
|
+
}>;
|
|
2973
|
+
/** `PUT /snapshots/:id/alias` — the sidecar's one mutable field. */
|
|
2974
|
+
alias: (idOrAlias: string, alias: string | null) => Promise<SnapshotRecord>;
|
|
2975
|
+
/**
|
|
2976
|
+
* Wait for a build to leave `running`, bounded by `waitTimeoutMs` (default 100 s).
|
|
2977
|
+
*
|
|
2978
|
+
* `timedOut: true` means exactly that and nothing else: the build is untouched and still
|
|
2979
|
+
* running — come back with `status(buildId)`.
|
|
2980
|
+
*/
|
|
2981
|
+
waitBuild: (buildId: string, opts?: WaitOpts) => Promise<{
|
|
2982
|
+
build: SnapshotBuild;
|
|
2983
|
+
timedOut: boolean;
|
|
2984
|
+
waitedMs: number;
|
|
2985
|
+
}>;
|
|
2986
|
+
/**
|
|
2987
|
+
* `POST /snapshots/:id/verify` — re-run the snapshot's recorded `verify` in a fresh fork.
|
|
2988
|
+
*
|
|
2989
|
+
* stores a snapshot only if its `verify` passed on the machine that built it; this asks
|
|
2990
|
+
* whether that is still true — after a move, after a month, before
|
|
2991
|
+
* trusting a number that came out of it. It is an ordinary one-sandbox run, so it goes
|
|
2992
|
+
* through the same report and the same ledger, and bills per second at the snapshot's tier, at most
|
|
2993
|
+
* `price` (in the reply, to print before waiting like `build_price`).
|
|
2994
|
+
*/
|
|
2995
|
+
verify: (id: string, opts?: {
|
|
2996
|
+
timeoutMs?: number;
|
|
2997
|
+
spendCap?: number;
|
|
2998
|
+
}) => Promise<SnapshotVerifyResponse>;
|
|
2999
|
+
/** `GET /snapshots/:id/manifest` — the snapshot's sealed manifest and what can be verified of it. */
|
|
3000
|
+
manifest: (idOrAlias: string) => Promise<SnapshotManifestResponse>;
|
|
3001
|
+
/**
|
|
3002
|
+
* The `.promigence` bundle for a snapshot: the sealed form, which names exactly one
|
|
3003
|
+
* environment and verifies offline (`verifyBundle`). This client composes it from the
|
|
3004
|
+
* snapshot's sealed manifest (`served_by: "client"`); the API serves no bundle of its own.
|
|
3005
|
+
*/
|
|
3006
|
+
bundle: (idOrAlias: string, opts?: {
|
|
3007
|
+
alias?: string;
|
|
3008
|
+
}) => Promise<{
|
|
3009
|
+
bundle: Bundle;
|
|
3010
|
+
served_by: "client";
|
|
3011
|
+
record?: SnapshotRecord;
|
|
3012
|
+
}>;
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
export declare function toCommandResult(r: ExecResponse): CommandResult;
|