@promigence/sdk 0.1.0-beta.2 → 0.1.0-beta.4

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.
@@ -83,6 +83,8 @@ export interface Capacity {
83
83
  queued_message?: string;
84
84
  }
85
85
  export type Outcome = M.Outcome;
86
+ /** `GET /me/outcomes`: what the organization got this month, measured. */
87
+ export type Outcomes = M.Outcomes;
86
88
  export interface Percentiles {
87
89
  p50: number;
88
90
  p95: number;
@@ -631,6 +633,8 @@ export interface HealthResponse {
631
633
  build_sha?: string;
632
634
  /** Whether the service can start new sandboxes right now. */
633
635
  serving?: boolean;
636
+ /** Where sandboxes run, in plain words (for example "US East Coast"); absent when not said. */
637
+ location?: string;
634
638
  [extra: string]: unknown;
635
639
  }
636
640
  /** One service's row in `GET /status` (`api`, `sandboxes`): `up`, `degraded` or `down`. */
@@ -1213,6 +1217,10 @@ export interface BackgroundExecRecord {
1213
1217
  stdout_total?: number;
1214
1218
  stderr_total?: number;
1215
1219
  truncated?: boolean;
1220
+ /** Started with `follow`: the newest output is kept, and the handle goes 10 minutes after it ends. */
1221
+ follow?: boolean;
1222
+ /** A range read of a `follow` command: bytes dropped between the asked offset and the first held. */
1223
+ skipped?: number;
1216
1224
  exit?: {
1217
1225
  exit_code: number | null;
1218
1226
  signal: number | null;
@@ -1421,6 +1429,11 @@ export interface PromigenceClientOpts {
1421
1429
  * on `client.notice`.
1422
1430
  */
1423
1431
  onNotice?: (notice: AccountNotice) => void;
1432
+ /**
1433
+ * Called with every answer's timing ([`Timing`]): the round trip this process saw and how long
1434
+ * the answer took on Promigence's side. A CLI uses it to say how far away the caller is.
1435
+ */
1436
+ onTiming?: (timing: Timing) => void;
1424
1437
  }
1425
1438
  /** `GET /sandboxes/:id/network`. */
1426
1439
  export interface SandboxNetwork {
@@ -1857,6 +1870,17 @@ interface RequestOpts {
1857
1870
  /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1858
1871
  headers?: Record<string, string>;
1859
1872
  }
1873
+ /** One answer's timing ([`PromigenceClient.lastTiming`]). */
1874
+ export interface Timing {
1875
+ method: string;
1876
+ path: string;
1877
+ /** The round trip as the client saw it, in ms, until the answer's headers arrived. */
1878
+ totalMs: number;
1879
+ /** How long the answer took on Promigence's side, in ms; `null` when the service did not say. */
1880
+ ourMs: number | null;
1881
+ }
1882
+ /** The `app` span of a `Server-Timing` header (`app;dur=12.35`), in ms; `null` without one. */
1883
+ export declare function serverAppMs(header: string | null | undefined): number | null;
1860
1884
  export declare class PromigenceClient {
1861
1885
  #private;
1862
1886
  readonly apiUrl: string;
@@ -1879,9 +1903,16 @@ export declare class PromigenceClient {
1879
1903
  requests: number;
1880
1904
  retries: number;
1881
1905
  };
1906
+ /**
1907
+ * The most recent answer's timing: `totalMs` is the whole round trip as this process saw it,
1908
+ * until the answer's headers arrived; `ourMs` is how long the answer took on Promigence's side
1909
+ * (`null` when the service did not say). `totalMs − ourMs` is the network between you and us.
1910
+ */
1911
+ lastTiming: Timing | null;
1882
1912
  /** The latest account notice any response carried (`Promigence-Notice`), if one did. */
1883
1913
  notice: AccountNotice | undefined;
1884
1914
  private readonly onNotice;
1915
+ private readonly onTiming;
1885
1916
  private readonly noticesSeen;
1886
1917
  constructor(opts?: PromigenceClientOpts);
1887
1918
  /** Read `Promigence-Notice` off a response (its raw value); tell the caller once per distinct notice. */
@@ -1975,6 +2006,13 @@ export declare class PromigenceClient {
1975
2006
  me(opts?: {
1976
2007
  signal?: AbortSignal;
1977
2008
  }): Promise<MeResponse>;
2009
+ /**
2010
+ * `GET /me/outcomes?period=month` — what this organization (with a project's key: that project) got
2011
+ * this month, measured from its own runs and sandboxes. A figure with nothing behind it is `null`.
2012
+ */
2013
+ outcomes(opts?: {
2014
+ signal?: AbortSignal;
2015
+ }): Promise<Outcomes>;
1978
2016
  readonly billing: {
1979
2017
  /** `GET /billing` — plan, card, period, usage, trial, upcoming invoice. */
1980
2018
  usage: () => Promise<BillingUsage>;
@@ -2245,6 +2283,7 @@ export declare class PromigenceClient {
2245
2283
  */
2246
2284
  execBackground: (sandboxId: string, opts: Omit<ExecOpts, "terminal" | "killOnExit" | "signal"> & {
2247
2285
  stdin?: boolean;
2286
+ follow?: boolean;
2248
2287
  }) => Promise<BackgroundExecRecord>;
2249
2288
  /** Background execs of one sandbox (`GET|POST|DELETE /sandboxes/:id/execs/:exec_id…`). */
2250
2289
  execs: {
@@ -2262,10 +2301,14 @@ export declare class PromigenceClient {
2262
2301
  read: (sandboxId: string, execId: string, stream: "stdout" | "stderr", opts?: {
2263
2302
  offset?: number;
2264
2303
  len?: number;
2304
+ waitMs?: number;
2305
+ untilOutput?: boolean;
2306
+ signal?: AbortSignal;
2265
2307
  }) => Promise<{
2266
2308
  data: Uint8Array;
2267
2309
  offset: number;
2268
2310
  eof: boolean;
2311
+ skipped: number;
2269
2312
  record: BackgroundExecRecord;
2270
2313
  }>;
2271
2314
  /**
@@ -31,6 +31,7 @@ exports.isKeyedCreate = isKeyedCreate;
31
31
  exports.checkSandboxId = checkSandboxId;
32
32
  exports.checkSpendCap = checkSpendCap;
33
33
  exports.newIdempotencyKey = newIdempotencyKey;
34
+ exports.serverAppMs = serverAppMs;
34
35
  exports.toCommandResult = toCommandResult;
35
36
  const node_fs_1 = require("node:fs");
36
37
  const node_os_1 = require("node:os");
@@ -939,6 +940,16 @@ function waitQuery(waitMs) {
939
940
  : "";
940
941
  }
941
942
  const notReleased = (what) => Promise.reject(new core_js_1.NotReleasedError(what));
943
+ function serverAppMs(header) {
944
+ if (!header)
945
+ return null;
946
+ for (const part of header.split(",")) {
947
+ const m = /^\s*app\s*;(?:.*;)?\s*dur=([0-9]+(?:\.[0-9]+)?)/.exec(part);
948
+ if (m)
949
+ return Number(m[1]);
950
+ }
951
+ return null;
952
+ }
942
953
  function headerOf(res, name) {
943
954
  return res instanceof http2_js_1.H2Response ? res.header(name) : res.headers.get(name);
944
955
  }
@@ -955,8 +966,10 @@ class PromigenceClient {
955
966
  cliErrors;
956
967
  caller;
957
968
  stats = { requests: 0, retries: 0 };
969
+ lastTiming = null;
958
970
  notice;
959
971
  onNotice;
972
+ onTiming;
960
973
  noticesSeen = new Set();
961
974
  constructor(opts = {}) {
962
975
  (0, trace_js_1.traceEvent)("client:ctor");
@@ -975,6 +988,7 @@ class PromigenceClient {
975
988
  this.caller = (0, caller_js_1.resolveCaller)(opts.caller);
976
989
  this.userAgent = (0, caller_js_1.userAgentWithCaller)(opts.userAgent ?? `promigence/${core_js_1.VERSION} node/${process.versions.node}`, this.caller);
977
990
  this.onNotice = opts.onNotice;
991
+ this.onTiming = opts.onTiming;
978
992
  this.cliErrors = opts.cliErrors === true;
979
993
  (0, trace_js_1.traceEvent)("client:ready");
980
994
  }
@@ -1080,6 +1094,7 @@ class PromigenceClient {
1080
1094
  for (let attempt = 0;; attempt++) {
1081
1095
  let res;
1082
1096
  this.stats.requests++;
1097
+ const sentAt = performance.now();
1083
1098
  try {
1084
1099
  if (this.h2) {
1085
1100
  const init = {
@@ -1109,6 +1124,13 @@ class PromigenceClient {
1109
1124
  res = await this.fetchImpl(url, init);
1110
1125
  }
1111
1126
  (0, trace_js_1.mark)(tr, "res");
1127
+ this.lastTiming = {
1128
+ method: opts.method,
1129
+ path: opts.path,
1130
+ totalMs: performance.now() - sentAt,
1131
+ ourMs: serverAppMs(headerOf(res, "server-timing")),
1132
+ };
1133
+ this.onTiming?.(this.lastTiming);
1112
1134
  }
1113
1135
  catch (err) {
1114
1136
  const e = err;
@@ -1426,6 +1448,9 @@ class PromigenceClient {
1426
1448
  me(opts = {}) {
1427
1449
  return this.request({ method: "GET", path: "/me", ...(opts.signal ? { signal: opts.signal } : {}) });
1428
1450
  }
1451
+ outcomes(opts = {}) {
1452
+ return this.request({ method: "GET", path: "/me/outcomes?period=month", ...(opts.signal ? { signal: opts.signal } : {}) });
1453
+ }
1429
1454
  billing = {
1430
1455
  usage: () => this.request({ method: "GET", path: "/billing" }),
1431
1456
  ledger: async (limit = 100) => (await this.billing.ledgerPage({ limit })).items,
@@ -2001,6 +2026,8 @@ class PromigenceClient {
2001
2026
  body["secrets"] = opts.secrets;
2002
2027
  if (opts.stdin)
2003
2028
  body["stdin"] = true;
2029
+ if (opts.follow)
2030
+ body["follow"] = true;
2004
2031
  const timeoutMs = commandTimeoutMs(opts.timeoutMs);
2005
2032
  if (timeoutMs !== undefined)
2006
2033
  body["timeout_ms"] = timeoutMs;
@@ -2033,11 +2060,18 @@ class PromigenceClient {
2033
2060
  const q = [`stream=${stream}`, `offset=${Math.max(0, Math.floor(opts.offset ?? 0))}`];
2034
2061
  if (opts.len !== undefined)
2035
2062
  q.push(`len=${Math.max(0, Math.floor(opts.len))}`);
2063
+ const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
2064
+ if (wait)
2065
+ q.push(`wait_ms=${wait}`);
2066
+ if (wait && opts.untilOutput)
2067
+ q.push("until=output");
2036
2068
  const r = await this.request({
2037
2069
  method: "GET",
2038
2070
  path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}?${q.join("&")}`,
2071
+ ...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
2072
+ ...(opts.signal ? { signal: opts.signal } : {}),
2039
2073
  });
2040
- return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), record: r };
2074
+ return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), skipped: r.skipped ?? 0, record: r };
2041
2075
  },
2042
2076
  stdin: (sandboxId, execId, opts = {}) => {
2043
2077
  if (opts.data === undefined && !opts.eof) {
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.2";
2
+ export declare const VERSION = "0.1.0-beta.4";
3
3
  export declare const DOCS_URL = "https://promigence.ai";
4
4
  /** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
5
5
  export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
package/dist/cjs/core.js CHANGED
@@ -7,7 +7,7 @@ exports.fixBeyondMessage = fixBeyondMessage;
7
7
  exports.previewsOff = previewsOff;
8
8
  exports.exitForCode = exitForCode;
9
9
  const hint_js_1 = require("./hint.js");
10
- exports.VERSION = "0.1.0-beta.2";
10
+ exports.VERSION = "0.1.0-beta.4";
11
11
  exports.DOCS_URL = "https://promigence.ai";
12
12
  exports.ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
13
13
  exports.DEFAULT_API_URL = "https://api.promigence.ai/v1";
@@ -146,6 +146,11 @@ export interface BackgroundExec {
146
146
  * How the command ended; only once `state` is `exited`.
147
147
  */
148
148
  exit?: BackgroundExecExit | null;
149
+ /**
150
+ * Started with `follow`: the newest output is kept, and the handle is freed 10 minutes
151
+ * after the command ends.
152
+ */
153
+ follow?: boolean | null;
149
154
  /**
150
155
  * Present when the sandbox pauses itself when idle: after this command ends, the idle rule
151
156
  * waits up to this many seconds for its result to be read before it pauses the sandbox (a
@@ -166,6 +171,11 @@ export interface BackgroundExec {
166
171
  len?: number | null;
167
172
  offset?: number | null;
168
173
  sandbox_id: string;
174
+ /**
175
+ * A `follow` command only: bytes between the `offset` asked for and the first byte still
176
+ * held, dropped to keep the newest output; `data` then starts at `offset + skipped`.
177
+ */
178
+ skipped?: number | null;
169
179
  /**
170
180
  * Unix nanoseconds.
171
181
  */
@@ -1018,6 +1028,14 @@ export interface ExecCreate {
1018
1028
  * Extra environment variables.
1019
1029
  */
1020
1030
  env?: Record<string, string>;
1031
+ /**
1032
+ * With `background`: the command is read as it runs, by a caller that may lose its
1033
+ * connection and come back. The newest `max_output_bytes` of each stream are kept (8 MiB by
1034
+ * default; older bytes are dropped, and a read that asks for them says how many in
1035
+ * `skipped`), and the handle is freed 10 minutes after the command ends, read or not. The
1036
+ * command never stops because a caller went away.
1037
+ */
1038
+ follow?: boolean;
1021
1039
  /**
1022
1040
  * End the sandbox when the command exits (default: the value of `terminal`).
1023
1041
  */
@@ -1904,6 +1922,11 @@ export interface Health {
1904
1922
  * The commit this build came from; empty for a local build.
1905
1923
  */
1906
1924
  build_sha: string;
1925
+ /**
1926
+ * Where sandboxes run, in plain words (for example "US East Coast"). Absent when the service
1927
+ * does not say.
1928
+ */
1929
+ location?: string | null;
1907
1930
  ok: boolean;
1908
1931
  /**
1909
1932
  * Whether new sandboxes can start right now. Answered to a request made with a key; absent
@@ -2325,6 +2348,16 @@ export interface Me {
2325
2348
  */
2326
2349
  key?: KeyRef | null;
2327
2350
  limits: MeLimits;
2351
+ /**
2352
+ * Where sandboxes run, in plain words (for example "US East Coast"), as `GET /v1/health` says
2353
+ * it. Absent when the service does not say.
2354
+ */
2355
+ location?: string | null;
2356
+ /**
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.
2359
+ */
2360
+ location_code?: string | null;
2328
2361
  /**
2329
2362
  * A notice about a coming change to the trial rules; `null` when there is none.
2330
2363
  */
@@ -2627,6 +2660,67 @@ export interface OtherUsage {
2627
2660
  entries: number;
2628
2661
  }
2629
2662
  export type Outcome = "completed" | "task_failed" | "env_failed" | "snapshot_failed" | "aborted_by_client" | "expired" | "cap_killed" | "not_started" | "superseded" | "grader_failed" | "preempted";
2663
+ /**
2664
+ * `GET /v1/me/outcomes?period=month` — what the organization (with a project's key: that project)
2665
+ * got this period, measured from its own runs and sandboxes. A figure with nothing behind it is
2666
+ * `null`; counts are `0`.
2667
+ */
2668
+ export interface Outcomes {
2669
+ /**
2670
+ * Finished runs of two or more sandboxes this period.
2671
+ */
2672
+ batch_runs: number;
2673
+ /**
2674
+ * For those runs: seconds not spent waiting, the sum of their sandboxes' own times minus
2675
+ * the runs' wall time; `null` without such a run.
2676
+ */
2677
+ batch_saved_s?: number | null;
2678
+ /**
2679
+ * For those runs: the sum of their sandboxes' own times divided by the runs' wall time
2680
+ * (how many times faster than one after another); `null` without such a run.
2681
+ */
2682
+ batch_speedup?: number | null;
2683
+ /**
2684
+ * Sandboxes that ended on a failure on our side and were not billed for it.
2685
+ */
2686
+ failures_ours: number;
2687
+ /**
2688
+ * What was given back for those failures, in USD (`0` when they ended before any charge).
2689
+ */
2690
+ failures_ours_usd: number;
2691
+ /**
2692
+ * Failed sandboxes kept to reopen that can still be reopened.
2693
+ */
2694
+ kept_failures: number;
2695
+ /**
2696
+ * Hours this period's sandboxes spent paused, which is not billed; `null` if none paused.
2697
+ */
2698
+ paused_hours?: number | null;
2699
+ /**
2700
+ * What those paused hours would have cost running, in USD; `null` if none paused.
2701
+ */
2702
+ paused_usd?: number | null;
2703
+ /**
2704
+ * `month`: the calendar month so far, UTC.
2705
+ */
2706
+ period: string;
2707
+ /**
2708
+ * The median milliseconds until a sandbox was ready to take work; `null` with no sandbox.
2709
+ */
2710
+ ready_ms_p50?: number | null;
2711
+ /**
2712
+ * Runs started this period.
2713
+ */
2714
+ runs: number;
2715
+ /**
2716
+ * Sandboxes started this period.
2717
+ */
2718
+ sandboxes: number;
2719
+ /**
2720
+ * When the period began (RFC 3339, UTC).
2721
+ */
2722
+ since: string;
2723
+ }
2630
2724
  /**
2631
2725
  * What a parked sandbox occupies on disk.
2632
2726
  */
@@ -3956,7 +4050,8 @@ export interface SandboxList {
3956
4050
  next_cursor?: string | null;
3957
4051
  sandboxes: Array<SandboxView>;
3958
4052
  /**
3959
- * The state filter this list was produced with; empty when none was given.
4053
+ * The state filter this list was produced with; empty when none was given. `queued` keeps
4054
+ * every waiting sandbox; `awaiting_capacity` only those waiting for capacity being added.
3960
4055
  */
3961
4056
  state: Array<string>;
3962
4057
  }
@@ -4256,13 +4351,14 @@ export interface SandboxView {
4256
4351
  */
4257
4352
  billable_seconds?: number | null;
4258
4353
  /**
4259
- * Once it has ended, what it cost in USD at its run's rates (the run report's `cost`).
4260
- * Present on `GET /v1/sandboxes/{id}` for an ended sandbox; absent from list rows.
4354
+ * What it has cost in USD at its run's rates: once it has ended, the run report's `cost`; while
4355
+ * it is live, the seconds billed so far (running time only). Present on `GET /v1/sandboxes/{id}`
4356
+ * once it has started; absent from list rows.
4261
4357
  */
4262
4358
  cost_usd?: number | null;
4263
4359
  /**
4264
4360
  * Milliseconds from the start request, after any time queued, to the sandbox being handed
4265
- * over: it does not include `queued_ms`. `total_ms` is the whole wait.
4361
+ * over: it does not include `queued_ms`. Add `queued_ms` for the whole wait.
4266
4362
  */
4267
4363
  e2e_ms?: number | null;
4268
4364
  /**
package/dist/client.d.ts CHANGED
@@ -83,6 +83,8 @@ export interface Capacity {
83
83
  queued_message?: string;
84
84
  }
85
85
  export type Outcome = M.Outcome;
86
+ /** `GET /me/outcomes`: what the organization got this month, measured. */
87
+ export type Outcomes = M.Outcomes;
86
88
  export interface Percentiles {
87
89
  p50: number;
88
90
  p95: number;
@@ -631,6 +633,8 @@ export interface HealthResponse {
631
633
  build_sha?: string;
632
634
  /** Whether the service can start new sandboxes right now. */
633
635
  serving?: boolean;
636
+ /** Where sandboxes run, in plain words (for example "US East Coast"); absent when not said. */
637
+ location?: string;
634
638
  [extra: string]: unknown;
635
639
  }
636
640
  /** One service's row in `GET /status` (`api`, `sandboxes`): `up`, `degraded` or `down`. */
@@ -1213,6 +1217,10 @@ export interface BackgroundExecRecord {
1213
1217
  stdout_total?: number;
1214
1218
  stderr_total?: number;
1215
1219
  truncated?: boolean;
1220
+ /** Started with `follow`: the newest output is kept, and the handle goes 10 minutes after it ends. */
1221
+ follow?: boolean;
1222
+ /** A range read of a `follow` command: bytes dropped between the asked offset and the first held. */
1223
+ skipped?: number;
1216
1224
  exit?: {
1217
1225
  exit_code: number | null;
1218
1226
  signal: number | null;
@@ -1421,6 +1429,11 @@ export interface PromigenceClientOpts {
1421
1429
  * on `client.notice`.
1422
1430
  */
1423
1431
  onNotice?: (notice: AccountNotice) => void;
1432
+ /**
1433
+ * Called with every answer's timing ([`Timing`]): the round trip this process saw and how long
1434
+ * the answer took on Promigence's side. A CLI uses it to say how far away the caller is.
1435
+ */
1436
+ onTiming?: (timing: Timing) => void;
1424
1437
  }
1425
1438
  /** `GET /sandboxes/:id/network`. */
1426
1439
  export interface SandboxNetwork {
@@ -1857,6 +1870,17 @@ interface RequestOpts {
1857
1870
  /** Extra request headers (e.g. `promigence-replaced-by`). Never the authorization. */
1858
1871
  headers?: Record<string, string>;
1859
1872
  }
1873
+ /** One answer's timing ([`PromigenceClient.lastTiming`]). */
1874
+ export interface Timing {
1875
+ method: string;
1876
+ path: string;
1877
+ /** The round trip as the client saw it, in ms, until the answer's headers arrived. */
1878
+ totalMs: number;
1879
+ /** How long the answer took on Promigence's side, in ms; `null` when the service did not say. */
1880
+ ourMs: number | null;
1881
+ }
1882
+ /** The `app` span of a `Server-Timing` header (`app;dur=12.35`), in ms; `null` without one. */
1883
+ export declare function serverAppMs(header: string | null | undefined): number | null;
1860
1884
  export declare class PromigenceClient {
1861
1885
  #private;
1862
1886
  readonly apiUrl: string;
@@ -1879,9 +1903,16 @@ export declare class PromigenceClient {
1879
1903
  requests: number;
1880
1904
  retries: number;
1881
1905
  };
1906
+ /**
1907
+ * The most recent answer's timing: `totalMs` is the whole round trip as this process saw it,
1908
+ * until the answer's headers arrived; `ourMs` is how long the answer took on Promigence's side
1909
+ * (`null` when the service did not say). `totalMs − ourMs` is the network between you and us.
1910
+ */
1911
+ lastTiming: Timing | null;
1882
1912
  /** The latest account notice any response carried (`Promigence-Notice`), if one did. */
1883
1913
  notice: AccountNotice | undefined;
1884
1914
  private readonly onNotice;
1915
+ private readonly onTiming;
1885
1916
  private readonly noticesSeen;
1886
1917
  constructor(opts?: PromigenceClientOpts);
1887
1918
  /** Read `Promigence-Notice` off a response (its raw value); tell the caller once per distinct notice. */
@@ -1975,6 +2006,13 @@ export declare class PromigenceClient {
1975
2006
  me(opts?: {
1976
2007
  signal?: AbortSignal;
1977
2008
  }): Promise<MeResponse>;
2009
+ /**
2010
+ * `GET /me/outcomes?period=month` — what this organization (with a project's key: that project) got
2011
+ * this month, measured from its own runs and sandboxes. A figure with nothing behind it is `null`.
2012
+ */
2013
+ outcomes(opts?: {
2014
+ signal?: AbortSignal;
2015
+ }): Promise<Outcomes>;
1978
2016
  readonly billing: {
1979
2017
  /** `GET /billing` — plan, card, period, usage, trial, upcoming invoice. */
1980
2018
  usage: () => Promise<BillingUsage>;
@@ -2245,6 +2283,7 @@ export declare class PromigenceClient {
2245
2283
  */
2246
2284
  execBackground: (sandboxId: string, opts: Omit<ExecOpts, "terminal" | "killOnExit" | "signal"> & {
2247
2285
  stdin?: boolean;
2286
+ follow?: boolean;
2248
2287
  }) => Promise<BackgroundExecRecord>;
2249
2288
  /** Background execs of one sandbox (`GET|POST|DELETE /sandboxes/:id/execs/:exec_id…`). */
2250
2289
  execs: {
@@ -2262,10 +2301,14 @@ export declare class PromigenceClient {
2262
2301
  read: (sandboxId: string, execId: string, stream: "stdout" | "stderr", opts?: {
2263
2302
  offset?: number;
2264
2303
  len?: number;
2304
+ waitMs?: number;
2305
+ untilOutput?: boolean;
2306
+ signal?: AbortSignal;
2265
2307
  }) => Promise<{
2266
2308
  data: Uint8Array;
2267
2309
  offset: number;
2268
2310
  eof: boolean;
2311
+ skipped: number;
2269
2312
  record: BackgroundExecRecord;
2270
2313
  }>;
2271
2314
  /**
package/dist/client.js CHANGED
@@ -904,6 +904,16 @@ function waitQuery(waitMs) {
904
904
  : "";
905
905
  }
906
906
  const notReleased = (what) => Promise.reject(new NotReleasedError(what));
907
+ export function serverAppMs(header) {
908
+ if (!header)
909
+ return null;
910
+ for (const part of header.split(",")) {
911
+ const m = /^\s*app\s*;(?:.*;)?\s*dur=([0-9]+(?:\.[0-9]+)?)/.exec(part);
912
+ if (m)
913
+ return Number(m[1]);
914
+ }
915
+ return null;
916
+ }
907
917
  function headerOf(res, name) {
908
918
  return res instanceof H2Response ? res.header(name) : res.headers.get(name);
909
919
  }
@@ -920,8 +930,10 @@ export class PromigenceClient {
920
930
  cliErrors;
921
931
  caller;
922
932
  stats = { requests: 0, retries: 0 };
933
+ lastTiming = null;
923
934
  notice;
924
935
  onNotice;
936
+ onTiming;
925
937
  noticesSeen = new Set();
926
938
  constructor(opts = {}) {
927
939
  traceEvent("client:ctor");
@@ -940,6 +952,7 @@ export class PromigenceClient {
940
952
  this.caller = resolveCaller(opts.caller);
941
953
  this.userAgent = userAgentWithCaller(opts.userAgent ?? `promigence/${VERSION} node/${process.versions.node}`, this.caller);
942
954
  this.onNotice = opts.onNotice;
955
+ this.onTiming = opts.onTiming;
943
956
  this.cliErrors = opts.cliErrors === true;
944
957
  traceEvent("client:ready");
945
958
  }
@@ -1045,6 +1058,7 @@ export class PromigenceClient {
1045
1058
  for (let attempt = 0;; attempt++) {
1046
1059
  let res;
1047
1060
  this.stats.requests++;
1061
+ const sentAt = performance.now();
1048
1062
  try {
1049
1063
  if (this.h2) {
1050
1064
  const init = {
@@ -1074,6 +1088,13 @@ export class PromigenceClient {
1074
1088
  res = await this.fetchImpl(url, init);
1075
1089
  }
1076
1090
  mark(tr, "res");
1091
+ this.lastTiming = {
1092
+ method: opts.method,
1093
+ path: opts.path,
1094
+ totalMs: performance.now() - sentAt,
1095
+ ourMs: serverAppMs(headerOf(res, "server-timing")),
1096
+ };
1097
+ this.onTiming?.(this.lastTiming);
1077
1098
  }
1078
1099
  catch (err) {
1079
1100
  const e = err;
@@ -1391,6 +1412,9 @@ export class PromigenceClient {
1391
1412
  me(opts = {}) {
1392
1413
  return this.request({ method: "GET", path: "/me", ...(opts.signal ? { signal: opts.signal } : {}) });
1393
1414
  }
1415
+ outcomes(opts = {}) {
1416
+ return this.request({ method: "GET", path: "/me/outcomes?period=month", ...(opts.signal ? { signal: opts.signal } : {}) });
1417
+ }
1394
1418
  billing = {
1395
1419
  usage: () => this.request({ method: "GET", path: "/billing" }),
1396
1420
  ledger: async (limit = 100) => (await this.billing.ledgerPage({ limit })).items,
@@ -1966,6 +1990,8 @@ export class PromigenceClient {
1966
1990
  body["secrets"] = opts.secrets;
1967
1991
  if (opts.stdin)
1968
1992
  body["stdin"] = true;
1993
+ if (opts.follow)
1994
+ body["follow"] = true;
1969
1995
  const timeoutMs = commandTimeoutMs(opts.timeoutMs);
1970
1996
  if (timeoutMs !== undefined)
1971
1997
  body["timeout_ms"] = timeoutMs;
@@ -1998,11 +2024,18 @@ export class PromigenceClient {
1998
2024
  const q = [`stream=${stream}`, `offset=${Math.max(0, Math.floor(opts.offset ?? 0))}`];
1999
2025
  if (opts.len !== undefined)
2000
2026
  q.push(`len=${Math.max(0, Math.floor(opts.len))}`);
2027
+ const wait = opts.waitMs !== undefined && opts.waitMs > 0 ? Math.min(Math.floor(opts.waitMs), WAIT_MS_CAP) : 0;
2028
+ if (wait)
2029
+ q.push(`wait_ms=${wait}`);
2030
+ if (wait && opts.untilOutput)
2031
+ q.push("until=output");
2001
2032
  const r = await this.request({
2002
2033
  method: "GET",
2003
2034
  path: `/sandboxes/${encodeURIComponent(sandboxId)}/execs/${encodeURIComponent(execId)}?${q.join("&")}`,
2035
+ ...(wait ? { timeoutMs: wait + this.requestTimeoutMs } : {}),
2036
+ ...(opts.signal ? { signal: opts.signal } : {}),
2004
2037
  });
2005
- return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), record: r };
2038
+ return { data: b64ToBytes(r.data ?? ""), offset: r.offset ?? opts.offset ?? 0, eof: Boolean(r.eof), skipped: r.skipped ?? 0, record: r };
2006
2039
  },
2007
2040
  stdin: (sandboxId, execId, opts = {}) => {
2008
2041
  if (opts.data === undefined && !opts.eof) {
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.2";
2
+ export declare const VERSION = "0.1.0-beta.4";
3
3
  export declare const DOCS_URL = "https://promigence.ai";
4
4
  /** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
5
5
  export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { hintArg } from "./hint.js";
2
- export const VERSION = "0.1.0-beta.2";
2
+ export const VERSION = "0.1.0-beta.4";
3
3
  export const DOCS_URL = "https://promigence.ai";
4
4
  export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
5
5
  export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
@@ -146,6 +146,11 @@ export interface BackgroundExec {
146
146
  * How the command ended; only once `state` is `exited`.
147
147
  */
148
148
  exit?: BackgroundExecExit | null;
149
+ /**
150
+ * Started with `follow`: the newest output is kept, and the handle is freed 10 minutes
151
+ * after the command ends.
152
+ */
153
+ follow?: boolean | null;
149
154
  /**
150
155
  * Present when the sandbox pauses itself when idle: after this command ends, the idle rule
151
156
  * waits up to this many seconds for its result to be read before it pauses the sandbox (a
@@ -166,6 +171,11 @@ export interface BackgroundExec {
166
171
  len?: number | null;
167
172
  offset?: number | null;
168
173
  sandbox_id: string;
174
+ /**
175
+ * A `follow` command only: bytes between the `offset` asked for and the first byte still
176
+ * held, dropped to keep the newest output; `data` then starts at `offset + skipped`.
177
+ */
178
+ skipped?: number | null;
169
179
  /**
170
180
  * Unix nanoseconds.
171
181
  */
@@ -1018,6 +1028,14 @@ export interface ExecCreate {
1018
1028
  * Extra environment variables.
1019
1029
  */
1020
1030
  env?: Record<string, string>;
1031
+ /**
1032
+ * With `background`: the command is read as it runs, by a caller that may lose its
1033
+ * connection and come back. The newest `max_output_bytes` of each stream are kept (8 MiB by
1034
+ * default; older bytes are dropped, and a read that asks for them says how many in
1035
+ * `skipped`), and the handle is freed 10 minutes after the command ends, read or not. The
1036
+ * command never stops because a caller went away.
1037
+ */
1038
+ follow?: boolean;
1021
1039
  /**
1022
1040
  * End the sandbox when the command exits (default: the value of `terminal`).
1023
1041
  */
@@ -1904,6 +1922,11 @@ export interface Health {
1904
1922
  * The commit this build came from; empty for a local build.
1905
1923
  */
1906
1924
  build_sha: string;
1925
+ /**
1926
+ * Where sandboxes run, in plain words (for example "US East Coast"). Absent when the service
1927
+ * does not say.
1928
+ */
1929
+ location?: string | null;
1907
1930
  ok: boolean;
1908
1931
  /**
1909
1932
  * Whether new sandboxes can start right now. Answered to a request made with a key; absent
@@ -2325,6 +2348,16 @@ export interface Me {
2325
2348
  */
2326
2349
  key?: KeyRef | null;
2327
2350
  limits: MeLimits;
2351
+ /**
2352
+ * Where sandboxes run, in plain words (for example "US East Coast"), as `GET /v1/health` says
2353
+ * it. Absent when the service does not say.
2354
+ */
2355
+ location?: string | null;
2356
+ /**
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.
2359
+ */
2360
+ location_code?: string | null;
2328
2361
  /**
2329
2362
  * A notice about a coming change to the trial rules; `null` when there is none.
2330
2363
  */
@@ -2627,6 +2660,67 @@ export interface OtherUsage {
2627
2660
  entries: number;
2628
2661
  }
2629
2662
  export type Outcome = "completed" | "task_failed" | "env_failed" | "snapshot_failed" | "aborted_by_client" | "expired" | "cap_killed" | "not_started" | "superseded" | "grader_failed" | "preempted";
2663
+ /**
2664
+ * `GET /v1/me/outcomes?period=month` — what the organization (with a project's key: that project)
2665
+ * got this period, measured from its own runs and sandboxes. A figure with nothing behind it is
2666
+ * `null`; counts are `0`.
2667
+ */
2668
+ export interface Outcomes {
2669
+ /**
2670
+ * Finished runs of two or more sandboxes this period.
2671
+ */
2672
+ batch_runs: number;
2673
+ /**
2674
+ * For those runs: seconds not spent waiting, the sum of their sandboxes' own times minus
2675
+ * the runs' wall time; `null` without such a run.
2676
+ */
2677
+ batch_saved_s?: number | null;
2678
+ /**
2679
+ * For those runs: the sum of their sandboxes' own times divided by the runs' wall time
2680
+ * (how many times faster than one after another); `null` without such a run.
2681
+ */
2682
+ batch_speedup?: number | null;
2683
+ /**
2684
+ * Sandboxes that ended on a failure on our side and were not billed for it.
2685
+ */
2686
+ failures_ours: number;
2687
+ /**
2688
+ * What was given back for those failures, in USD (`0` when they ended before any charge).
2689
+ */
2690
+ failures_ours_usd: number;
2691
+ /**
2692
+ * Failed sandboxes kept to reopen that can still be reopened.
2693
+ */
2694
+ kept_failures: number;
2695
+ /**
2696
+ * Hours this period's sandboxes spent paused, which is not billed; `null` if none paused.
2697
+ */
2698
+ paused_hours?: number | null;
2699
+ /**
2700
+ * What those paused hours would have cost running, in USD; `null` if none paused.
2701
+ */
2702
+ paused_usd?: number | null;
2703
+ /**
2704
+ * `month`: the calendar month so far, UTC.
2705
+ */
2706
+ period: string;
2707
+ /**
2708
+ * The median milliseconds until a sandbox was ready to take work; `null` with no sandbox.
2709
+ */
2710
+ ready_ms_p50?: number | null;
2711
+ /**
2712
+ * Runs started this period.
2713
+ */
2714
+ runs: number;
2715
+ /**
2716
+ * Sandboxes started this period.
2717
+ */
2718
+ sandboxes: number;
2719
+ /**
2720
+ * When the period began (RFC 3339, UTC).
2721
+ */
2722
+ since: string;
2723
+ }
2630
2724
  /**
2631
2725
  * What a parked sandbox occupies on disk.
2632
2726
  */
@@ -3956,7 +4050,8 @@ export interface SandboxList {
3956
4050
  next_cursor?: string | null;
3957
4051
  sandboxes: Array<SandboxView>;
3958
4052
  /**
3959
- * The state filter this list was produced with; empty when none was given.
4053
+ * The state filter this list was produced with; empty when none was given. `queued` keeps
4054
+ * every waiting sandbox; `awaiting_capacity` only those waiting for capacity being added.
3960
4055
  */
3961
4056
  state: Array<string>;
3962
4057
  }
@@ -4256,13 +4351,14 @@ export interface SandboxView {
4256
4351
  */
4257
4352
  billable_seconds?: number | null;
4258
4353
  /**
4259
- * Once it has ended, what it cost in USD at its run's rates (the run report's `cost`).
4260
- * Present on `GET /v1/sandboxes/{id}` for an ended sandbox; absent from list rows.
4354
+ * What it has cost in USD at its run's rates: once it has ended, the run report's `cost`; while
4355
+ * it is live, the seconds billed so far (running time only). Present on `GET /v1/sandboxes/{id}`
4356
+ * once it has started; absent from list rows.
4261
4357
  */
4262
4358
  cost_usd?: number | null;
4263
4359
  /**
4264
4360
  * Milliseconds from the start request, after any time queued, to the sandbox being handed
4265
- * over: it does not include `queued_ms`. `total_ms` is the whole wait.
4361
+ * over: it does not include `queued_ms`. Add `queued_ms` for the whole wait.
4266
4362
  */
4267
4363
  e2e_ms?: number | null;
4268
4364
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promigence/sdk",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.4",
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",