@promigence/sdk 0.1.0-beta.14 → 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 +134 -23
- package/dist/cjs/client.js +202 -80
- 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 +30 -5
- package/dist/cjs/http2.d.ts +8 -0
- package/dist/cjs/http2.js +22 -1
- package/dist/cjs/models.gen.d.ts +663 -43
- package/dist/cjs/pty.d.ts +11 -14
- package/dist/cjs/pty.js +15 -12
- 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 +134 -23
- package/dist/client.js +201 -77
- 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 +26 -4
- package/dist/http2.d.ts +8 -0
- package/dist/http2.js +22 -1
- package/dist/models.gen.d.ts +663 -43
- package/dist/pty.d.ts +11 -14
- package/dist/pty.js +13 -11
- 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/cjs/pty.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive terminals: `GET /v1/sandboxes/:id/pty` over a WebSocket (RFC 6455).
|
|
3
3
|
*
|
|
4
|
-
* The protocol:
|
|
4
|
+
* The protocol (`promigence.pty.v2`):
|
|
5
5
|
*
|
|
6
|
-
* client → server
|
|
6
|
+
* client → server first, one text frame `{"start":{"cmd"?,"secrets"?}}`: what to run and the secret
|
|
7
|
+
* names it gets, inside the connection, never in a header or the URL; then
|
|
8
|
+
* binary = keystrokes; text = `{"resize":{cols,rows}}` | `{"signal":N}` | `{"eof":true}`
|
|
7
9
|
* server → client binary = terminal output; text = `{"event":"open",…}` first and
|
|
8
|
-
* `{"event":"exit"|"error"|"closed",…}` last, then a Close frame
|
|
10
|
+
* `{"event":"exit"|"error"|"closed",…}` last, then a Close frame. A terminal that
|
|
11
|
+
* cannot open says so in its first frame: `{"event":"error","status",…}`, then Close.
|
|
9
12
|
*
|
|
10
13
|
* Zero dependencies, so this is a small client over `node:http` rather than the global
|
|
11
14
|
* `WebSocket`: that one cannot send an `Authorization` header, and a key in the query string is
|
|
@@ -32,26 +35,20 @@ export interface PtyExit {
|
|
|
32
35
|
message: string;
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
|
-
/** The
|
|
36
|
-
export declare const
|
|
37
|
-
/** The header the terminal's secret **names** travel in, base64 JSON. */
|
|
38
|
-
export declare const PTY_SECRETS_HEADER = "promigence-pty-secrets";
|
|
38
|
+
/** The WebSocket subprotocol a terminal speaks: its first message is `{"start": {"cmd"?, "secrets"?}}`. */
|
|
39
|
+
export declare const PTY_PROTOCOL = "promigence.pty.v2";
|
|
39
40
|
export interface PtyConnectOpts {
|
|
40
41
|
/** `http(s)://host[:port]/v1/sandboxes/<id>/pty?cols=&rows=` — dimensions only. */
|
|
41
42
|
url: string;
|
|
42
43
|
apiKey: string;
|
|
43
44
|
userAgent: string;
|
|
44
45
|
/**
|
|
45
|
-
* The command to run
|
|
46
|
-
*
|
|
47
|
-
* It used to be `?cmd=`, with `envs` composed into it as `export K=v;` — so a terminal opened
|
|
48
|
-
* with an API key in its environment wrote that key into the query string of every request,
|
|
49
|
-
* and therefore into every access log along the path. Names and values both stay out of the
|
|
50
|
-
* URL now.
|
|
46
|
+
* The command to run. Sent in the terminal's first message, inside the connection: never in a
|
|
47
|
+
* header or the URL, where a log on the way could keep it.
|
|
51
48
|
*/
|
|
52
49
|
command?: string;
|
|
53
50
|
/** Stored secret names (or `{name, as}` bindings), resolved server-side into the terminal's
|
|
54
|
-
* environment.
|
|
51
|
+
* environment. Sent in the first message with the command. */
|
|
55
52
|
secrets?: unknown[];
|
|
56
53
|
/** The `promigence-caller` label, when one is set (`setCaller`). */
|
|
57
54
|
caller?: string;
|
package/dist/cjs/pty.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PTY_RESTART_CLOSED_EXIT = exports.PTY_RESTART_EXIT = exports.PTY_RECONNECT_MAX_WAIT_MS = exports.PTY_RECONNECT_FIRST_WAIT_MS = exports.PTY_RECONNECT_FOR_MS = exports.PTY_RECONNECT_CODE = exports.PtySession = exports.FrameReader = exports.
|
|
3
|
+
exports.PTY_RESTART_CLOSED_EXIT = exports.PTY_RESTART_EXIT = exports.PTY_RECONNECT_MAX_WAIT_MS = exports.PTY_RECONNECT_FIRST_WAIT_MS = exports.PTY_RECONNECT_FOR_MS = exports.PTY_RECONNECT_CODE = exports.PtySession = exports.FrameReader = exports.PTY_PROTOCOL = exports.PTY_RESUME_HEADER = exports.PTY_INPUT_KEEP = exports.PTY_INPUT_CHUNK = void 0;
|
|
4
|
+
exports.ptyStartMessage = ptyStartMessage;
|
|
4
5
|
exports.wsAccept = wsAccept;
|
|
5
6
|
exports.encodeFrame = encodeFrame;
|
|
6
7
|
exports.connectPty = connectPty;
|
|
@@ -11,8 +12,10 @@ exports.PTY_INPUT_CHUNK = 64 * 1024;
|
|
|
11
12
|
exports.PTY_INPUT_KEEP = 256 * 1024;
|
|
12
13
|
exports.PTY_RESUME_HEADER = "promigence-resume";
|
|
13
14
|
const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
14
|
-
exports.
|
|
15
|
-
|
|
15
|
+
exports.PTY_PROTOCOL = "promigence.pty.v2";
|
|
16
|
+
function ptyStartMessage(command, secrets) {
|
|
17
|
+
return JSON.stringify({ start: { ...(command ? { cmd: command } : {}), ...(secrets?.length ? { secrets } : {}) } });
|
|
18
|
+
}
|
|
16
19
|
function wsAccept(key) {
|
|
17
20
|
return (0, node_crypto_1.createHash)("sha1").update(key + WS_GUID).digest("base64");
|
|
18
21
|
}
|
|
@@ -258,15 +261,7 @@ function openLeg(opts, url, resume) {
|
|
|
258
261
|
authorization: `Bearer ${opts.apiKey}`,
|
|
259
262
|
"user-agent": opts.userAgent,
|
|
260
263
|
...(opts.caller ? { [caller_js_1.CALLER_HEADER]: opts.caller } : {}),
|
|
261
|
-
...(resume ? { [exports.PTY_RESUME_HEADER]: resume } : {}),
|
|
262
|
-
...(opts.command && !resume
|
|
263
|
-
? { [exports.PTY_COMMAND_HEADER]: Buffer.from(opts.command, "utf8").toString("base64") }
|
|
264
|
-
: {}),
|
|
265
|
-
...(opts.secrets?.length && !resume
|
|
266
|
-
? {
|
|
267
|
-
[exports.PTY_SECRETS_HEADER]: Buffer.from(JSON.stringify(opts.secrets), "utf8").toString("base64"),
|
|
268
|
-
}
|
|
269
|
-
: {}),
|
|
264
|
+
...(resume ? { [exports.PTY_RESUME_HEADER]: resume } : { "sec-websocket-protocol": exports.PTY_PROTOCOL }),
|
|
270
265
|
},
|
|
271
266
|
});
|
|
272
267
|
let resolveEnded;
|
|
@@ -299,6 +294,8 @@ function openLeg(opts, url, resume) {
|
|
|
299
294
|
return;
|
|
300
295
|
}
|
|
301
296
|
socket.setNoDelay(true);
|
|
297
|
+
if (!resume)
|
|
298
|
+
socket.write(encodeFrame(0x1, Buffer.from(ptyStartMessage(opts.command, opts.secrets), "utf8")));
|
|
302
299
|
const reader = new FrameReader();
|
|
303
300
|
let exit;
|
|
304
301
|
let closeCode;
|
|
@@ -336,6 +333,12 @@ function openLeg(opts, url, resume) {
|
|
|
336
333
|
...(typeof v["wall_ms"] === "number" ? { wallMs: v["wall_ms"] } : {}),
|
|
337
334
|
};
|
|
338
335
|
}
|
|
336
|
+
else if (v["event"] === "error" && !wasOpen && typeof v["status"] === "number") {
|
|
337
|
+
const e = (v["error"] && typeof v["error"] === "object" ? v["error"] : { code: v["code"], message: v["message"] });
|
|
338
|
+
fail(v["status"], JSON.stringify({ error: e }), typeof v["retry_after"] === "string" ? v["retry_after"] : undefined);
|
|
339
|
+
socket.destroy();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
339
342
|
else if (v["event"] === "error" || v["event"] === "closed") {
|
|
340
343
|
exit = {
|
|
341
344
|
exitCode: null, signal: null,
|
package/dist/cjs/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/cjs/sandbox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Sandbox = exports.DEFAULT_SNAPSHOT = exports.DEFAULT_PTY_CMD = void 0;
|
|
3
|
+
exports.Sandbox = exports.DEFAULT_SNAPSHOT = exports.DEFAULT_PTY_CMD = exports.EXTEND_OVER_CAP_FIX = void 0;
|
|
4
4
|
exports.shellQuote = shellQuote;
|
|
5
5
|
exports.ptyCommand = ptyCommand;
|
|
6
6
|
exports.toEntryInfo = toEntryInfo;
|
|
@@ -14,6 +14,8 @@ const client_js_1 = require("./client.js");
|
|
|
14
14
|
const handles_js_1 = require("./handles.js");
|
|
15
15
|
const command_errors_js_1 = require("./command-errors.js");
|
|
16
16
|
const hint_js_1 = require("./hint.js");
|
|
17
|
+
exports.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 " +
|
|
18
|
+
"sandbox with the longer `timeoutMs` and a `spendCap` that covers it.";
|
|
17
19
|
const EXEC_TIMEOUT_DEFAULT_MS = 15 * 60_000;
|
|
18
20
|
function textSink(cb) {
|
|
19
21
|
if (!cb)
|
|
@@ -47,6 +49,15 @@ function ptyCommand(opts) {
|
|
|
47
49
|
parts.push(opts.cmd ?? exports.DEFAULT_PTY_CMD);
|
|
48
50
|
return parts.join(" ");
|
|
49
51
|
}
|
|
52
|
+
function allowList(hosts) {
|
|
53
|
+
if (!Array.isArray(hosts)) {
|
|
54
|
+
throw new core_js_1.InvalidArgumentError("allowHosts must be a list of hosts", 'Pass allowHosts: ["github.com"].');
|
|
55
|
+
}
|
|
56
|
+
if (!hosts.some((h) => typeof h !== "string" || h.trim() !== "")) {
|
|
57
|
+
throw new core_js_1.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.');
|
|
58
|
+
}
|
|
59
|
+
return hosts;
|
|
60
|
+
}
|
|
50
61
|
function permString(mode) {
|
|
51
62
|
const r = (m) => `${m & 4 ? "r" : "-"}${m & 2 ? "w" : "-"}${m & 1 ? "x" : "-"}`;
|
|
52
63
|
return r((mode >> 6) & 7) + r((mode >> 3) & 7) + r(mode & 7);
|
|
@@ -99,6 +110,8 @@ async function imageSnapshot(client, opts, hooks = {}) {
|
|
|
99
110
|
create.tier = opts.tier;
|
|
100
111
|
if (opts.alias !== undefined)
|
|
101
112
|
create.alias = opts.alias;
|
|
113
|
+
if (opts.moveAlias)
|
|
114
|
+
create.moveAlias = true;
|
|
102
115
|
if (opts.registrySecret !== undefined)
|
|
103
116
|
create.registrySecret = opts.registrySecret;
|
|
104
117
|
let build = (await client.snapshots.create(create)) ?? {};
|
|
@@ -372,6 +385,9 @@ class Sandbox {
|
|
|
372
385
|
return format === "bytes" ? bytes : new TextDecoder().decode(bytes);
|
|
373
386
|
}),
|
|
374
387
|
write: async (path, data, opts = {}) => {
|
|
388
|
+
if (typeof path !== "string") {
|
|
389
|
+
throw new core_js_1.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).");
|
|
390
|
+
}
|
|
375
391
|
let body;
|
|
376
392
|
if (typeof data === "string" || data instanceof Uint8Array)
|
|
377
393
|
body = data;
|
|
@@ -566,8 +582,8 @@ class Sandbox {
|
|
|
566
582
|
start.timeoutMs = opts.timeoutMs;
|
|
567
583
|
if (opts.allowInternetAccess === false)
|
|
568
584
|
start.network = "none";
|
|
569
|
-
if (opts.allowHosts
|
|
570
|
-
start.network = { allow: opts.allowHosts };
|
|
585
|
+
if (opts.allowHosts !== undefined && opts.allowHosts !== null)
|
|
586
|
+
start.network = { allow: allowList(opts.allowHosts) };
|
|
571
587
|
if (opts.metadata)
|
|
572
588
|
start.labels = opts.metadata;
|
|
573
589
|
if (opts.secrets?.length)
|
|
@@ -705,7 +721,15 @@ class Sandbox {
|
|
|
705
721
|
return rec.state === "ready" || rec.state === "running";
|
|
706
722
|
}
|
|
707
723
|
async setTimeout(ms) {
|
|
708
|
-
|
|
724
|
+
try {
|
|
725
|
+
await this.#client.sandboxes.patch(this.sandboxId, { timeoutMs: ms });
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
if (e instanceof core_js_1.SpendCapError && e.code === "cap_exceeded") {
|
|
729
|
+
Object.defineProperty(e, "fix", { value: exports.EXTEND_OVER_CAP_FIX, enumerable: true, configurable: true });
|
|
730
|
+
}
|
|
731
|
+
throw e;
|
|
732
|
+
}
|
|
709
733
|
this.timeoutMs = ms;
|
|
710
734
|
}
|
|
711
735
|
async setAutoPause(seconds, mode) {
|
|
@@ -292,6 +292,7 @@ function start(init) {
|
|
|
292
292
|
let rrAny = 0;
|
|
293
293
|
let pumpTimer;
|
|
294
294
|
let pumpSoon;
|
|
295
|
+
let turnsHeld = false;
|
|
295
296
|
const freeNow = (l, now) => {
|
|
296
297
|
for (let i = 0; i < l.length; i++) {
|
|
297
298
|
const k = (rr + i) % l.length;
|
|
@@ -306,6 +307,8 @@ function start(init) {
|
|
|
306
307
|
const pump = () => {
|
|
307
308
|
pumpTimer = undefined;
|
|
308
309
|
pumpSoon = undefined;
|
|
310
|
+
if (turnsHeld)
|
|
311
|
+
return;
|
|
309
312
|
while (waiting.length > 0) {
|
|
310
313
|
const l = live();
|
|
311
314
|
if (l.length === 0) {
|
|
@@ -348,7 +351,7 @@ function start(init) {
|
|
|
348
351
|
rrAny = (rrAny % l.length) + 1;
|
|
349
352
|
return e;
|
|
350
353
|
}
|
|
351
|
-
if (waiting.length === 0) {
|
|
354
|
+
if (waiting.length === 0 && !turnsHeld) {
|
|
352
355
|
const now = hrNow();
|
|
353
356
|
const e = freeNow(l, now);
|
|
354
357
|
if (e !== undefined) {
|
|
@@ -529,6 +532,12 @@ function start(init) {
|
|
|
529
532
|
}
|
|
530
533
|
else if (m?.t === "crash-for-test")
|
|
531
534
|
setImmediate(() => { throw new Error("transport thread crash (test)"); });
|
|
535
|
+
else if (m?.t === "hold-turns-for-test")
|
|
536
|
+
turnsHeld = true;
|
|
537
|
+
else if (m?.t === "release-turns-for-test") {
|
|
538
|
+
turnsHeld = false;
|
|
539
|
+
pump();
|
|
540
|
+
}
|
|
532
541
|
});
|
|
533
542
|
void open();
|
|
534
543
|
}
|