@promigence/sdk 0.1.0-beta.16 → 0.1.0-beta.17

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/cjs/proxy.js CHANGED
@@ -10,6 +10,7 @@ const node_http_1 = require("node:http");
10
10
  const node_https_1 = require("node:https");
11
11
  const node_tls_1 = require("node:tls");
12
12
  const node_stream_1 = require("node:stream");
13
+ const node_zlib_1 = require("node:zlib");
13
14
  const ca_guard_js_1 = require("./ca-guard.js");
14
15
  function withCertGuard(existing) {
15
16
  return existing === undefined ? ca_guard_js_1.guardCertChain : (0, ca_guard_js_1.makeCertChainGuard)(existing);
@@ -138,7 +139,32 @@ async function bodyOf(init) {
138
139
  return Buffer.from(b);
139
140
  return Buffer.from(await new Response(b).arrayBuffer());
140
141
  }
141
- function toResponse(res) {
142
+ const ACCEPT_ENCODING = "br, gzip";
143
+ function decoded(res) {
144
+ const enc = res.headers["content-encoding"];
145
+ if (typeof enc !== "string")
146
+ return res;
147
+ const steps = [];
148
+ for (const c of enc.toLowerCase().split(",").map((s) => s.trim()).reverse()) {
149
+ if (c === "" || c === "identity")
150
+ continue;
151
+ if (c === "gzip" || c === "x-gzip") {
152
+ steps.push((0, node_zlib_1.createGunzip)({ flush: node_zlib_1.constants.Z_SYNC_FLUSH, finishFlush: node_zlib_1.constants.Z_SYNC_FLUSH }));
153
+ }
154
+ else if (c === "br") {
155
+ steps.push((0, node_zlib_1.createBrotliDecompress)({
156
+ flush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH, finishFlush: node_zlib_1.constants.BROTLI_OPERATION_FLUSH,
157
+ }));
158
+ }
159
+ else {
160
+ return res;
161
+ }
162
+ }
163
+ if (steps.length === 0)
164
+ return res;
165
+ return (0, node_stream_1.pipeline)([res, ...steps], () => { });
166
+ }
167
+ function toResponse(res, head) {
142
168
  const headers = new Headers();
143
169
  for (const [k, v] of Object.entries(res.headers)) {
144
170
  if (v === undefined)
@@ -151,7 +177,8 @@ function toResponse(res) {
151
177
  }
152
178
  const status = res.statusCode ?? 502;
153
179
  const noBody = status === 204 || status === 304;
154
- return new Response(noBody ? null : node_stream_1.Readable.toWeb(res), {
180
+ const body = head ? res : decoded(res);
181
+ return new Response(noBody ? null : node_stream_1.Readable.toWeb(body), {
155
182
  status, statusText: res.statusMessage ?? "", headers,
156
183
  });
157
184
  }
@@ -164,6 +191,8 @@ function proxyFetch(proxy) {
164
191
  const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
165
192
  const method = (init?.method ?? "GET").toUpperCase();
166
193
  const headers = headersOf(init);
194
+ if (headers["accept-encoding"] === undefined)
195
+ headers["accept-encoding"] = ACCEPT_ENCODING;
167
196
  const body = await bodyOf(init);
168
197
  if (body)
169
198
  headers["content-length"] = String(body.length);
@@ -186,7 +215,7 @@ function proxyFetch(proxy) {
186
215
  const done = (res) => {
187
216
  settled = true;
188
217
  signal?.removeEventListener("abort", onAbort);
189
- resolve(toResponse(res));
218
+ resolve(toResponse(res, method === "HEAD"));
190
219
  };
191
220
  if (url.protocol === "http:") {
192
221
  const req = proxyRequest({
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { inspect, type InspectOptions } from "node:util";
13
13
  import { type CommandResult, type CommandRunOpts, type ForkOpts, type OptimizeFor, type SandboxOpts, type Tier } from "./core.js";
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";
14
+ import { PromigenceClient, type BackgroundExecRecord, type DirEntry, type DirListOpts, type ExtractResult, type FileInfo, type FileMatch, type Page, type PortExposeOpts, 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
17
  /** `setTimeout`'s fix when the longer timeout does not fit under the run's spend cap. */
@@ -289,6 +289,7 @@ export declare class Sandbox {
289
289
  /**
290
290
  * Run `cmd` and resolve with its result. A non-zero exit throws `CommandExitError`; a command
291
291
  * killed at its own `timeoutMs` throws `CommandTimeoutError` (a `CommandExitError` too).
292
+ * With `throwOnExit: false` both resolve instead, and the result's `timedOut` tells them apart.
292
293
  */
293
294
  run(cmd: string, opts?: ForegroundRunOpts): Promise<CommandResult>;
294
295
  /** Background commands in this sandbox (running and ended, until killed with forget). */
@@ -325,7 +326,11 @@ export declare class Sandbox {
325
326
  * `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
326
327
  */
327
328
  readonly ports: {
328
- expose(port: number): Promise<PortRecord>;
329
+ /**
330
+ * Publish `port`: `{ length: "long" }` for a longer, harder-to-guess URL (apps with sensitive
331
+ * data); omitted, the length this sandbox's project or organization chose (short by default).
332
+ */
333
+ expose(port: number, opts?: PortExposeOpts): Promise<PortRecord>;
329
334
  list(): Promise<PortRecord[]>;
330
335
  /** A token revokes that URL; a port number every URL for it. Returns the revoked tokens. */
331
336
  revoke(tokenOrPort: string | number): Promise<string[]>;
@@ -490,8 +495,9 @@ export declare class Sandbox {
490
495
  * the window may be. Bring it back with `resume()`, which is the warm path.
491
496
  *
492
497
  * `mode: "activity"` is for an agent that runs inside the sandbox on its own: it also waits
493
- * until nothing inside has used CPU, moved a byte or held a connection open for the window
494
- * (`AutoPauseMode`). Omitted, the mode is left as it is (`"requests"` unless set).
498
+ * until, for the whole window, the sandbox has used under 5% of one core, sent and received no
499
+ * network byte and held no connection out open; disk writes do not count (`AutoPauseMode`).
500
+ * Omitted, the mode is left as it is (`"requests"` unless set).
495
501
  */
496
502
  setAutoPause(seconds: number, mode?: AutoPauseMode): Promise<void>;
497
503
  /** The idle rule in force: seconds before it pauses itself, `0` when it never does. */
@@ -513,7 +519,8 @@ export declare class Sandbox {
513
519
  * full `url` with its scheme.
514
520
  *
515
521
  * Async, because the address is a capability minted on demand rather than
516
- * a hostname derived from the id. An existing URL for the port is reused, not re-minted.
522
+ * a hostname derived from the id. An existing URL for the port is reused, not re-minted; a new
523
+ * one has the length this sandbox's project or organization chose (short by default).
517
524
  *
518
525
  * If your account's preview URLs aren't on, this throws `PreviewsUnavailableError`; write to
519
526
  * support@promigence.ai to have them turned on, and meanwhile reach the port with
@@ -371,7 +371,7 @@ class Sandbox {
371
371
  },
372
372
  };
373
373
  this.ports = {
374
- expose: (port) => this.#client.sandboxes.ports.expose(this.sandboxId, port),
374
+ expose: (port, opts) => this.#client.sandboxes.ports.expose(this.sandboxId, port, opts),
375
375
  list: () => this.#client.sandboxes.ports.list(this.sandboxId),
376
376
  revoke: async (tokenOrPort) => (await this.#client.sandboxes.ports.revoke(this.sandboxId, tokenOrPort)).revoked,
377
377
  };
package/dist/client.d.ts CHANGED
@@ -178,6 +178,8 @@ export interface SandboxRef extends QueuedInfo {
178
178
  }
179
179
  /** How a grader's result is read. */
180
180
  export type ScoreMode = M.ScoreMode;
181
+ /** Which of a run's sandboxes are kept to reopen where they ended (`RunStartOpts.capture`). */
182
+ export type CaptureMode = M.CaptureMode;
181
183
  /**
182
184
  * Score the agent's work after the episode, in a clean copy of the snapshot with the agent's
183
185
  * changes applied. `holdout_cmd` is your own second test set; the report carries the gap
@@ -396,6 +398,12 @@ export interface SecretRecord {
396
398
  raw?: "allowed" | "refused";
397
399
  /** Whether the value is kept out of the bound hosts' answers (present when `hosts` is). */
398
400
  scan_responses?: boolean;
401
+ /**
402
+ * On `secrets.set` only: things to know about the value just stored, one `code: sentence` each;
403
+ * absent when there are none. `api_key_value`: the value holds an API key of this service, which
404
+ * any command in a sandbox given this secret can use.
405
+ */
406
+ warnings?: string[];
399
407
  }
400
408
  /**
401
409
  * `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
@@ -745,8 +753,25 @@ export interface OrgRecord {
745
753
  created_at: string;
746
754
  /** Days command output is kept after a sandbox ends; `null` = the default, 7. */
747
755
  output_retention_days?: number | null;
748
- /** Keep a failed sandbox to reopen where it broke, when a run does not say; absent or `null` = the default. */
756
+ /**
757
+ * This organization's choice: keep a failed sandbox to reopen where it broke, when a run does not say (`capture`);
758
+ * absent or `null` = not chosen, so the default applies: not kept unless the organization or project chose to keep
759
+ * them. What applies is `failure_captures_effective`.
760
+ */
749
761
  failure_captures?: boolean | null;
762
+ /** The length of new preview URLs when a request does not say; absent or `null` = the default, short. */
763
+ preview_link_length?: PreviewLinkLength | null;
764
+ /** What applies to a failed run that does not say: kept or not, and whose choice decided it. */
765
+ failure_captures_effective?: CapturesEffective;
766
+ }
767
+ /**
768
+ * What applies to a failed run's sandboxes when the run does not say (`capture`): `kept`, and `from`, whose choice
769
+ * decided it: `project`, `org`, or `default` (neither chose: not kept unless the organization or project chose to
770
+ * keep them). A run's own `capture` is followed either way.
771
+ */
772
+ export interface CapturesEffective {
773
+ kept: boolean;
774
+ from: "project" | "org" | "default" | (string & {});
750
775
  }
751
776
  export interface ProjectRecord {
752
777
  project_id: string;
@@ -758,8 +783,16 @@ export interface ProjectRecord {
758
783
  monthly_cap_usd?: number | null;
759
784
  /** Days this project's command output is kept after a sandbox ends; `null` = the organization's. */
760
785
  output_retention_days?: number | null;
761
- /** Keep this project's failed sandboxes to reopen, when a run does not say; absent or `null` = the organization's. */
786
+ /**
787
+ * This project's choice: keep its failed sandboxes to reopen, when a run does not say (`capture`); absent or `null` =
788
+ * the organization's choice, and where neither chose, the default: not kept unless the organization or project
789
+ * chose to keep them. What applies is `failure_captures_effective`.
790
+ */
762
791
  failure_captures?: boolean | null;
792
+ /** The length of this project's new preview URLs when a request does not say; absent or `null` = the organization's. */
793
+ preview_link_length?: PreviewLinkLength | null;
794
+ /** What applies to its failed runs that do not say: kept or not, and whose choice decided it. */
795
+ failure_captures_effective?: CapturesEffective;
763
796
  created_at: string;
764
797
  }
765
798
  /** The longest command output may be kept after a sandbox ends, in days. */
@@ -1383,7 +1416,7 @@ export interface SshRevoked {
1383
1416
  export interface PortRecord {
1384
1417
  sandbox_id: string;
1385
1418
  port: number;
1386
- /** 256-bit capability; anyone with the URL reaches the port. */
1419
+ /** The URL's own name: anyone with the URL reaches the port. Pass it to `revoke` to take this one URL down. */
1387
1420
  token: string;
1388
1421
  url: string;
1389
1422
  /** The same token as a DNS label, for a wildcard-DNS deployment (`<label>.<suffix>`). */
@@ -1391,6 +1424,25 @@ export interface PortRecord {
1391
1424
  created_unix_ns?: number;
1392
1425
  /** When the URL stops working on its own (`ttl_s`); `null` for one that works until revoked. */
1393
1426
  expires_unix_ns?: number | null;
1427
+ /**
1428
+ * `"short"` (`https://brave-otter-48213.<domain>/`, the default) or `"long"` (a longer,
1429
+ * harder-to-guess URL, for apps with sensitive data).
1430
+ */
1431
+ length?: PreviewLinkLength;
1432
+ }
1433
+ /**
1434
+ * How long a preview URL is: `"short"` (the default, `https://brave-otter-48213.<domain>/`) or
1435
+ * `"long"` (a longer, harder-to-guess URL, for apps with sensitive data). Custom domains are
1436
+ * coming soon.
1437
+ */
1438
+ export type PreviewLinkLength = "short" | "long";
1439
+ /** Options of `ports.expose`. */
1440
+ export interface PortExposeOpts {
1441
+ /**
1442
+ * `"short"` or `"long"`. Omitted: the length the sandbox's project chose, else its
1443
+ * organization's, else short.
1444
+ */
1445
+ length?: PreviewLinkLength;
1394
1446
  }
1395
1447
  /** A key named without its secret (`/me`'s `key`, the key lists). */
1396
1448
  export interface KeyRef {
@@ -1422,8 +1474,31 @@ export interface MeResponse {
1422
1474
  max_concurrency?: number;
1423
1475
  max_session_s?: number | null;
1424
1476
  max_sandbox_vcpus?: number | null;
1477
+ /** Everything stored together, in GB: snapshots, paused sandboxes and saved failures; `null` for no limit. */
1478
+ max_storage_gb?: number | null;
1479
+ /**
1480
+ * The storage saved failures may use, in GB: when a new one would pass it, the oldest are removed to make room;
1481
+ * `null` for no limit.
1482
+ */
1483
+ max_capture_storage_gb?: number | null;
1484
+ /** Snapshots that may be kept; `null` for no limit. */
1485
+ max_snapshots?: number | null;
1486
+ /** Paused sandboxes that may be kept; `null` for no limit. */
1487
+ max_paused?: number | null;
1425
1488
  [extra: string]: number | null | undefined;
1426
1489
  };
1490
+ /**
1491
+ * What the organisation stores now, in bytes (a GB of `limits` is 2^30 bytes): everything together (`used_bytes`,
1492
+ * against `limits.max_storage_gb`) and its saved failures (`captures_bytes`, against
1493
+ * `limits.max_capture_storage_gb`), with the counts. Absent from a server before it.
1494
+ */
1495
+ storage?: {
1496
+ used_bytes: number;
1497
+ captures_bytes: number;
1498
+ snapshots: number;
1499
+ paused: number;
1500
+ captures: number;
1501
+ } | null;
1427
1502
  /**
1428
1503
  * Sandboxes holding capacity now (queued, starting, ready or running; paused ones hold none)
1429
1504
  * against the limit.
@@ -1603,6 +1678,12 @@ export interface RunStartOpts {
1603
1678
  verifyEach?: boolean;
1604
1679
  network?: NetworkMode;
1605
1680
  optimizeFor?: OptimizeFor;
1681
+ /**
1682
+ * Which of this run's sandboxes are kept to reopen where they ended (`runs.open`): `on_failure`
1683
+ * keeps one whose command failed, `off` keeps none, `always` keeps every one. Left out, the
1684
+ * default applies.
1685
+ */
1686
+ capture?: CaptureMode;
1606
1687
  /**
1607
1688
  * Secrets for every sandbox of this run. An `exec` is no longer required: a run that names
1608
1689
  * its secrets here gives them to every command run against every one of its sandboxes,
@@ -1666,9 +1747,9 @@ export interface ExecOpts {
1666
1747
  */
1667
1748
  secrets?: SecretBinding[];
1668
1749
  /**
1669
- * Cancel an in-flight exec, streamed or not: the client stops waiting and the request is closed,
1670
- * which ends the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by
1671
- * the caller's signal"); a streamed one rejects with the signal's `AbortError`.
1750
+ * Cancel an in-flight exec, streamed or not: the client stops waiting and tells the service to
1751
+ * stop the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by the
1752
+ * caller's signal"); a streamed one rejects with the signal's `AbortError`.
1672
1753
  */
1673
1754
  signal?: AbortSignal;
1674
1755
  /**
@@ -2062,6 +2143,9 @@ export declare class PromigenceClient {
2062
2143
  private guardDispatcher;
2063
2144
  private dispatcherFor;
2064
2145
  private againKeyed;
2146
+ private heldExec;
2147
+ private reattachBuffered;
2148
+ private cancelHeldExec;
2065
2149
  request<T>(opts: RequestOpts): Promise<T>;
2066
2150
  /**
2067
2151
  * A request whose body — in either direction — is **bytes, not JSON** (`files get|put`).
@@ -2259,15 +2343,29 @@ export declare class PromigenceClient {
2259
2343
  /** `PATCH /orgs/:org/projects/:project {name}` — rename it (the `default` project keeps its name). */
2260
2344
  rename: (org: string, project: string, name: string) => Promise<ProjectRecord>;
2261
2345
  /**
2262
- * `PATCH /orgs/:org/projects/:project` — rename it, set how long its command output is kept, or
2263
- * whether its failed sandboxes are kept to reopen (that one on its own). The settings need an owner signed in.
2346
+ * `PATCH /orgs/:org/projects/:project` — rename it, set how long its command output is kept,
2347
+ * whether its failed sandboxes are kept to reopen (on its own; `null` = the organization's choice, and
2348
+ * where neither chose, not kept unless the organization or project chose to keep them), or how long
2349
+ * its new preview URLs are (on its own). The settings need an owner signed in.
2264
2350
  */
2265
2351
  update: (org: string, project: string, update: {
2266
2352
  name?: string;
2267
2353
  outputRetentionDays?: number | null;
2268
2354
  failureCaptures?: boolean | null;
2355
+ previewLinkLength?: PreviewLinkLength | null;
2269
2356
  }) => Promise<ProjectRecord>;
2270
2357
  };
2358
+ /**
2359
+ * `PATCH /orgs/:org` — rename it, set how long its command output is kept, whether its failed
2360
+ * sandboxes are kept to reopen, or how long its new preview URLs are (those two each on their
2361
+ * own). The settings need an owner signed in.
2362
+ */
2363
+ update: (org: string, update: {
2364
+ name?: string;
2365
+ outputRetentionDays?: number | null;
2366
+ failureCaptures?: boolean | null;
2367
+ previewLinkLength?: PreviewLinkLength | null;
2368
+ }) => Promise<OrgRecord>;
2271
2369
  };
2272
2370
  private group_orgs;
2273
2371
  readonly credits: {
@@ -2441,7 +2539,13 @@ export declare class PromigenceClient {
2441
2539
  */
2442
2540
  private waitLoop;
2443
2541
  readonly sandboxes: {
2444
- /** `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`. */
2542
+ /**
2543
+ * `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`.
2544
+ *
2545
+ * If the connection drops while the command runs, the SDK picks the command up again where the
2546
+ * service keeps it and returns its own answer; when it cannot, it throws `ExecLostError`. The
2547
+ * command is never sent a second time.
2548
+ */
2445
2549
  exec: (sandboxId: string, opts: ExecOpts) => Promise<ExecResponse>;
2446
2550
  /**
2447
2551
  * `POST /sandboxes/:id/execs` with `Accept: text/event-stream`.
@@ -2453,6 +2557,10 @@ export declare class PromigenceClient {
2453
2557
  *
2454
2558
  * Uses `fetch`'s streaming body rather than `res.text()`, which is what `request()` does and
2455
2559
  * why it cannot be reused here: `await res.text()` is precisely the buffering this removes.
2560
+ *
2561
+ * If the connection drops while the command runs, the SDK picks the command up again where the
2562
+ * service keeps it, and the stream goes on where it left off; when it cannot, it throws
2563
+ * `ExecLostError`. The command is never sent a second time.
2456
2564
  */
2457
2565
  execStream: (sandboxId: string, opts: ExecOpts, on?: {
2458
2566
  stdout?: (chunk: Uint8Array) => void;
@@ -2547,7 +2655,12 @@ export declare class PromigenceClient {
2547
2655
  * `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
2548
2656
  */
2549
2657
  ports: {
2550
- expose: (sandboxId: string, port: number) => Promise<PortRecord>;
2658
+ /**
2659
+ * Publish a port: `{ length: "long" }` for a longer, harder-to-guess URL (apps with
2660
+ * sensitive data); omitted, the length the sandbox's project or organization chose
2661
+ * (short by default).
2662
+ */
2663
+ expose: (sandboxId: string, port: number, opts?: PortExposeOpts) => Promise<PortRecord>;
2551
2664
  list: (sandboxId: string) => Promise<PortRecord[]>;
2552
2665
  /** A token revokes that URL; a port number revokes every URL for that port. */
2553
2666
  revoke: (sandboxId: string, tokenOrPort: string | number) => Promise<{