@promigence/sdk 0.1.0-beta.3 → 0.1.0-beta.5

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.
@@ -359,7 +359,10 @@ export interface BuildJoined {
359
359
  */
360
360
  source: unknown;
361
361
  /**
362
- * `queued` | `running` | `verified` | `rejected` | `failed`.
362
+ * `queued` | `running` | `verified` | `built` | `rejected` | `failed`. `built` is a
363
+ * finished build that stored its snapshot with no verification command to run (an image
364
+ * built without `verify`): done and ready to start, like `verified`, but nothing was
365
+ * verified.
363
366
  */
364
367
  state: string;
365
368
  tier: string;
@@ -371,8 +374,8 @@ export interface BuildJoined {
371
374
  /**
372
375
  * True when the build finished `verified` and its verification command passed. False while
373
376
  * it is queued or running, when it failed or was rejected, and for an image built without
374
- * `verify` (stored, but nothing was verified). Nothing is stored and nothing is charged if
375
- * the command exits non-zero.
377
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
378
+ * is charged if the command exits non-zero.
376
379
  */
377
380
  verified: boolean;
378
381
  /**
@@ -402,7 +405,8 @@ export interface BuildLogView {
402
405
  */
403
406
  log: Array<string>;
404
407
  /**
405
- * `queued` | `running` | `verified` | `rejected` | `failed`.
408
+ * `queued` | `running` | `verified` | `built` | `rejected` | `failed` (`built`: stored with
409
+ * no verification command to run).
406
410
  */
407
411
  state: string;
408
412
  }
@@ -506,7 +510,10 @@ export interface BuildView {
506
510
  */
507
511
  source: unknown;
508
512
  /**
509
- * `queued` | `running` | `verified` | `rejected` | `failed`.
513
+ * `queued` | `running` | `verified` | `built` | `rejected` | `failed`. `built` is a
514
+ * finished build that stored its snapshot with no verification command to run (an image
515
+ * built without `verify`): done and ready to start, like `verified`, but nothing was
516
+ * verified.
510
517
  */
511
518
  state: string;
512
519
  tier: string;
@@ -518,8 +525,8 @@ export interface BuildView {
518
525
  /**
519
526
  * True when the build finished `verified` and its verification command passed. False while
520
527
  * it is queued or running, when it failed or was rejected, and for an image built without
521
- * `verify` (stored, but nothing was verified). Nothing is stored and nothing is charged if
522
- * the command exits non-zero.
528
+ * `verify` (state `built`: stored, but nothing was verified). Nothing is stored and nothing
529
+ * is charged if the command exits non-zero.
523
530
  */
524
531
  verified: boolean;
525
532
  /**
@@ -821,6 +828,68 @@ export interface CliTokenCreate {
821
828
  export type Code = "fork_failed" | "start_timeout" | "readiness_failed" | "verify_too_slow" | "verify_each_failed" | "sandbox_unresponsive" | "oom_within_tier" | "disk_full_within_tier" | "sandbox_interrupted" | "integrity_mismatch" | "egress_unavailable" | "service_timeout" | "capacity_busy" | "verify_did_not_cover" | "nonzero_exit" | "task_timeout" | "oom_over_tier" | "disk_over_tier" | "cap_killed" | "cap_exceeded" | "aborted_by_client" | "expired" | "not_started" | "superseded" | "usage" | "internal" | "auth" | "not_found" | "unreachable" | "rate_limited" | "timeout" | "not_verified" | "enoent" | "trial_credits_exhausted" | "card_required" | "concurrency_limit" | "egress_cap_reached" | "network_policy_unsupported" | "network_host_refused" | "toolchain_mismatch" | "resume_unavailable" | "sandbox_unreachable" | "region_at_capacity" | "tier_unavailable" | "snapshot_unavailable" | "capacity_limit" | "build_timeout" | "builder_unavailable" | "builder_busy" | "overloaded" | "preempted" | "grader_failed" | "grade_not_passed" | "shutdown_by_task" | "modified_by_task" | "cap_paused" | "credits_paused" | "forbidden" | "snapshot_incompatible" | "storage_limit_reached" | "dockerfile_build_failed" | "image_unresolvable" | "registry_unavailable" | "credit_unclaimed" | "billing_unavailable" | "project_cap_reached" | "spend_limit_reached" | "tier_over_plan" | "secrets_in_snapshot" | "secrets_unavailable" | "secret_binding_unsupported" | "idempotency_key_reused" | "idempotency_key_in_use" | "repo_credentials_not_supported" | "account_exists" | "invite_invalid" | "invite_required" | "key_expired";
822
829
  export interface Concurrency {
823
830
  }
831
+ /**
832
+ * One request to connect a code host (`github` or `gitlab`) to the organization, started by the
833
+ * person whose key or console session asked for it, and how it ended.
834
+ */
835
+ export interface ConnectRequest {
836
+ /**
837
+ * The account on the code host, once it is known.
838
+ */
839
+ account?: string | null;
840
+ /**
841
+ * RFC 3339.
842
+ */
843
+ created_at: string;
844
+ /**
845
+ * RFC 3339: when it ended; absent while it is pending.
846
+ */
847
+ finished_at?: string | null;
848
+ /**
849
+ * One or two sentences for a person: what happened, then what to do next.
850
+ */
851
+ message: string;
852
+ /**
853
+ * `github` or `gitlab`.
854
+ */
855
+ provider: string;
856
+ /**
857
+ * `icr_` and 32 hex digits.
858
+ */
859
+ request_id: string;
860
+ /**
861
+ * `pending` while it is not finished; then how it ended: `connected`, `updated` (already
862
+ * connected to this organization; its repository access changed), `already_connected`,
863
+ * `pending_provider_approval` (an owner of the account must approve it on the code host
864
+ * first), `declined`, `other_org` (connected to another organization), `needs_admin`,
865
+ * `not_installer`, `wrong_user` (the connection page was opened signed in to another
866
+ * account), `expired` or `failed`.
867
+ */
868
+ state: string;
869
+ }
870
+ /**
871
+ * `POST /v1/integrations/{github,gitlab}/requests`: a connection started for you in the console.
872
+ * Open `connect_url` in a browser signed in to the console as you, then read
873
+ * `GET /v1/integrations/{github,gitlab}/requests/{request_id}` for how it ends.
874
+ */
875
+ export interface ConnectTicket {
876
+ /**
877
+ * The console page that starts the connection: it carries the ticket and nothing else.
878
+ */
879
+ connect_url: string;
880
+ /**
881
+ * When the ticket stops working (RFC 3339): 5 minutes.
882
+ */
883
+ expires_at: string;
884
+ /**
885
+ * The connection request it starts (`icr_` and 32 hex digits).
886
+ */
887
+ request_id: string;
888
+ /**
889
+ * `ict_` and 32 hex digits: used once, by you only, within 5 minutes.
890
+ */
891
+ ticket_id: string;
892
+ }
824
893
  export interface Cost {
825
894
  /**
826
895
  * Σ `billable_seconds` over the run's sandboxes.
@@ -1436,7 +1505,8 @@ export interface FileWritten {
1436
1505
  */
1437
1506
  chunks: number;
1438
1507
  /**
1439
- * The file's permission bits, octal (`644`).
1508
+ * The file's permission bits after the write, octal (`644`). Without `?mode=` a file that
1509
+ * already existed keeps its own, and a new file is `644`.
1440
1510
  */
1441
1511
  mode: string;
1442
1512
  /**
@@ -2105,7 +2175,9 @@ export interface InviteView {
2105
2175
  */
2106
2176
  export interface KeyCreate {
2107
2177
  /**
2108
- * `full` (the default) | `run` (use sandboxes, nothing else) | `read` (lists and reports).
2178
+ * `full` (the default) | `run` (sandboxes, runs and builds, whose runs can use the project's
2179
+ * stored secrets; no changes to secrets, keys or billing) | `read` (reads everything in the
2180
+ * project, file contents and command output included; changes nothing).
2109
2181
  */
2110
2182
  access?: string | null;
2111
2183
  /**
@@ -2166,7 +2238,9 @@ export interface KeyRef {
2166
2238
  */
2167
2239
  export interface KeyView {
2168
2240
  /**
2169
- * What the key may do: `full`, `run` (use sandboxes) or `read` (lists and reports).
2241
+ * What the key may do: `full`, `run` (sandboxes, runs and builds, whose runs can use the
2242
+ * project's stored secrets; no changes to secrets, keys or billing) or `read` (reads
2243
+ * everything in the project, file contents and command output included; changes nothing).
2170
2244
  */
2171
2245
  access?: string;
2172
2246
  /**
@@ -2354,8 +2428,8 @@ export interface Me {
2354
2428
  */
2355
2429
  location?: string | null;
2356
2430
  /**
2357
- * The same place as a short code for an icon: `us-east`, `us-west`, `us-midwest`, `eu-west` or
2358
- * `india`. Only beside `location`; a code you do not know is fine to ignore.
2431
+ * The same place as a short code, for example `us-east`. Only beside `location`; new codes may
2432
+ * appear, and a code you do not know is fine to ignore.
2359
2433
  */
2360
2434
  location_code?: string | null;
2361
2435
  /**
@@ -2540,7 +2614,9 @@ export interface OrgDeleted {
2540
2614
  */
2541
2615
  export interface OrgKeyCreate {
2542
2616
  /**
2543
- * `full` (the default) | `run` (use sandboxes, nothing else) | `read` (lists and reports).
2617
+ * `full` (the default) | `run` (sandboxes, runs and builds, whose runs can use the project's
2618
+ * stored secrets; no changes to secrets, keys or billing) | `read` (reads everything in the
2619
+ * project, file contents and command output included; changes nothing).
2544
2620
  */
2545
2621
  access?: string | null;
2546
2622
  /**
@@ -2577,7 +2653,9 @@ export interface OrgKeyCreate {
2577
2653
  */
2578
2654
  export interface OrgKeyView {
2579
2655
  /**
2580
- * What the key may do: `full`, `run` (use sandboxes) or `read` (lists and reports).
2656
+ * What the key may do: `full`, `run` (sandboxes, runs and builds, whose runs can use the
2657
+ * project's stored secrets; no changes to secrets, keys or billing) or `read` (reads
2658
+ * everything in the project, file contents and command output included; changes nothing).
2581
2659
  */
2582
2660
  access?: string;
2583
2661
  /**
@@ -3423,7 +3501,7 @@ export interface Run {
3423
3501
  */
3424
3502
  export interface RunCanceled {
3425
3503
  /**
3426
- * How many queued sandboxes were dropped. They were never started and are never billed.
3504
+ * How many queued sandboxes were dropped. They were never started, so they are not billed.
3427
3505
  */
3428
3506
  canceled: number;
3429
3507
  ok: boolean;
@@ -4050,7 +4128,8 @@ export interface SandboxList {
4050
4128
  next_cursor?: string | null;
4051
4129
  sandboxes: Array<SandboxView>;
4052
4130
  /**
4053
- * The state filter this list was produced with; empty when none was given.
4131
+ * The state filter this list was produced with; empty when none was given. `queued` keeps
4132
+ * every waiting sandbox; `awaiting_capacity` only those waiting for capacity being added.
4054
4133
  */
4055
4134
  state: Array<string>;
4056
4135
  }
@@ -4357,7 +4436,7 @@ export interface SandboxView {
4357
4436
  cost_usd?: number | null;
4358
4437
  /**
4359
4438
  * Milliseconds from the start request, after any time queued, to the sandbox being handed
4360
- * over: it does not include `queued_ms`. `total_ms` is the whole wait.
4439
+ * over: it does not include `queued_ms`. Add `queued_ms` for the whole wait.
4361
4440
  */
4362
4441
  e2e_ms?: number | null;
4363
4442
  /**
@@ -4641,13 +4720,13 @@ export interface SecretStored {
4641
4720
  */
4642
4721
  next: Array<string>;
4643
4722
  /**
4644
- * What a sandbox that names this secret is given in its place: it works in requests to the
4645
- * bound hosts and nowhere else. Present when `hosts` is.
4723
+ * What the variable holds, in place of the value, in a sandbox that names this secret.
4724
+ * Present when `hosts` is.
4646
4725
  */
4647
4726
  placeholder?: string | null;
4648
4727
  /**
4649
- * `refused` when the value itself can never be given to a sandbox, `allowed` otherwise.
4650
- * Present when `hosts` is.
4728
+ * `refused` when the value itself can never be given to a sandbox, `allowed` (the default)
4729
+ * otherwise. Present when `hosts` is.
4651
4730
  */
4652
4731
  raw?: string | null;
4653
4732
  /**
@@ -4655,11 +4734,10 @@ export interface SecretStored {
4655
4734
  */
4656
4735
  updated_at: string;
4657
4736
  /**
4658
- * For each of `hosts`, in the same order, the address a sandbox sends its requests to,
4659
- * with the request's path added after it. Requests sent there are authenticated with the
4660
- * secret; a request made straight to the host (`https://<host>/…`)
4661
- * carries the placeholder, and the host refuses it. For a `*.` host, put the name
4662
- * in place of the `*`. Present when `hosts` is.
4737
+ * For each of `hosts`, in the same order, the address a sandbox sends that host's requests
4738
+ * to, with the request's path added after it. Requests sent there are authenticated with the
4739
+ * secret; a request sent straight to the host is not. For a `*.` host, put the name in place
4740
+ * of the `*`. Present when `hosts` is.
4663
4741
  */
4664
4742
  urls?: Array<string>;
4665
4743
  }
@@ -4686,14 +4764,15 @@ export interface SecretSummary {
4686
4764
  headers?: Array<string>;
4687
4765
  /**
4688
4766
  * The hosts the secret is bound to. Absent when it is not bound: a sandbox that names it is
4689
- * then given the value itself. When present, a sandbox is given a placeholder instead,
4690
- * which works in requests to these hosts and nowhere else.
4767
+ * then given the value itself. When present, requests to these hosts sent through `urls`
4768
+ * are authenticated with it, and the value stays out of the sandbox unless a run asks for
4769
+ * it and `raw` allows it.
4691
4770
  */
4692
4771
  hosts?: Array<string>;
4693
4772
  name: string;
4694
4773
  /**
4695
- * `refused` when the value itself can never be given to a sandbox, `allowed` otherwise.
4696
- * Present when `hosts` is.
4774
+ * `refused` when the value itself can never be given to a sandbox, `allowed` (the default)
4775
+ * otherwise. Present when `hosts` is.
4697
4776
  */
4698
4777
  raw?: string | null;
4699
4778
  /**
@@ -4725,8 +4804,9 @@ export interface SecretUseBound {
4725
4804
  */
4726
4805
  as?: string | null;
4727
4806
  /**
4728
- * `raw` delivers the value itself to a secret bound to hosts; omitted, such a secret is
4729
- * delivered as a placeholder that only works toward its hosts.
4807
+ * `raw` gives the command the value itself of a secret bound to hosts (refused when the
4808
+ * secret's `raw` is `refused`); omitted, the value stays out of the sandbox and requests to
4809
+ * its hosts sent through its `urls` are authenticated with it.
4730
4810
  */
4731
4811
  mode?: SecretUseMode | null;
4732
4812
  /**
@@ -4866,6 +4946,14 @@ export interface SnapshotCreate {
4866
4946
  * Build from a Dockerfile.
4867
4947
  */
4868
4948
  dockerfile?: DockerfileSource | null;
4949
+ /**
4950
+ * Image and Dockerfile forms: environment variables for every command, terminal and SSH
4951
+ * session in sandboxes from this snapshot (and for its own `setup`, `verify` and `start`),
4952
+ * set over the image's own `ENV` as `docker run -e` sets them. A value may build on a
4953
+ * variable the image sets with `${containerEnv:NAME}`, or `${containerEnv:NAME:default}`
4954
+ * when the image may not set it: `"PATH": "${containerEnv:PATH}:/opt/tools/bin"`.
4955
+ */
4956
+ env?: Record<string, string>;
4869
4957
  /**
4870
4958
  * `github:` repositories: the snapshot the build starts from, by id or alias. Default
4871
4959
  * `promigence/base`.
@@ -4997,7 +5085,7 @@ export interface SnapshotDeduped {
4997
5085
  */
4998
5086
  source: unknown;
4999
5087
  /**
5000
- * `verified`.
5088
+ * `verified`, or `built` when the snapshot was stored with no verification command to run.
5001
5089
  */
5002
5090
  state: string;
5003
5091
  /**
@@ -1,3 +1,16 @@
1
+ /**
2
+ * `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` / `NO_PROXY` for the SDK and the CLI.
3
+ *
4
+ * Node's built-in `fetch` ignores the proxy variables (Node 22 has no `NODE_USE_ENV_PROXY`), so
5
+ * behind a corporate proxy the CLI could not reach the API while the Python SDK, which honours
6
+ * them, could. This is a small `fetch` over `node:http`/`node:https` that goes through the proxy
7
+ * the environment names: an `http://` URL is sent to the proxy in absolute form, an `https://` URL
8
+ * through a `CONNECT` tunnel with TLS to the API inside it (the proxy never sees the key). It is
9
+ * used only when a proxy variable applies to the API's host; otherwise nothing changes.
10
+ *
11
+ * No dependency: the proxy agents live in the `undici` package, which this SDK does not ship.
12
+ */
13
+ import { type ClientRequest, type RequestOptions } from "node:http";
1
14
  type FetchLike = typeof globalThis.fetch;
2
15
  /** Does `NO_PROXY` exempt `host` (`*`, an exact name, a `.suffix` or `suffix`, with an optional port)? */
3
16
  export declare function noProxy(host: string, port: string, env?: NodeJS.ProcessEnv): boolean;
@@ -8,4 +21,11 @@ export declare function proxyFor(url: string, env?: NodeJS.ProcessEnv): URL | un
8
21
  * sends JSON and file chunks of at most 16 MiB); response bodies stream.
9
22
  */
10
23
  export declare function proxyFetch(proxy: URL): FetchLike;
24
+ /**
25
+ * `node:http(s).request(url, options)`, through the proxy the environment names for `url` when one
26
+ * applies ([`proxyFor`]): an https URL over a `CONNECT` tunnel with TLS to the server inside it
27
+ * (the proxy never sees what is sent), an http URL in absolute form. For what `fetch` cannot
28
+ * carry: the terminal's and the SSH tunnel's WebSocket upgrades. With no proxy, the plain request.
29
+ */
30
+ export declare function requestVia(url: URL, options: RequestOptions, env?: NodeJS.ProcessEnv): ClientRequest;
11
31
  export {};
package/dist/cjs/proxy.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noProxy = noProxy;
4
4
  exports.proxyFor = proxyFor;
5
5
  exports.proxyFetch = proxyFetch;
6
+ exports.requestVia = requestVia;
6
7
  const node_http_1 = require("node:http");
7
8
  const node_https_1 = require("node:https");
8
9
  const node_tls_1 = require("node:tls");
@@ -174,3 +175,38 @@ function proxyFetch(proxy) {
174
175
  };
175
176
  return fetchViaProxy;
176
177
  }
178
+ function requestVia(url, options, env = process.env) {
179
+ const proxy = proxyFor(url.href, env);
180
+ if (!proxy)
181
+ return (url.protocol === "https:" ? node_https_1.request : node_http_1.request)(url, options);
182
+ const proxyRequest = proxy.protocol === "https:" ? node_https_1.request : node_http_1.request;
183
+ const proxyPort = Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80));
184
+ if (url.protocol === "http:") {
185
+ return proxyRequest({
186
+ ...options, host: proxy.hostname, port: proxyPort, path: url.href,
187
+ headers: { ...options.headers, host: url.host, ...proxyAuth(proxy) },
188
+ });
189
+ }
190
+ const port = Number(url.port || 443);
191
+ const createConnection = (_opts, done) => {
192
+ const connect = proxyRequest({
193
+ host: proxy.hostname, port: proxyPort, method: "CONNECT", path: `${url.hostname}:${port}`,
194
+ headers: { host: `${url.hostname}:${port}`, ...proxyAuth(proxy) },
195
+ });
196
+ connect.on("error", (e) => done(e));
197
+ connect.on("connect", (res, socket) => {
198
+ if (res.statusCode !== 200) {
199
+ socket.destroy();
200
+ done(new Error(`the proxy ${proxy.host} refused CONNECT ${url.host} (HTTP ${res.statusCode})`));
201
+ return;
202
+ }
203
+ done(null, (0, node_tls_1.connect)({ socket, servername: url.hostname, ALPNProtocols: ["http/1.1"] }));
204
+ });
205
+ connect.end();
206
+ return undefined;
207
+ };
208
+ return (0, node_https_1.request)(url, {
209
+ ...options,
210
+ createConnection: createConnection,
211
+ });
212
+ }
package/dist/cjs/pty.js CHANGED
@@ -5,9 +5,8 @@ exports.wsAccept = wsAccept;
5
5
  exports.encodeFrame = encodeFrame;
6
6
  exports.connectPty = connectPty;
7
7
  const node_crypto_1 = require("node:crypto");
8
- const node_http_1 = require("node:http");
9
- const node_https_1 = require("node:https");
10
8
  const caller_js_1 = require("./caller.js");
9
+ const proxy_js_1 = require("./proxy.js");
11
10
  exports.PTY_INPUT_CHUNK = 64 * 1024;
12
11
  const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
13
12
  exports.PTY_COMMAND_HEADER = "promigence-pty-command";
@@ -151,7 +150,7 @@ exports.PtySession = PtySession;
151
150
  function connectPty(opts) {
152
151
  const url = new URL(opts.url);
153
152
  const key = (0, node_crypto_1.randomBytes)(16).toString("base64");
154
- const req = (url.protocol === "https:" ? node_https_1.request : node_http_1.request)(url, {
153
+ const req = (0, proxy_js_1.requestVia)(url, {
155
154
  method: "GET",
156
155
  headers: {
157
156
  connection: "Upgrade",
@@ -297,8 +297,14 @@ export declare class Sandbox {
297
297
  read(path: string, opts: {
298
298
  format: "bytes";
299
299
  }): Promise<Uint8Array>;
300
- /** Text is written as UTF-8; bytes as they are (a typed array, an `ArrayBuffer` or a `Blob`). */
301
- write(path: string, data: string | Uint8Array | ArrayBuffer | ArrayBufferView | Blob): Promise<void>;
300
+ /**
301
+ * Text is written as UTF-8; bytes as they are (a typed array, an `ArrayBuffer` or a `Blob`).
302
+ * A file that already exists keeps its mode, as a shell `>` or an editor's save does; a new
303
+ * one is `0o644`. `{ mode: 0o755 }` sets the mode either way.
304
+ */
305
+ write(path: string, data: string | Uint8Array | ArrayBuffer | ArrayBufferView | Blob, opts?: {
306
+ mode?: number;
307
+ }): Promise<void>;
302
308
  remove(path: string, opts?: {
303
309
  recursive?: boolean;
304
310
  }): Promise<void>;
@@ -69,7 +69,7 @@ function toEntryInfoFromPath(f) {
69
69
  return toEntryInfo({ ...f, name: at < 0 ? f.path : f.path.slice(at + 1) });
70
70
  }
71
71
  function buildIsFree(b) {
72
- return b.deduped === true || b.joined === true || b.idempotent_replay === true || b.state === "verified";
72
+ return b.deduped === true || b.joined === true || b.idempotent_replay === true || b.state === "verified" || b.state === "built";
73
73
  }
74
74
  function buildNotice(image) {
75
75
  return (b) => {
@@ -360,7 +360,7 @@ class Sandbox {
360
360
  const bytes = await this.#client.sandboxes.files.get(this.sandboxId, path);
361
361
  return format === "bytes" ? bytes : new TextDecoder().decode(bytes);
362
362
  }),
363
- write: async (path, data) => {
363
+ write: async (path, data, opts = {}) => {
364
364
  let body;
365
365
  if (typeof data === "string" || data instanceof Uint8Array)
366
366
  body = data;
@@ -374,7 +374,7 @@ class Sandbox {
374
374
  throw new core_js_1.InvalidArgumentError(`files.write data must be a string, a typed array, an ArrayBuffer or a Blob, got ${data === null ? "null" : typeof data}`, "Pass text, or the bytes as a Uint8Array.");
375
375
  }
376
376
  this.dirty = true;
377
- await this.#client.sandboxes.files.put(this.sandboxId, path, body, {});
377
+ await this.#client.sandboxes.files.put(this.sandboxId, path, body, opts.mode === undefined ? {} : { mode: opts.mode });
378
378
  },
379
379
  remove: async (path, opts = {}) => {
380
380
  this.dirty = true;
package/dist/cjs/ssh.js CHANGED
@@ -1,15 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.connectSshTunnel = connectSshTunnel;
4
- const node_http_1 = require("node:http");
5
- const node_https_1 = require("node:https");
6
4
  const node_crypto_1 = require("node:crypto");
7
5
  const caller_js_1 = require("./caller.js");
6
+ const proxy_js_1 = require("./proxy.js");
8
7
  const pty_js_1 = require("./pty.js");
9
8
  function connectSshTunnel(opts) {
10
9
  const url = new URL(opts.url);
11
10
  const key = (0, node_crypto_1.randomBytes)(16).toString("base64");
12
- const req = (url.protocol === "https:" ? node_https_1.request : node_http_1.request)(url, {
11
+ const req = (0, proxy_js_1.requestVia)(url, {
13
12
  method: "GET",
14
13
  headers: {
15
14
  connection: "Upgrade",
package/dist/client.d.ts CHANGED
@@ -5,7 +5,7 @@ import { type Bundle } from "./bundle.js";
5
5
  import { type PtyExit, type PtySession } from "./pty.js";
6
6
  import { type SshTunnel } from "./ssh.js";
7
7
  import type * as M from "./models.gen.js";
8
- import { type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
8
+ import { type ConnectRequest, type ConnectTicket, type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
9
9
  import { type GitlabConnect, type GitlabDisconnected, type GitlabRepos, type GitlabStatus, type GitlabUntracked } from "./gitlab.js";
10
10
  export type FetchLike = typeof globalThis.fetch;
11
11
  /**
@@ -343,7 +343,8 @@ export interface RunReport {
343
343
  [extra: string]: unknown;
344
344
  }
345
345
  /** A build of a snapshot (`POST /snapshots`, `GET /snapshots/builds/:id`). */
346
- export type BuildState = "queued" | "running" | "verified" | "rejected" | "failed";
346
+ /** `built`: finished and stored with no verification command to run; done, like `verified`. */
347
+ export type BuildState = "queued" | "running" | "verified" | "built" | "rejected" | "failed";
347
348
  export interface SecretRecord {
348
349
  name: string;
349
350
  /** Length of the stored value, so a caller can sanity-check without reading it. */
@@ -353,9 +354,9 @@ export interface SecretRecord {
353
354
  updated_at?: string;
354
355
  /**
355
356
  * The hosts the secret is bound to. Absent when it is not bound: a sandbox that names it is
356
- * then given the value. When present, the sandbox never sees the value: requests it sends to the
357
- * host's address in `urls` are authenticated with the secret; a request made straight to
358
- * `https://<host>/` is not.
357
+ * then given the value. When present, requests the sandbox sends to the host's address in
358
+ * `urls` are authenticated with the secret, a request made straight to `https://<host>/` is not,
359
+ * and the value stays out of the sandbox unless a run asks for it (`raw`).
359
360
  */
360
361
  hosts?: string[];
361
362
  /**
@@ -364,7 +365,7 @@ export interface SecretRecord {
364
365
  */
365
366
  urls?: string[];
366
367
  /**
367
- * The variables a sandbox that names this secret is also given, each pointed at its host's
368
+ * The variables a sandbox that names this secret is also given, each set to its host's
368
369
  * address in `urls`: `OPENAI_BASE_URL` for `api.openai.com`, `ANTHROPIC_BASE_URL` for
369
370
  * `api.anthropic.com`. Absent when there is none. A variable the caller sets itself wins.
370
371
  */
@@ -379,10 +380,10 @@ export interface SecretRecord {
379
380
  scan_responses?: boolean;
380
381
  }
381
382
  /**
382
- * `secrets.set` options: bind the secret to hosts. A sandbox that names a bound secret never sees
383
- * its value: requests it sends to the host's address the answer gives in `urls` (and to
384
- * `$OPENAI_BASE_URL` / `$ANTHROPIC_BASE_URL` for those providers) are authenticated with it, and
385
- * one sent straight to the host is not.
383
+ * `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
384
+ * the answer gives in `urls` (and to `$OPENAI_BASE_URL` / `$ANTHROPIC_BASE_URL` for those
385
+ * providers) are authenticated with it, one sent straight to the host is not, and the value stays
386
+ * out of the sandbox unless a run asks for it (`raw: "refused"` forbids that; the default allows it).
386
387
  *
387
388
  * Every option left out keeps what the secret already has, so setting a bound secret again with
388
389
  * a new value (a rotation) keeps its binding. `hosts: []` removes it.
@@ -1478,8 +1479,8 @@ export interface SandboxNetworkCut {
1478
1479
  * secrets: ["OPENAI_API_KEY", { name: "work_secret_7", as: "ANTHROPIC_WORK_SECRET" }]
1479
1480
  * ```
1480
1481
  *
1481
- * A secret bound to hosts (`secrets.set(name, value, { hosts })`) arrives as its placeholder;
1482
- * `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
1482
+ * A secret bound to hosts (`secrets.set(name, value, { hosts })`) does not put its value in the
1483
+ * sandbox; `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
1483
1484
  */
1484
1485
  export type SecretBinding = string | {
1485
1486
  name: string;
@@ -1649,6 +1650,14 @@ export interface SnapshotCreateOpts {
1649
1650
  verify?: VerifySpec;
1650
1651
  /** Image forms: a background process started before the snapshot, so forks inherit it running. */
1651
1652
  start?: string | string[];
1653
+ /**
1654
+ * Image and Dockerfile forms: environment variables for every command, terminal and SSH
1655
+ * session in this snapshot's sandboxes (and its own setup, verify and start), set over the
1656
+ * image's `ENV` as `docker run -e` sets them. A value may build on what the image sets with
1657
+ * `${containerEnv:NAME}` (or `${containerEnv:NAME:default}`):
1658
+ * `{ PATH: "${containerEnv:PATH}:/opt/tools/bin" }`.
1659
+ */
1660
+ env?: Record<string, string>;
1652
1661
  tier?: Tier;
1653
1662
  /** Image forms: the name of a stored secret holding the registry credentials. */
1654
1663
  registrySecret?: string;
@@ -1772,28 +1781,6 @@ export declare function imageRefNamesVersion(image: string): boolean;
1772
1781
  export declare function checkImageVersioned(image: string): void;
1773
1782
  /** A git context, pinned: `https://github.com/org/app@<40 hex>`. A branch is not a pin. */
1774
1783
  export declare const CONTEXT_GIT_RE: RegExp;
1775
- /** The prefix a packed directory travels under. */
1776
- export declare const CONTEXT_TAR_PREFIX = "tar;base64,";
1777
- /**
1778
- * Directories never worth sending. `.dockerignore` is **not** applied to a context that fits: it
1779
- * is sent with the context and the build applies it, so honouring it twice could only disagree
1780
- * with the build. The cost is that editing an ignored file still changes the snapshot's
1781
- * identity, which is the safe direction to be wrong in. Only a context over the limit — one that
1782
- * would be refused anyway — is packed again without what its `.dockerignore` excludes.
1783
- */
1784
- export declare const CONTEXT_SKIP: Set<string>;
1785
- /**
1786
- * A `.dockerignore`, as the build reads it: one pattern a line, `#` comments, `!` re-includes,
1787
- * Go `filepath.Match` syntax plus `**`, anchored at the context root, and a pattern that matches a
1788
- * directory matches everything under it. The last pattern that matches a path decides. Returns
1789
- * `undefined` for a file with no patterns.
1790
- */
1791
- export declare function dockerignoreMatcher(text: string): {
1792
- ignored: (rel: string) => boolean;
1793
- exceptions: boolean;
1794
- } | undefined;
1795
- /** The server's limit, refused here so a 9 MB context does not cross the wire to be told no. */
1796
- export declare const CONTEXT_MAX_BYTES: number;
1797
1784
  /**
1798
1785
  * What in a repository URL can carry a credential: a user name, password or token (`https://TOKEN@…`,
1799
1786
  * `https://user:pass@…`, `:token@`, a percent-encoded `@`, any `@` in a scheme URL, the scp form's
@@ -1813,19 +1800,6 @@ export declare const REPO_CREDENTIALS_FIX: string;
1813
1800
  export declare function checkRepoUrl(url: unknown, field: string): void;
1814
1801
  /** Refuse a context that is neither a pinned git URL nor a packed directory. */
1815
1802
  export declare function checkDockerfileContext(context: unknown): asserts context is string;
1816
- /**
1817
- * Pack a directory into the string `dockerfile.context` takes.
1818
- *
1819
- * The archive is only transport: the snapshot's identity is computed from the *entries* server
1820
- * side, so this does not have to agree byte-for-byte with `sdk-py`'s packer — and two calls on
1821
- * an unchanged directory give the same snapshot either way.
1822
- */
1823
- export declare function packContext(dir: string, opts?: {
1824
- maxBytes?: number;
1825
- keep?: readonly string[];
1826
- }): string;
1827
- /** The Dockerfile's path as the context sees it, for `dockerfile.path`. */
1828
- export declare function contextRelative(contextDir: string, dockerfilePath: string): string;
1829
1803
  /**
1830
1804
  * The `POST /snapshots` body for one of its three forms; throws `InvalidArgumentError` before
1831
1805
  * anything is sent. Kept field-for-field with `sdk-py` (`_ops.snapshot_create_body`) so the two
@@ -2115,6 +2089,14 @@ export declare class PromigenceClient {
2115
2089
  status: () => Promise<GithubStatus>;
2116
2090
  /** `POST /integrations/github/connect` — the install link (15 minutes); open it in a browser. */
2117
2091
  connect: () => Promise<GithubConnect>;
2092
+ /**
2093
+ * `POST /integrations/github/requests` — a connection started for you in the console: open
2094
+ * `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
2095
+ * it here: use `connect()`).
2096
+ */
2097
+ ticket: () => Promise<ConnectTicket>;
2098
+ /** `GET /integrations/github/requests/:id` — one of your connection requests: `pending`, or how it ended. */
2099
+ request: (requestId: string) => Promise<ConnectRequest>;
2118
2100
  /** `DELETE /integrations/github/installations/:id` — nothing is built from that account any more. */
2119
2101
  disconnect: (installationId: number | string) => Promise<GithubDisconnected>;
2120
2102
  /** `GET /integrations/github/repos` — one page (at most 100) across every connection. */
@@ -2135,6 +2117,14 @@ export declare class PromigenceClient {
2135
2117
  status: () => Promise<GitlabStatus>;
2136
2118
  /** `POST /integrations/gitlab/connect` — the authorize link (15 minutes); open it in a browser. */
2137
2119
  connect: () => Promise<GitlabConnect>;
2120
+ /**
2121
+ * `POST /integrations/gitlab/requests` — a connection started for you in the console: open
2122
+ * `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
2123
+ * it here: use `connect()`).
2124
+ */
2125
+ ticket: () => Promise<ConnectTicket>;
2126
+ /** `GET /integrations/gitlab/requests/:id` — one of your connection requests: `pending`, or how it ended. */
2127
+ request: (requestId: string) => Promise<ConnectRequest>;
2138
2128
  /** `DELETE /integrations/gitlab/connections/:id` — revoked on GitLab; nothing is built through it any more. */
2139
2129
  disconnect: (connectionId: string) => Promise<GitlabDisconnected>;
2140
2130
  /** `GET /integrations/gitlab/repos` — one page (at most 100) of one connection's projects. */
@@ -2636,6 +2626,9 @@ export declare class PromigenceClient {
2636
2626
  * A failure mid-loop leaves a **prefix** in the sandbox, not a corrupt file, and the
2637
2627
  * thrown error carries how far it got (`written` in the error's evidence).
2638
2628
  * Re-running the same `put` is safe: chunk 0 truncates.
2629
+ *
2630
+ * `mode` (`0o755`) sets the file's permission bits. Without it a file that already exists
2631
+ * keeps its mode, as a shell `>` does, and a new one is `0o644`.
2639
2632
  */
2640
2633
  put: (sandboxId: string, path: string, data: string | Uint8Array, opts?: {
2641
2634
  mode?: number;