@promigence/sdk 0.1.0-beta.15 → 0.1.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/caller.js +1 -1
- package/dist/cjs/caller.js +1 -1
- package/dist/cjs/client.d.ts +50 -9
- package/dist/cjs/client.js +77 -47
- package/dist/cjs/command-errors.d.ts +1 -0
- package/dist/cjs/command-errors.js +2 -0
- package/dist/cjs/context.d.ts +19 -4
- package/dist/cjs/context.js +77 -5
- package/dist/cjs/core.d.ts +32 -4
- package/dist/cjs/core.js +28 -5
- package/dist/cjs/http2.d.ts +8 -0
- package/dist/cjs/http2.js +22 -1
- package/dist/cjs/models.gen.d.ts +64 -7
- package/dist/cjs/sandbox.d.ts +26 -8
- package/dist/cjs/sandbox.js +28 -4
- package/dist/cjs/transport-worker.js +10 -1
- package/dist/client.d.ts +50 -9
- package/dist/client.js +76 -44
- package/dist/command-errors.d.ts +1 -0
- package/dist/command-errors.js +3 -1
- package/dist/context.d.ts +19 -4
- package/dist/context.js +74 -4
- package/dist/core.d.ts +32 -4
- package/dist/core.js +24 -4
- package/dist/http2.d.ts +8 -0
- package/dist/http2.js +22 -1
- package/dist/models.gen.d.ts +64 -7
- package/dist/sandbox.d.ts +26 -8
- package/dist/sandbox.js +28 -4
- package/dist/transport-worker.js +10 -1
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
2
|
+
export const VERSION = "0.1.0-beta.16";
|
|
3
3
|
export const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
5
|
+
export function errorsDocsFor(code) {
|
|
6
|
+
return `${ERRORS_DOCS_URL}#${code}`;
|
|
7
|
+
}
|
|
5
8
|
export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
|
|
6
9
|
export const KEY_CONTACT = "support@promigence.ai";
|
|
7
10
|
export const WAIT_TIMEOUT_MS = 100_000;
|
|
@@ -10,10 +13,11 @@ export const IMAGE_BUILD_LIST_PRICE = 0.1656;
|
|
|
10
13
|
export const MAX_OUTPUT_BYTES_DEFAULT = 64 * 1024;
|
|
11
14
|
export const FILE_PUT_CHUNK_BYTES = 16 * 1024 * 1024;
|
|
12
15
|
const PREVIEWS_OFF = /^previews are not enabled on this deployment\.?$/i;
|
|
13
|
-
export const PREVIEWS_UNAVAILABLE = "preview URLs are not
|
|
16
|
+
export const PREVIEWS_UNAVAILABLE = "preview URLs are not turned on for this account";
|
|
14
17
|
export function previewsFix(sandboxId = "SID", port = 8000) {
|
|
15
18
|
return `Reach the port over SSH instead: \`promigence sandbox ssh ${hintArg(sandboxId)} -- -N -L ${hintArg(`${port}:localhost:${port}`)}\` ` +
|
|
16
|
-
`forwards http://localhost:${port} here to port ${port} in the sandbox until Ctrl-C
|
|
19
|
+
`forwards http://localhost:${port} here to port ${port} in the sandbox until Ctrl-C. ` +
|
|
20
|
+
"Write to support@promigence.ai to have preview URLs turned on.";
|
|
17
21
|
}
|
|
18
22
|
export async function withPreviewsFix(call, sandboxId, port = "PORT") {
|
|
19
23
|
try {
|
|
@@ -52,7 +56,7 @@ export class PromigenceError extends Error {
|
|
|
52
56
|
this.name = new.target.name;
|
|
53
57
|
this.code = code;
|
|
54
58
|
this.fix = previewsOff ? previewsFix() : fixBeyondMessage(init.message, init.fix);
|
|
55
|
-
this.docs = init.docs ??
|
|
59
|
+
this.docs = init.docs ?? errorsDocsFor(code);
|
|
56
60
|
this.exitCode = init.exitCode ?? 1;
|
|
57
61
|
this.cliExit = init.exitCode ?? 1;
|
|
58
62
|
this.guide = `promigence agent-guide --section errors --code ${hintArg(code)}`;
|
|
@@ -99,6 +103,21 @@ export class PreviewsUnavailableError extends InvalidArgumentError {
|
|
|
99
103
|
super(PREVIEWS_UNAVAILABLE, previewsFix(sandboxId, port), "unsupported");
|
|
100
104
|
}
|
|
101
105
|
}
|
|
106
|
+
const SANDBOX_ENDED = /^sandbox (\S+) has ended \(`([a-z_]+)`\)/;
|
|
107
|
+
export function sandboxEndedIn(message) {
|
|
108
|
+
const m = SANDBOX_ENDED.exec(message.trim());
|
|
109
|
+
return m ? { sandboxId: m[1], state: m[2] } : undefined;
|
|
110
|
+
}
|
|
111
|
+
export const SANDBOX_ENDED_FIX = "Start a new sandbox: Sandbox.create(). This one's getInfo() says how it ended (`outcome`).";
|
|
112
|
+
export class SandboxEndedError extends InvalidArgumentError {
|
|
113
|
+
sandboxId;
|
|
114
|
+
state;
|
|
115
|
+
constructor(message, sandboxId, state, fix = SANDBOX_ENDED_FIX) {
|
|
116
|
+
super(message, fix, "usage");
|
|
117
|
+
this.sandboxId = sandboxId;
|
|
118
|
+
this.state = state;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
102
121
|
export class EnvFailedError extends PromigenceError {
|
|
103
122
|
constructor(code, message, fix) {
|
|
104
123
|
super({ code, message, fix, exitCode: 3 });
|
|
@@ -216,6 +235,7 @@ export const EXIT_BY_CODE = {
|
|
|
216
235
|
env_failed: 3, snapshot_rejected: 6, build_failed: 3, snapshot_failed: 4,
|
|
217
236
|
not_connected: 7,
|
|
218
237
|
service_restart: 3,
|
|
238
|
+
digest_mismatch: 4,
|
|
219
239
|
fork_failed: 3, start_timeout: 3, readiness_failed: 3, verify_too_slow: 3,
|
|
220
240
|
verify_each_failed: 3, toolchain_mismatch: 3, sandbox_unresponsive: 3, oom_within_tier: 3,
|
|
221
241
|
disk_full_within_tier: 3, sandbox_interrupted: 3, integrity_mismatch: 3, egress_unavailable: 3,
|
package/dist/http2.d.ts
CHANGED
|
@@ -197,10 +197,18 @@ export declare const __http2Internals: {
|
|
|
197
197
|
gapUs: number;
|
|
198
198
|
pace: string;
|
|
199
199
|
};
|
|
200
|
+
/** The offset that turns the thread's stamps into trace times (`clockOffset`), read from the two clocks given. */
|
|
201
|
+
clockOffset(hr: () => number, now: () => number): number;
|
|
200
202
|
/** Tests: make the transport thread stop answering (it reads nothing more), as a stuck one would. */
|
|
201
203
|
wedgeThreadForTest(): void;
|
|
202
204
|
/** Tests: make the transport thread fail as a crash would. */
|
|
203
205
|
crashThreadForTest(): void;
|
|
206
|
+
/**
|
|
207
|
+
* Tests: the transport thread gives no spaced request its turn until `releaseTurnsForTest`. Both go as messages, in order
|
|
208
|
+
* with the requests and their cancels, so what is sent between them reaches the thread while every spaced request waits.
|
|
209
|
+
*/
|
|
210
|
+
holdTurnsForTest(): void;
|
|
211
|
+
releaseTurnsForTest(): void;
|
|
204
212
|
/** The early lookup, for tests: start one with a platform, DNS servers and hosts file of the test's own. */
|
|
205
213
|
early: {
|
|
206
214
|
start(host: string): boolean;
|
package/dist/http2.js
CHANGED
|
@@ -1340,6 +1340,12 @@ const THREAD_GRACE_MS = 1_000;
|
|
|
1340
1340
|
const THREAD_SWEEP_MS = 250;
|
|
1341
1341
|
const THREAD_MARKS = 4096;
|
|
1342
1342
|
const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
|
|
1343
|
+
function clockOffset(hr = () => Number(process.hrtime.bigint()) / 1e6, now = () => performance.now(), n = 5) {
|
|
1344
|
+
let best = -Infinity;
|
|
1345
|
+
for (let i = 0; i < n; i++)
|
|
1346
|
+
best = Math.max(best, hr() - now());
|
|
1347
|
+
return best;
|
|
1348
|
+
}
|
|
1343
1349
|
class TransportThread {
|
|
1344
1350
|
origin;
|
|
1345
1351
|
worker;
|
|
@@ -1365,7 +1371,7 @@ class TransportThread {
|
|
|
1365
1371
|
posts = 0;
|
|
1366
1372
|
answered = 0;
|
|
1367
1373
|
pace = THREAD_DEFAULTS.pace;
|
|
1368
|
-
hrOffset =
|
|
1374
|
+
hrOffset = clockOffset();
|
|
1369
1375
|
constructor(origin, worker, port, acks) {
|
|
1370
1376
|
this.origin = origin;
|
|
1371
1377
|
this.worker = worker;
|
|
@@ -2054,6 +2060,9 @@ export const __http2Internals = {
|
|
|
2054
2060
|
threadKnobs(env) {
|
|
2055
2061
|
return threadKnobs(env);
|
|
2056
2062
|
},
|
|
2063
|
+
clockOffset(hr, now) {
|
|
2064
|
+
return clockOffset(hr, now);
|
|
2065
|
+
},
|
|
2057
2066
|
wedgeThreadForTest() {
|
|
2058
2067
|
try {
|
|
2059
2068
|
transportThread?.port.postMessage({ t: "wedge-for-test" });
|
|
@@ -2066,6 +2075,18 @@ export const __http2Internals = {
|
|
|
2066
2075
|
}
|
|
2067
2076
|
catch { }
|
|
2068
2077
|
},
|
|
2078
|
+
holdTurnsForTest() {
|
|
2079
|
+
try {
|
|
2080
|
+
transportThread?.port.postMessage({ t: "hold-turns-for-test" });
|
|
2081
|
+
}
|
|
2082
|
+
catch { }
|
|
2083
|
+
},
|
|
2084
|
+
releaseTurnsForTest() {
|
|
2085
|
+
try {
|
|
2086
|
+
transportThread?.port.postMessage({ t: "release-turns-for-test" });
|
|
2087
|
+
}
|
|
2088
|
+
catch { }
|
|
2089
|
+
},
|
|
2069
2090
|
early: {
|
|
2070
2091
|
start(host) {
|
|
2071
2092
|
return earlyLookupFor(host.toLowerCase()) !== undefined;
|
package/dist/models.gen.d.ts
CHANGED
|
@@ -739,6 +739,11 @@ export interface BuildQueued {
|
|
|
739
739
|
* True when a verification command was given.
|
|
740
740
|
*/
|
|
741
741
|
verified: boolean;
|
|
742
|
+
/**
|
|
743
|
+
* What to know before the build ends: with `move_alias`, which snapshot gives up the alias
|
|
744
|
+
* to it once it is stored.
|
|
745
|
+
*/
|
|
746
|
+
warnings?: Array<string>;
|
|
742
747
|
}
|
|
743
748
|
/**
|
|
744
749
|
* Builds in the period.
|
|
@@ -3074,7 +3079,7 @@ export interface NameCreate {
|
|
|
3074
3079
|
*/
|
|
3075
3080
|
name: string;
|
|
3076
3081
|
}
|
|
3077
|
-
export type Network = "none" | "egress";
|
|
3082
|
+
export type Network = "none" | "egress" | "allow";
|
|
3078
3083
|
/**
|
|
3079
3084
|
* An allowlist: the only hosts the sandbox may reach.
|
|
3080
3085
|
*/
|
|
@@ -3528,6 +3533,11 @@ export interface Port {
|
|
|
3528
3533
|
* Unix nanoseconds.
|
|
3529
3534
|
*/
|
|
3530
3535
|
created_unix_ns?: number | null;
|
|
3536
|
+
/**
|
|
3537
|
+
* When the URL stops working on its own, Unix nanoseconds; `null` when it works until it is
|
|
3538
|
+
* revoked or the sandbox ends.
|
|
3539
|
+
*/
|
|
3540
|
+
expires_unix_ns?: number | null;
|
|
3531
3541
|
/**
|
|
3532
3542
|
* The same publication addressed by name rather than by path. Apps that emit absolute paths
|
|
3533
3543
|
* need this form.
|
|
@@ -3539,8 +3549,8 @@ export interface Port {
|
|
|
3539
3549
|
port?: number | null;
|
|
3540
3550
|
sandbox_id: string;
|
|
3541
3551
|
/**
|
|
3542
|
-
* The token the URL carries. Pass it
|
|
3543
|
-
* revoke this one URL.
|
|
3552
|
+
* The token the URL carries. Pass it in the body of `POST /v1/sandboxes/{id}/ports/revoke`
|
|
3553
|
+
* (`{"token": …}`) to revoke this one URL.
|
|
3544
3554
|
*/
|
|
3545
3555
|
token?: string | null;
|
|
3546
3556
|
/**
|
|
@@ -3563,6 +3573,25 @@ export interface PortOpen {
|
|
|
3563
3573
|
* 1–65535.
|
|
3564
3574
|
*/
|
|
3565
3575
|
port: number;
|
|
3576
|
+
/**
|
|
3577
|
+
* Seconds until the URL stops working on its own, 1 to 2592000 (30 days). Absent: it works
|
|
3578
|
+
* until it is revoked or the sandbox ends.
|
|
3579
|
+
*/
|
|
3580
|
+
ttl_s?: number | null;
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3583
|
+
* `POST /v1/sandboxes/{id}/ports/revoke`: revoke one URL by its token, or every URL of a port,
|
|
3584
|
+
* with what it names in the body. The token never travels in a URL.
|
|
3585
|
+
*/
|
|
3586
|
+
export interface PortRevoke {
|
|
3587
|
+
/**
|
|
3588
|
+
* A port: every URL minted for it is revoked. Exactly one of `token` and `port`.
|
|
3589
|
+
*/
|
|
3590
|
+
port?: number | null;
|
|
3591
|
+
/**
|
|
3592
|
+
* The token of the one URL to revoke.
|
|
3593
|
+
*/
|
|
3594
|
+
token?: string | null;
|
|
3566
3595
|
}
|
|
3567
3596
|
/**
|
|
3568
3597
|
* `POST /v1/billing/portal` — a link to the billing portal.
|
|
@@ -3571,7 +3600,7 @@ export interface PortalSession {
|
|
|
3571
3600
|
url: string;
|
|
3572
3601
|
}
|
|
3573
3602
|
/**
|
|
3574
|
-
* `DELETE /v1/sandboxes/{id}/ports/{token_or_port}`.
|
|
3603
|
+
* `POST /v1/sandboxes/{id}/ports/revoke` and `DELETE /v1/sandboxes/{id}/ports/{token_or_port}`.
|
|
3575
3604
|
*/
|
|
3576
3605
|
export interface PortsRevoked {
|
|
3577
3606
|
ok: boolean;
|
|
@@ -4158,6 +4187,11 @@ export interface ReplayStats {
|
|
|
4158
4187
|
success_pct: number;
|
|
4159
4188
|
}
|
|
4160
4189
|
export interface Requested {
|
|
4190
|
+
/**
|
|
4191
|
+
* With `network: "allow"`: the only destinations the run's sandboxes may reach, as the run
|
|
4192
|
+
* named them. Absent for the other modes.
|
|
4193
|
+
*/
|
|
4194
|
+
allow?: Array<string>;
|
|
4161
4195
|
allow_partial: boolean;
|
|
4162
4196
|
count: number;
|
|
4163
4197
|
/**
|
|
@@ -4736,6 +4770,12 @@ export interface Sandbox {
|
|
|
4736
4770
|
*/
|
|
4737
4771
|
holdout_passed?: boolean | null;
|
|
4738
4772
|
holdout_score?: number | null;
|
|
4773
|
+
/**
|
|
4774
|
+
* `PROMIGENCE_INDEX`: this sandbox's place in its run, from 0. A retry, a replay or a
|
|
4775
|
+
* reopened failure keeps its original's. Absent when the run's command set the variable
|
|
4776
|
+
* itself.
|
|
4777
|
+
*/
|
|
4778
|
+
index?: number | null;
|
|
4739
4779
|
/**
|
|
4740
4780
|
* True when this is a one-time wait: once the waiting sandboxes can start, the rest of the
|
|
4741
4781
|
* run starts at once too. False otherwise.
|
|
@@ -4763,6 +4803,12 @@ export interface Sandbox {
|
|
|
4763
4803
|
retry_of?: string | null;
|
|
4764
4804
|
sandbox_id: string;
|
|
4765
4805
|
score?: number | null;
|
|
4806
|
+
/**
|
|
4807
|
+
* `PROMIGENCE_SEED`: the number this sandbox's commands were given in that variable. A
|
|
4808
|
+
* retry, a replay or a reopened failure keeps its original's. Absent when the run's command
|
|
4809
|
+
* set the variable itself.
|
|
4810
|
+
*/
|
|
4811
|
+
seed?: number | null;
|
|
4766
4812
|
state: SandboxState;
|
|
4767
4813
|
t_end?: string | null;
|
|
4768
4814
|
t_handoff?: string | null;
|
|
@@ -5692,6 +5738,11 @@ export interface SessionTrial {
|
|
|
5692
5738
|
*/
|
|
5693
5739
|
export interface SnapshotAliasSet {
|
|
5694
5740
|
alias: string | null;
|
|
5741
|
+
/**
|
|
5742
|
+
* Take the name from the snapshot of the project that has it, which stays, unnamed. Without
|
|
5743
|
+
* it, a name another snapshot has is refused.
|
|
5744
|
+
*/
|
|
5745
|
+
move_alias?: boolean;
|
|
5695
5746
|
}
|
|
5696
5747
|
/**
|
|
5697
5748
|
* What an image occupies in storage: `allocated`, which can be less than its size.
|
|
@@ -5745,6 +5796,11 @@ export interface SnapshotCreate {
|
|
|
5745
5796
|
* A container image, `REF@sha256:<digest>`; a tag is resolved to its digest first.
|
|
5746
5797
|
*/
|
|
5747
5798
|
image?: string | null;
|
|
5799
|
+
/**
|
|
5800
|
+
* With `alias`: take the name from the snapshot of the project that has it, which stays,
|
|
5801
|
+
* unnamed. Without it, an `alias` another snapshot has is refused and nothing is built.
|
|
5802
|
+
*/
|
|
5803
|
+
move_alias?: boolean;
|
|
5748
5804
|
/**
|
|
5749
5805
|
* `github:` repositories: a branch, a tag or a commit sha, resolved to the commit it names
|
|
5750
5806
|
* when the build is accepted. Default: the repository's default branch.
|
|
@@ -5815,9 +5871,10 @@ export interface SnapshotDeduped {
|
|
|
5815
5871
|
alias_applied?: boolean | null;
|
|
5816
5872
|
/**
|
|
5817
5873
|
* The alias that was asked for, when the snapshot did not hold it. The snapshot takes it
|
|
5818
|
-
* (`alias_applied: true`) as a new build would: another snapshot of the project
|
|
5819
|
-
*
|
|
5820
|
-
*
|
|
5874
|
+
* (`alias_applied: true`) as a new build would: a name another snapshot of the project has
|
|
5875
|
+
* is taken only with `move_alias`, and that snapshot keeps its row, without a name. A
|
|
5876
|
+
* snapshot has one alias, so a name it had before gives way. `warnings` says which names
|
|
5877
|
+
* moved.
|
|
5821
5878
|
*/
|
|
5822
5879
|
alias_requested?: string | null;
|
|
5823
5880
|
/**
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { type CommandResult, type CommandRunOpts, type ForkOpts, type OptimizeFo
|
|
|
14
14
|
import { PromigenceClient, type BackgroundExecRecord, type DirEntry, type DirListOpts, type ExtractResult, type FileInfo, type FileMatch, type Page, type PortRecord, type AutoPauseMode, type SandboxRecord, type SearchOpts, type SecretBinding, type SnapshotBuild, type VerifySpec } from "./client.js";
|
|
15
15
|
import { CommandHandle } from "./handles.js";
|
|
16
16
|
import type { PtyExit, PtySession, PtySize } from "./pty.js";
|
|
17
|
+
/** `setTimeout`'s fix when the longer timeout does not fit under the run's spend cap. */
|
|
18
|
+
export declare const EXTEND_OVER_CAP_FIX: string;
|
|
17
19
|
/**
|
|
18
20
|
* `commands.run` options for a foreground command. The call resolves with the whole result when
|
|
19
21
|
* the command ends; `onStdout` / `onStderr` also receive its output as it is written.
|
|
@@ -150,6 +152,8 @@ export interface SandboxCreateOpts extends SandboxOpts {
|
|
|
150
152
|
start?: string | string[];
|
|
151
153
|
tier?: Tier;
|
|
152
154
|
alias?: string;
|
|
155
|
+
/** With `alias`: take the name from the snapshot that has it (refused without this). */
|
|
156
|
+
moveAlias?: boolean;
|
|
153
157
|
/** Name of a stored secret holding the registry credentials. */
|
|
154
158
|
registrySecret?: string;
|
|
155
159
|
/** Bound on the first build of an image (default 15 min). Expiry cancels nothing. */
|
|
@@ -196,7 +200,7 @@ export interface SandboxCreateOpts extends SandboxOpts {
|
|
|
196
200
|
* inputs) was built before, or 202 with a `build_id`. The **body**, not the status, decides: an
|
|
197
201
|
* idempotency replay answers 202 with a document that may already be `verified`.
|
|
198
202
|
*/
|
|
199
|
-
export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
|
|
203
|
+
export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "moveAlias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
|
|
200
204
|
/**
|
|
201
205
|
* The `POST /snapshots` answer, before any wait: `build_price` (the most it can cost) and
|
|
202
206
|
* whether it was `deduped` or `joined` (nothing new charged). An implicit build costs money, so a caller that prints prices prints this one too.
|
|
@@ -312,7 +316,14 @@ export declare class Sandbox {
|
|
|
312
316
|
resize(pid: number, size: PtySize): Promise<void>;
|
|
313
317
|
kill(pid: number): Promise<boolean>;
|
|
314
318
|
};
|
|
315
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* Preview URLs for ports inside the sandbox (`getHost` gives the address alone).
|
|
321
|
+
*
|
|
322
|
+
* Anyone holding a preview URL can open it; revoke it any time, and it ends with the sandbox.
|
|
323
|
+
* If your account's preview URLs aren't on, `expose` throws `PreviewsUnavailableError`; write to
|
|
324
|
+
* support@promigence.ai to have them turned on, and meanwhile reach the port with
|
|
325
|
+
* `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
|
|
326
|
+
*/
|
|
316
327
|
readonly ports: {
|
|
317
328
|
expose(port: number): Promise<PortRecord>;
|
|
318
329
|
list(): Promise<PortRecord[]>;
|
|
@@ -458,13 +469,16 @@ export declare class Sandbox {
|
|
|
458
469
|
}>;
|
|
459
470
|
isRunning(): Promise<boolean>;
|
|
460
471
|
/**
|
|
461
|
-
* `PATCH /sandboxes/:id` —
|
|
472
|
+
* `PATCH /sandboxes/:id` — set this sandbox's timeout, in milliseconds.
|
|
462
473
|
*
|
|
463
|
-
* The timeout is the sandbox's total lifetime,
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
474
|
+
* The timeout is the sandbox's total lifetime, counted from its start, not from now:
|
|
475
|
+
* `setTimeout(600_000)` on a sandbox that has run 2 minutes leaves it 8 more.
|
|
476
|
+
*
|
|
477
|
+
* - Raising it commits the added seconds, which has to fit in what is left of the spend cap you set
|
|
478
|
+
* (the run's, or the org default): past it, it throws `SpendCapError` (409) rather than silently
|
|
479
|
+
* extending.
|
|
480
|
+
* - Lowering it goes down to what the sandbox has run plus 60 s, and no further
|
|
481
|
+
* (`InvalidArgumentError` below that). To stop it now, `kill()` it.
|
|
468
482
|
*/
|
|
469
483
|
setTimeout(ms: number): Promise<void>;
|
|
470
484
|
/**
|
|
@@ -500,6 +514,10 @@ export declare class Sandbox {
|
|
|
500
514
|
*
|
|
501
515
|
* Async, because the address is a capability minted on demand rather than
|
|
502
516
|
* a hostname derived from the id. An existing URL for the port is reused, not re-minted.
|
|
517
|
+
*
|
|
518
|
+
* If your account's preview URLs aren't on, this throws `PreviewsUnavailableError`; write to
|
|
519
|
+
* support@promigence.ai to have them turned on, and meanwhile reach the port with
|
|
520
|
+
* `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
|
|
503
521
|
*/
|
|
504
522
|
getHost(port: number): Promise<string>;
|
|
505
523
|
/**
|
package/dist/sandbox.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { inspect } from "node:util";
|
|
2
|
-
import { ApiError, CommandExitError, EnvFailedError, IMAGE_BUILD_TIMEOUT_MS, InvalidArgumentError, NotFoundError, TimeoutError, UnsupportedError, WAIT_TIMEOUT_MS, withPreviewsFix, } from "./core.js";
|
|
2
|
+
import { ApiError, CommandExitError, EnvFailedError, IMAGE_BUILD_TIMEOUT_MS, InvalidArgumentError, NotFoundError, SpendCapError, TimeoutError, UnsupportedError, WAIT_TIMEOUT_MS, withPreviewsFix, } from "./core.js";
|
|
3
3
|
import { PromigenceClient, LONG_POLL_MS, checkImageRef, checkImageVersioned, checkSandboxId, errorFor, newIdempotencyKey, toCommandResult, isWaiting, } from "./client.js";
|
|
4
4
|
import { CommandHandle } from "./handles.js";
|
|
5
5
|
import { CommandTimeoutError, answerTimedOut } from "./command-errors.js";
|
|
6
6
|
import { hintArg } from "./hint.js";
|
|
7
|
+
export const EXTEND_OVER_CAP_FIX = "Raising the timeout must fit in what is left of the run's spend cap, and this does not: start a new " +
|
|
8
|
+
"sandbox with the longer `timeoutMs` and a `spendCap` that covers it.";
|
|
7
9
|
const EXEC_TIMEOUT_DEFAULT_MS = 15 * 60_000;
|
|
8
10
|
function textSink(cb) {
|
|
9
11
|
if (!cb)
|
|
@@ -37,6 +39,15 @@ export function ptyCommand(opts) {
|
|
|
37
39
|
parts.push(opts.cmd ?? DEFAULT_PTY_CMD);
|
|
38
40
|
return parts.join(" ");
|
|
39
41
|
}
|
|
42
|
+
function allowList(hosts) {
|
|
43
|
+
if (!Array.isArray(hosts)) {
|
|
44
|
+
throw new InvalidArgumentError("allowHosts must be a list of hosts", 'Pass allowHosts: ["github.com"].');
|
|
45
|
+
}
|
|
46
|
+
if (!hosts.some((h) => typeof h !== "string" || h.trim() !== "")) {
|
|
47
|
+
throw new InvalidArgumentError("allowHosts names no host, so the allow-list is empty", 'Name each host the sandbox may reach: allowHosts: ["github.com"]. For no network pass allowInternetAccess: false; to reach anything leave allowHosts out.');
|
|
48
|
+
}
|
|
49
|
+
return hosts;
|
|
50
|
+
}
|
|
40
51
|
function permString(mode) {
|
|
41
52
|
const r = (m) => `${m & 4 ? "r" : "-"}${m & 2 ? "w" : "-"}${m & 1 ? "x" : "-"}`;
|
|
42
53
|
return r((mode >> 6) & 7) + r((mode >> 3) & 7) + r(mode & 7);
|
|
@@ -89,6 +100,8 @@ export async function imageSnapshot(client, opts, hooks = {}) {
|
|
|
89
100
|
create.tier = opts.tier;
|
|
90
101
|
if (opts.alias !== undefined)
|
|
91
102
|
create.alias = opts.alias;
|
|
103
|
+
if (opts.moveAlias)
|
|
104
|
+
create.moveAlias = true;
|
|
92
105
|
if (opts.registrySecret !== undefined)
|
|
93
106
|
create.registrySecret = opts.registrySecret;
|
|
94
107
|
let build = (await client.snapshots.create(create)) ?? {};
|
|
@@ -362,6 +375,9 @@ export class Sandbox {
|
|
|
362
375
|
return format === "bytes" ? bytes : new TextDecoder().decode(bytes);
|
|
363
376
|
}),
|
|
364
377
|
write: async (path, data, opts = {}) => {
|
|
378
|
+
if (typeof path !== "string") {
|
|
379
|
+
throw new InvalidArgumentError(`files.write takes one path and its data; got ${Array.isArray(path) ? "an array" : path === null ? "null" : typeof path} as the path`, "Write each file with its own call: for (const f of files) await sandbox.files.write(f.path, f.data).");
|
|
380
|
+
}
|
|
365
381
|
let body;
|
|
366
382
|
if (typeof data === "string" || data instanceof Uint8Array)
|
|
367
383
|
body = data;
|
|
@@ -556,8 +572,8 @@ export class Sandbox {
|
|
|
556
572
|
start.timeoutMs = opts.timeoutMs;
|
|
557
573
|
if (opts.allowInternetAccess === false)
|
|
558
574
|
start.network = "none";
|
|
559
|
-
if (opts.allowHosts
|
|
560
|
-
start.network = { allow: opts.allowHosts };
|
|
575
|
+
if (opts.allowHosts !== undefined && opts.allowHosts !== null)
|
|
576
|
+
start.network = { allow: allowList(opts.allowHosts) };
|
|
561
577
|
if (opts.metadata)
|
|
562
578
|
start.labels = opts.metadata;
|
|
563
579
|
if (opts.secrets?.length)
|
|
@@ -695,7 +711,15 @@ export class Sandbox {
|
|
|
695
711
|
return rec.state === "ready" || rec.state === "running";
|
|
696
712
|
}
|
|
697
713
|
async setTimeout(ms) {
|
|
698
|
-
|
|
714
|
+
try {
|
|
715
|
+
await this.#client.sandboxes.patch(this.sandboxId, { timeoutMs: ms });
|
|
716
|
+
}
|
|
717
|
+
catch (e) {
|
|
718
|
+
if (e instanceof SpendCapError && e.code === "cap_exceeded") {
|
|
719
|
+
Object.defineProperty(e, "fix", { value: EXTEND_OVER_CAP_FIX, enumerable: true, configurable: true });
|
|
720
|
+
}
|
|
721
|
+
throw e;
|
|
722
|
+
}
|
|
699
723
|
this.timeoutMs = ms;
|
|
700
724
|
}
|
|
701
725
|
async setAutoPause(seconds, mode) {
|
package/dist/transport-worker.js
CHANGED
|
@@ -290,6 +290,7 @@ function start(init) {
|
|
|
290
290
|
let rrAny = 0;
|
|
291
291
|
let pumpTimer;
|
|
292
292
|
let pumpSoon;
|
|
293
|
+
let turnsHeld = false;
|
|
293
294
|
const freeNow = (l, now) => {
|
|
294
295
|
for (let i = 0; i < l.length; i++) {
|
|
295
296
|
const k = (rr + i) % l.length;
|
|
@@ -304,6 +305,8 @@ function start(init) {
|
|
|
304
305
|
const pump = () => {
|
|
305
306
|
pumpTimer = undefined;
|
|
306
307
|
pumpSoon = undefined;
|
|
308
|
+
if (turnsHeld)
|
|
309
|
+
return;
|
|
307
310
|
while (waiting.length > 0) {
|
|
308
311
|
const l = live();
|
|
309
312
|
if (l.length === 0) {
|
|
@@ -346,7 +349,7 @@ function start(init) {
|
|
|
346
349
|
rrAny = (rrAny % l.length) + 1;
|
|
347
350
|
return e;
|
|
348
351
|
}
|
|
349
|
-
if (waiting.length === 0) {
|
|
352
|
+
if (waiting.length === 0 && !turnsHeld) {
|
|
350
353
|
const now = hrNow();
|
|
351
354
|
const e = freeNow(l, now);
|
|
352
355
|
if (e !== undefined) {
|
|
@@ -527,6 +530,12 @@ function start(init) {
|
|
|
527
530
|
}
|
|
528
531
|
else if (m?.t === "crash-for-test")
|
|
529
532
|
setImmediate(() => { throw new Error("transport thread crash (test)"); });
|
|
533
|
+
else if (m?.t === "hold-turns-for-test")
|
|
534
|
+
turnsHeld = true;
|
|
535
|
+
else if (m?.t === "release-turns-for-test") {
|
|
536
|
+
turnsHeld = false;
|
|
537
|
+
pump();
|
|
538
|
+
}
|
|
530
539
|
});
|
|
531
540
|
void open();
|
|
532
541
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promigence/sdk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.16",
|
|
4
4
|
"description": "The fastest sandboxes for AI agents and code. The Promigence TypeScript SDK: snapshot an environment once, fork it as often as you need, run commands in every copy. A simple Sandbox class and the full PromigenceClient, with zero runtime dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://www.promigence.ai",
|