@aexhq/sdk 0.37.2 → 0.37.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.
package/dist/cli.mjs CHANGED
@@ -22,7 +22,7 @@ var COMMON_EVIDENCE = [
22
22
  var ANTHROPIC_LIVE_USER_EVIDENCE = [
23
23
  {
24
24
  label: "Installed-SDK Anthropic live user test",
25
- href: "../../../apps/user-tests/test/live/live-sdk-anthropic-managed.test.ts"
25
+ href: "../../../apps/user-tests/test/live/providers/live-sdk-anthropic-managed.test.ts"
26
26
  }
27
27
  ];
28
28
  var DEEPSEEK_LIVE_USER_EVIDENCE = [
@@ -652,6 +652,7 @@ var isTerminalType = (e) => e.type === "RUN_FINISHED" || e.type === "RUN_ERROR"
652
652
  var COORDINATOR_PING = "aex:ping";
653
653
  var DEFAULT_IDLE_TIMEOUT_MS = 45e3;
654
654
  var DEFAULT_PING_INTERVAL_MS = 15e3;
655
+ var DEFAULT_EVENT_QUIET_RECHECK_MS = 9e4;
655
656
  async function* streamCoordinatorEvents(opts) {
656
657
  const makeWs = opts.webSocketFactory ?? ((url) => new WebSocket(url));
657
658
  const isTerminal = opts.isTerminal ?? isTerminalType;
@@ -659,6 +660,7 @@ async function* streamCoordinatorEvents(opts) {
659
660
  const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
660
661
  const idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
661
662
  const pingIntervalMs = opts.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
663
+ const eventQuietRecheckMs = opts.eventQuietRecheckMs ?? DEFAULT_EVENT_QUIET_RECHECK_MS;
662
664
  let cursor = (opts.from ?? 0) - 1;
663
665
  let attempts = 0;
664
666
  let done = false;
@@ -681,6 +683,7 @@ async function* streamCoordinatorEvents(opts) {
681
683
  };
682
684
  let idleTimer = null;
683
685
  let pingTimer = null;
686
+ let quietTimer = null;
684
687
  const stopTimers = () => {
685
688
  if (idleTimer !== null) {
686
689
  clearTimeout(idleTimer);
@@ -690,6 +693,10 @@ async function* streamCoordinatorEvents(opts) {
690
693
  clearInterval(pingTimer);
691
694
  pingTimer = null;
692
695
  }
696
+ if (quietTimer !== null) {
697
+ clearTimeout(quietTimer);
698
+ quietTimer = null;
699
+ }
693
700
  };
694
701
  const armIdle = () => {
695
702
  if (idleTimeoutMs <= 0)
@@ -706,6 +713,21 @@ async function* streamCoordinatorEvents(opts) {
706
713
  wake();
707
714
  }, idleTimeoutMs);
708
715
  };
716
+ const armQuiet = () => {
717
+ if (eventQuietRecheckMs <= 0)
718
+ return;
719
+ if (quietTimer !== null)
720
+ clearTimeout(quietTimer);
721
+ quietTimer = setTimeout(() => {
722
+ quietTimer = null;
723
+ if (closed)
724
+ return;
725
+ closed = true;
726
+ disconnectReason = "quiet_recheck";
727
+ closeQuietly(ws);
728
+ wake();
729
+ }, eventQuietRecheckMs);
730
+ };
709
731
  ws.addEventListener("open", () => {
710
732
  armIdle();
711
733
  if (pingIntervalMs > 0 && typeof ws.send === "function") {
@@ -724,9 +746,12 @@ async function* streamCoordinatorEvents(opts) {
724
746
  return;
725
747
  try {
726
748
  const evt = JSON.parse(data);
727
- if (typeof evt.sequence === "number" && evt.sequence > cursor) {
728
- queue.push(evt);
729
- wake();
749
+ if (typeof evt.sequence === "number") {
750
+ armQuiet();
751
+ if (evt.sequence > cursor) {
752
+ queue.push(evt);
753
+ wake();
754
+ }
730
755
  }
731
756
  } catch {
732
757
  }
@@ -756,6 +781,7 @@ async function* streamCoordinatorEvents(opts) {
756
781
  };
757
782
  opts.signal?.addEventListener("abort", onAbort, { once: true });
758
783
  armIdle();
784
+ armQuiet();
759
785
  try {
760
786
  while (true) {
761
787
  while (queue.length > 0) {
@@ -778,6 +804,7 @@ async function* streamCoordinatorEvents(opts) {
778
804
  } finally {
779
805
  stopTimers();
780
806
  opts.signal?.removeEventListener("abort", onAbort);
807
+ closeQuietly(ws);
781
808
  }
782
809
  if (done || opts.signal?.aborted)
783
810
  return;
@@ -786,7 +813,9 @@ async function* streamCoordinatorEvents(opts) {
786
813
  console.warn(`[aex] event stream gave up after ${maxReconnects} reconnect attempt(s) (last: ${disconnectReason || "unknown"}); ended before a terminal event at seq ${cursor + 1}`);
787
814
  return;
788
815
  }
789
- console.warn(`[aex] event stream disconnected (${disconnectReason || "unknown"}); reconnecting attempt ${attempts} from seq ${cursor + 1}`);
816
+ if (disconnectReason !== "quiet_recheck") {
817
+ console.warn(`[aex] event stream disconnected (${disconnectReason || "unknown"}); reconnecting attempt ${attempts} from seq ${cursor + 1}`);
818
+ }
790
819
  await sleep(reconnectDelayMs, opts.signal);
791
820
  }
792
821
  }
@@ -807,17 +836,17 @@ function sleep(ms, signal) {
807
836
  }
808
837
 
809
838
  // ../contracts/dist/run-unit.js
810
- function parseRunUnitSubmission(input) {
839
+ function parseRunUnitSubmission(input, fallbackModel) {
811
840
  if (!input || typeof input !== "object" || Array.isArray(input)) {
812
- return fallbackFlat();
841
+ return fallbackFlat(fallbackModel);
813
842
  }
814
843
  const value = input;
815
844
  if (value.kind === "submission") {
816
- return parseFlatProjection(value);
845
+ return parseFlatProjection(value, fallbackModel);
817
846
  }
818
- return fallbackFlat();
847
+ return fallbackFlat(fallbackModel);
819
848
  }
820
- function parseFlatProjection(value) {
849
+ function parseFlatProjection(value, fallbackModel) {
821
850
  const submissionRaw = isRecord(value.submission) ? value.submission : {};
822
851
  const outputsRaw = isRecord(submissionRaw.outputs) ? submissionRaw.outputs : {};
823
852
  const allowedDirs = toOptionalStringArray(outputsRaw.allowedDirs);
@@ -827,7 +856,7 @@ function parseFlatProjection(value) {
827
856
  const maxTotalBytes = toOptionalPositiveInteger(outputsRaw.maxTotalBytes);
828
857
  const maxFiles = toOptionalPositiveInteger(outputsRaw.maxFiles);
829
858
  const submission = {
830
- model: coerceRunUnitModel(submissionRaw.model),
859
+ model: coerceRunUnitModel(submissionRaw.model ?? fallbackModel),
831
860
  ...typeof submissionRaw.system === "string" ? { system: submissionRaw.system } : {},
832
861
  prompt: toStringArray(submissionRaw.prompt),
833
862
  agentsMd: [],
@@ -859,11 +888,11 @@ function parseSecurityProfile(value) {
859
888
  function toOptionalPositiveInteger(value) {
860
889
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
861
890
  }
862
- function fallbackFlat() {
891
+ function fallbackFlat(fallbackModel) {
863
892
  return {
864
893
  kind: "submission",
865
894
  submission: {
866
- model: Models.CLAUDE_HAIKU_4_5,
895
+ model: coerceRunUnitModel(fallbackModel),
867
896
  prompt: [],
868
897
  agentsMd: [],
869
898
  files: [],
@@ -892,7 +921,10 @@ function normalizeRunUnit(raw) {
892
921
  ...str3(r.terminalAt) ? { terminalAt: r.terminalAt } : {},
893
922
  ...str3(r.deletedAt) ? { deletedAt: r.deletedAt } : {},
894
923
  attemptCount: typeof r.attemptCount === "number" ? r.attemptCount : Array.isArray(r.attempts) ? r.attempts.length : 0,
895
- submission: parseRunUnitSubmission(r.submission),
924
+ // Plane responses that project a flat record (no `submission` snapshot)
925
+ // still carry the run's `model` at the top level — prefer it over the
926
+ // static fallback so `unit()` never claims a model the run did not use.
927
+ submission: parseRunUnitSubmission(r.submission, r.model),
896
928
  ...isRecord(r.capsSnapshot) ? { capsSnapshot: r.capsSnapshot } : {},
897
929
  attempts: arr(r.attempts),
898
930
  events: {
@@ -1363,6 +1395,11 @@ var HIGH_ENTROPY_CANDIDATE = /[A-Za-z0-9+/=-]{24,}/g;
1363
1395
  var ENTROPY_BITS_PER_CHAR = 3;
1364
1396
  var MIN_CHAR_CLASSES = 2;
1365
1397
  var HIGH_ENTROPY_NO_DIGIT_MIN_LEN = 40;
1398
+ function isCanonicalRunIdHex(input, matchStart, match) {
1399
+ if (!/^[0-9a-f]{32}$/.test(match))
1400
+ return false;
1401
+ return input.slice(Math.max(0, matchStart - 4), matchStart) === "run_";
1402
+ }
1366
1403
  function redactSecrets(value) {
1367
1404
  if (typeof value === "string") {
1368
1405
  return redactString(value);
@@ -1395,7 +1432,7 @@ function redactString(input, known = []) {
1395
1432
  // prefix; the rest replace the whole match.
1396
1433
  typeof captured === "string" ? `${captured} ${REDACTED}` : REDACTED
1397
1434
  )), out);
1398
- return out.replace(HIGH_ENTROPY_CANDIDATE, (match) => looksHighEntropySecret(match) ? REDACTED : match);
1435
+ return out.replace(HIGH_ENTROPY_CANDIDATE, (match, offset, whole) => !isCanonicalRunIdHex(whole, offset, match) && looksHighEntropySecret(match) ? REDACTED : match);
1399
1436
  }
1400
1437
  function isSecretKey(key) {
1401
1438
  return /(?:api[_-]?key|authorization|token|secret|password|credential)/i.test(key);
@@ -1437,8 +1474,8 @@ function shannonEntropyBits(value) {
1437
1474
  var AexError = class extends Error {
1438
1475
  code;
1439
1476
  details;
1440
- constructor(code, message, details) {
1441
- super(redactSecrets(message));
1477
+ constructor(code, message, details, options) {
1478
+ super(redactSecrets(message), options?.cause === void 0 ? void 0 : { cause: options.cause });
1442
1479
  this.name = this.constructor.name;
1443
1480
  this.code = code;
1444
1481
  this.details = details === void 0 ? void 0 : redactSecrets(details);
@@ -1458,6 +1495,85 @@ var AexApiError = class extends AexError {
1458
1495
  this.body = redactSecrets(body);
1459
1496
  }
1460
1497
  };
1498
+ var AexNetworkError = class extends AexError {
1499
+ method;
1500
+ /** Request host — never carries credentials or the query string. */
1501
+ host;
1502
+ path;
1503
+ /** Transport failure code (e.g. `ECONNREFUSED`), when detectable. */
1504
+ causeCode;
1505
+ /** Attempts made when a retry layer exhausted its budget; `1` otherwise. */
1506
+ attempts;
1507
+ constructor(args) {
1508
+ const causeCode = extractErrorCode(args.cause);
1509
+ super("NETWORK_ERROR", networkErrorMessage(args, causeCode), { method: args.method, host: args.host, path: args.path, ...causeCode ? { code: causeCode } : {} }, { cause: args.cause });
1510
+ this.method = args.method;
1511
+ this.host = args.host;
1512
+ this.path = args.path;
1513
+ this.causeCode = causeCode;
1514
+ this.attempts = args.attempts ?? 1;
1515
+ }
1516
+ };
1517
+ function networkErrorMessage(args, causeCode) {
1518
+ const target = args.host ? `${args.host}${args.path}` : "request";
1519
+ const detail = shortCauseMessage(args.cause, causeCode);
1520
+ const suffix = args.attempts === void 0 ? "" : ` after ${args.attempts} attempt${args.attempts === 1 ? "" : "s"} over ${args.elapsedMs ?? 0}ms`;
1521
+ let message = `${args.method} ${target} failed`;
1522
+ if (causeCode)
1523
+ message += `: ${causeCode}`;
1524
+ if (detail)
1525
+ message += causeCode ? ` (${detail})` : `: ${detail}`;
1526
+ return message + suffix;
1527
+ }
1528
+ function shortCauseMessage(cause, causeCode) {
1529
+ const nested = cause instanceof Error && cause.cause instanceof Error ? cause.cause : cause;
1530
+ const message = nested instanceof Error ? nested.message || nested.name : typeof nested === "string" ? nested : void 0;
1531
+ if (!message || message === causeCode)
1532
+ return void 0;
1533
+ return message.replace(/https?:\/\/[^\s<>"'`]+/g, (raw) => redactUrl(raw)).slice(0, 200);
1534
+ }
1535
+ function extractErrorCode(err2) {
1536
+ const code = stringProperty(err2, "code");
1537
+ if (code)
1538
+ return code;
1539
+ const cause = objectProperty(err2, "cause");
1540
+ const causeCode = stringProperty(cause, "code");
1541
+ if (causeCode)
1542
+ return causeCode;
1543
+ const match = /\bE[A-Z0-9_]+\b/.exec(errorMessageOf(err2));
1544
+ return match?.[0];
1545
+ }
1546
+ function redactUrl(url) {
1547
+ try {
1548
+ const parsed = new URL(url);
1549
+ const auth = parsed.username || parsed.password ? "[redacted]@" : "";
1550
+ const query = parsed.search ? "?[redacted]" : "";
1551
+ return `${parsed.protocol}//${auth}${parsed.host}${parsed.pathname}${query}`;
1552
+ } catch {
1553
+ const withoutAuth = url.replace(/\/\/[^/?#\s]+@/, "//[redacted]@");
1554
+ const queryStart = withoutAuth.indexOf("?");
1555
+ return queryStart === -1 ? withoutAuth : `${withoutAuth.slice(0, queryStart)}?[redacted]`;
1556
+ }
1557
+ }
1558
+ function errorMessageOf(err2) {
1559
+ if (err2 instanceof Error)
1560
+ return err2.message || err2.name;
1561
+ if (typeof err2 === "string")
1562
+ return err2;
1563
+ return String(err2);
1564
+ }
1565
+ function objectProperty(value, key) {
1566
+ if (!value || typeof value !== "object")
1567
+ return void 0;
1568
+ const prop = value[key];
1569
+ return prop && typeof prop === "object" ? prop : void 0;
1570
+ }
1571
+ function stringProperty(value, key) {
1572
+ if (!value || typeof value !== "object")
1573
+ return void 0;
1574
+ const prop = value[key];
1575
+ return typeof prop === "string" && prop.length > 0 ? prop : void 0;
1576
+ }
1461
1577
 
1462
1578
  // ../contracts/dist/webhook-verify.js
1463
1579
  var encoder2 = new TextEncoder();
@@ -1474,7 +1590,11 @@ var HttpClient = class {
1474
1590
  }
1475
1591
  const raw = options.baseUrl ?? AEX_DEFAULT_BASE_URL;
1476
1592
  const normalized = raw.endsWith("/") ? raw : `${raw}/`;
1477
- this.#baseUrl = new URL(normalized);
1593
+ try {
1594
+ this.#baseUrl = new URL(normalized);
1595
+ } catch (err2) {
1596
+ throw new Error(`HttpClient: invalid aex baseUrl ${JSON.stringify(redactUrl(raw))} \u2014 expected an absolute URL like "${AEX_DEFAULT_BASE_URL}"`, { cause: err2 });
1597
+ }
1478
1598
  this.#apiToken = options.apiToken;
1479
1599
  this.#fetch = options.fetch ?? fetch;
1480
1600
  this.#debug = options.debug;
@@ -1499,11 +1619,17 @@ var HttpClient = class {
1499
1619
  }
1500
1620
  }
1501
1621
  const startedMs = Date.now();
1502
- const response = await this.#fetch(url, { ...init, headers });
1622
+ let response;
1623
+ try {
1624
+ response = await this.#fetch(url, { ...init, headers });
1625
+ } catch (err2) {
1626
+ throw toNetworkError(init.method, url, err2);
1627
+ }
1503
1628
  this.#trace(init.method, url, response.status, startedMs);
1504
1629
  const body = await readJson(response);
1505
1630
  if (!response.ok) {
1506
- throw new AexApiError(response.status, extractErrorMessage(body), body);
1631
+ const errorBody = withResponseRequestId(body, response.headers);
1632
+ throw new AexApiError(response.status, extractErrorMessage(errorBody), errorBody);
1507
1633
  }
1508
1634
  return body;
1509
1635
  }
@@ -1517,15 +1643,33 @@ var HttpClient = class {
1517
1643
  ...normalizeHeaders(init.headers)
1518
1644
  };
1519
1645
  const startedMs = Date.now();
1520
- const response = await this.#fetch(url, { ...init, headers });
1646
+ let response;
1647
+ try {
1648
+ response = await this.#fetch(url, { ...init, headers });
1649
+ } catch (err2) {
1650
+ throw toNetworkError(init.method, url, err2);
1651
+ }
1521
1652
  this.#trace(init.method, url, response.status, startedMs);
1522
1653
  if (!response.ok) {
1523
1654
  const body = await readJson(response);
1524
- throw new AexApiError(response.status, extractErrorMessage(body), body);
1655
+ const errorBody = withResponseRequestId(body, response.headers);
1656
+ throw new AexApiError(response.status, extractErrorMessage(errorBody), errorBody);
1525
1657
  }
1526
1658
  return { response };
1527
1659
  }
1528
1660
  };
1661
+ function toNetworkError(method, url, err2) {
1662
+ if (err2 instanceof AexError)
1663
+ return err2;
1664
+ if (err2?.name === "AbortError")
1665
+ return err2;
1666
+ return new AexNetworkError({
1667
+ method: (method ?? "GET").toUpperCase(),
1668
+ host: url.host,
1669
+ path: url.pathname,
1670
+ cause: err2
1671
+ });
1672
+ }
1529
1673
  function normalizeHeaders(headers) {
1530
1674
  if (!headers)
1531
1675
  return {};
@@ -1545,11 +1689,36 @@ async function readJson(response) {
1545
1689
  return { raw: text };
1546
1690
  }
1547
1691
  }
1692
+ function withResponseRequestId(body, headers) {
1693
+ if (!body || typeof body !== "object" || Array.isArray(body))
1694
+ return body;
1695
+ const record = body;
1696
+ if (typeof record.requestId === "string" && record.requestId.trim())
1697
+ return body;
1698
+ const requestId = responseRequestId(headers);
1699
+ return requestId ? { ...record, requestId } : body;
1700
+ }
1701
+ function responseRequestId(headers) {
1702
+ for (const name of ["x-request-id", "request-id"]) {
1703
+ const value = headers.get(name)?.trim();
1704
+ if (value)
1705
+ return value;
1706
+ }
1707
+ return void 0;
1708
+ }
1548
1709
  function extractErrorMessage(body) {
1549
1710
  if (body && typeof body === "object") {
1550
1711
  const obj = body;
1551
- if (typeof obj.error === "string")
1712
+ if (typeof obj.error === "string") {
1713
+ const status2 = body.status;
1714
+ if (obj.error === "session_busy" && typeof status2 === "string") {
1715
+ return `session_busy (session status: ${status2})`;
1716
+ }
1717
+ if (typeof obj.message === "string" && obj.message.length > 0 && obj.message !== obj.error) {
1718
+ return `${obj.error}: ${obj.message}`;
1719
+ }
1552
1720
  return obj.error;
1721
+ }
1553
1722
  if (obj.error && typeof obj.error === "object" && "message" in obj.error) {
1554
1723
  const message = obj.error.message;
1555
1724
  if (typeof message === "string")
@@ -2360,7 +2529,23 @@ async function listRuns(http, query) {
2360
2529
  params.limit = String(query.limit);
2361
2530
  if (query?.cursor !== void 0)
2362
2531
  params.cursor = query.cursor;
2363
- return http.request("/api/runs", {}, params);
2532
+ const page = await http.request("/api/runs", {}, params);
2533
+ let changed = false;
2534
+ const runs = [];
2535
+ for (const run of page.runs) {
2536
+ if (typeof run.id !== "string" || typeof run.status !== "string" || typeof run.createdAt !== "string" || typeof run.updatedAt !== "string") {
2537
+ changed = true;
2538
+ continue;
2539
+ }
2540
+ if (typeof run.costUsd !== "number" && run.costUsd !== void 0) {
2541
+ const { costUsd: _dropped, ...rest } = run;
2542
+ runs.push(rest);
2543
+ changed = true;
2544
+ continue;
2545
+ }
2546
+ runs.push(run);
2547
+ }
2548
+ return changed ? { ...page, runs } : page;
2364
2549
  }
2365
2550
  function idempotencyHeaders(options) {
2366
2551
  return options?.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
@@ -2488,12 +2673,17 @@ async function outputLink(http, runId, selectorOrQuery, options) {
2488
2673
  method: "POST",
2489
2674
  body: JSON.stringify({ expiresInSeconds })
2490
2675
  });
2676
+ const effectiveExpiresIn = result.expiresInSeconds ?? expiresInSeconds;
2491
2677
  return {
2492
2678
  ...result,
2493
- expiresInSeconds: result.expiresInSeconds ?? expiresInSeconds,
2679
+ expiresInSeconds: effectiveExpiresIn,
2680
+ expiresAt: result.expiresAt ?? syntheticExpiresAt(effectiveExpiresIn),
2494
2681
  output: result.output ?? output
2495
2682
  };
2496
2683
  }
2684
+ function syntheticExpiresAt(expiresInSeconds) {
2685
+ return new Date(Date.now() + expiresInSeconds * 1e3).toISOString();
2686
+ }
2497
2687
  async function createOutputLink(http, runId, selectorOrQuery, options) {
2498
2688
  return outputLink(http, runId, selectorOrQuery, options);
2499
2689
  }
@@ -2503,9 +2693,11 @@ async function eventArchiveLink(http, runId, options) {
2503
2693
  method: "POST",
2504
2694
  body: JSON.stringify({ expiresInSeconds })
2505
2695
  });
2696
+ const effectiveExpiresIn = result.expiresInSeconds ?? expiresInSeconds;
2506
2697
  return {
2507
2698
  ...result,
2508
- expiresInSeconds: result.expiresInSeconds ?? expiresInSeconds
2699
+ expiresInSeconds: effectiveExpiresIn,
2700
+ expiresAt: result.expiresAt ?? syntheticExpiresAt(effectiveExpiresIn)
2509
2701
  };
2510
2702
  }
2511
2703
  function resolveOutputFileSelector(outputs, selector, runId) {
@@ -3127,18 +3319,39 @@ async function resolveCommonHostFlags(io2, argv) {
3127
3319
  function describeApiError(err2) {
3128
3320
  if (err2 instanceof AexApiError) {
3129
3321
  const remedy = remedyForStatus(err2.status);
3322
+ const detail = describeErrorBody(err2.body);
3130
3323
  return {
3131
3324
  code: err2.code,
3132
- message: err2.message,
3325
+ message: detail ? `${err2.message} \u2014 ${detail}` : err2.message,
3133
3326
  status: err2.status,
3134
3327
  ...remedy ? { remedy } : {}
3135
3328
  };
3136
3329
  }
3137
3330
  if (err2 instanceof AexError) {
3138
- return { code: err2.code, message: err2.message };
3331
+ const causeCode = err2 instanceof AexNetworkError ? err2.causeCode : extractErrorCode(err2.cause);
3332
+ const remedy = causeCode ? remedyForNetworkCode(causeCode) : void 0;
3333
+ const message = causeCode && !err2.message.includes(causeCode) ? `${err2.message} (${causeCode})` : err2.message;
3334
+ return { code: err2.code, message, ...remedy ? { remedy } : {} };
3139
3335
  }
3140
3336
  return { code: "error", message: err2 instanceof Error ? err2.message : String(err2) };
3141
3337
  }
3338
+ var STANDARD_ERROR_BODY_KEYS = /* @__PURE__ */ new Set(["ok", "error", "message", "code"]);
3339
+ function describeErrorBody(body) {
3340
+ if (!body || typeof body !== "object")
3341
+ return void 0;
3342
+ const record = body;
3343
+ const extraKeys = Object.keys(record).filter((key) => !STANDARD_ERROR_BODY_KEYS.has(key));
3344
+ if (extraKeys.length === 0)
3345
+ return void 0;
3346
+ let text;
3347
+ try {
3348
+ text = JSON.stringify(Object.fromEntries(extraKeys.map((key) => [key, record[key]])));
3349
+ } catch {
3350
+ return void 0;
3351
+ }
3352
+ const redacted = redactSecrets(text);
3353
+ return redacted.length > 400 ? `${redacted.slice(0, 400)}\u2026 (truncated)` : redacted;
3354
+ }
3142
3355
  function remedyForStatus(status2) {
3143
3356
  if (status2 === 400)
3144
3357
  return "malformed request \u2014 if this is an auth failure, check --api-token or run `aex login`";
@@ -3154,6 +3367,26 @@ function remedyForStatus(status2) {
3154
3367
  return "server error \u2014 retry; re-run with --debug to capture the request trace";
3155
3368
  return void 0;
3156
3369
  }
3370
+ function remedyForNetworkCode(code) {
3371
+ switch (code) {
3372
+ case "ECONNREFUSED":
3373
+ case "ENOTFOUND":
3374
+ case "EAI_AGAIN":
3375
+ case "EHOSTUNREACH":
3376
+ case "ENETUNREACH":
3377
+ return "cannot reach the aex API \u2014 check --aex-url and network connectivity";
3378
+ case "ECONNRESET":
3379
+ case "ETIMEDOUT":
3380
+ case "UND_ERR_CONNECT_TIMEOUT":
3381
+ return "connection dropped \u2014 retry; check --aex-url, network connectivity, or any proxy/VPN";
3382
+ case "CERT_HAS_EXPIRED":
3383
+ case "DEPTH_ZERO_SELF_SIGNED_CERT":
3384
+ case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
3385
+ return "TLS verification failed \u2014 check --aex-url points at the right host";
3386
+ default:
3387
+ return void 0;
3388
+ }
3389
+ }
3157
3390
  function suggest(input, candidates) {
3158
3391
  const needle = input.trim();
3159
3392
  if (!needle)
@@ -3411,7 +3644,7 @@ async function runRunCmd(io2, argv) {
3411
3644
  return USAGE_ERR;
3412
3645
  }
3413
3646
  rest = providerFlag.remaining;
3414
- let provider = DEFAULT_RUN_PROVIDER;
3647
+ let explicitProvider;
3415
3648
  if (providerFlag.value !== null) {
3416
3649
  if (!RUN_PROVIDERS.includes(providerFlag.value)) {
3417
3650
  const hint = suggest(providerFlag.value, RUN_PROVIDERS);
@@ -3419,7 +3652,7 @@ async function runRunCmd(io2, argv) {
3419
3652
  `);
3420
3653
  return USAGE_ERR;
3421
3654
  }
3422
- provider = providerFlag.value;
3655
+ explicitProvider = providerFlag.value;
3423
3656
  }
3424
3657
  const providerKeyValues = {};
3425
3658
  for (const p of RUN_PROVIDERS) {
@@ -3433,11 +3666,6 @@ async function runRunCmd(io2, argv) {
3433
3666
  if (flag.value !== null)
3434
3667
  providerKeyValues[p] = flag.value;
3435
3668
  }
3436
- if (!providerKeyValues[provider]) {
3437
- io2.stderr(`--${provider}-api-key is required when --provider is ${provider} (the platform does not store provider keys on your behalf)
3438
- `);
3439
- return USAGE_ERR;
3440
- }
3441
3669
  const idempotency = takeFlagValue(rest, "--idempotency-key");
3442
3670
  if (idempotency.error) {
3443
3671
  io2.stderr(`${idempotency.error}
@@ -3637,6 +3865,12 @@ async function runRunCmd(io2, argv) {
3637
3865
  ...Object.keys(metadataFlags.entries).length > 0 ? { metadata: { ...metadataFlags.entries } } : {}
3638
3866
  };
3639
3867
  }
3868
+ const provider = explicitProvider ?? providersForModel(runConfig.model)[0] ?? DEFAULT_RUN_PROVIDER;
3869
+ if (!providerKeyValues[provider]) {
3870
+ io2.stderr(`--${provider}-api-key is required for provider ${provider}${explicitProvider === void 0 ? ` (inferred from --model ${runConfig.model})` : ""} (the platform does not store provider keys on your behalf)
3871
+ `);
3872
+ return USAGE_ERR;
3873
+ }
3640
3874
  const mcpServersForSubmission = [...runConfig.mcpServers ?? []];
3641
3875
  const mcpServerSecrets = [];
3642
3876
  const mcpHeaderBag = new Map(mcpHeadersFromConfig);
@@ -5436,6 +5670,12 @@ function renderEnvelope(e, options = {}) {
5436
5670
  }
5437
5671
  case "CUSTOM": {
5438
5672
  const label = e.message ?? str2(e.data.name) ?? "custom";
5673
+ if (e.data.name === "aex.session.idle") {
5674
+ const value = e.data.value;
5675
+ const reason = value && typeof value.reason === "string" ? value.reason : "";
5676
+ if (reason && reason !== "completed")
5677
+ return `[aex] ${label} (${reason})`;
5678
+ }
5439
5679
  return `[aex] ${label}`;
5440
5680
  }
5441
5681
  case "LOG": {
@@ -1 +1 @@
1
- 0b259b81011fcda9d15954453cc67f17d1b28add9a32903218d068c3dd1c2e9d cli.mjs
1
+ c8d969d9ee5c6e6bb2e0a642ba8adce0aa235ef2bba96b99d8832ae1957ed2a3 cli.mjs
package/dist/client.d.ts CHANGED
@@ -68,7 +68,14 @@ export interface RunResult {
68
68
  readonly outputs: readonly Output[];
69
69
  /** Aggregate token usage when the deployment exposes it on the record. */
70
70
  readonly usage?: UsageSummary;
71
- /** Settle-time showback estimate (USD), from `run.costTelemetry`. */
71
+ /**
72
+ * Settle-time showback estimate (USD), from the settle-stamped session
73
+ * record's `costUsd` (the full `costTelemetry` block is served on
74
+ * `GET /api/runs/:id`, not on the session projection). The settle
75
+ * write lands tens of seconds AFTER the turn parks, so by default this is
76
+ * usually absent on a fresh run — pass `settleConsistent: true` to wait for
77
+ * it, or read `sessions.get(runId).costUsd` later.
78
+ */
72
79
  readonly costUsd?: number;
73
80
  /** The run's error message when `!ok`. */
74
81
  readonly error?: string;
@@ -82,6 +89,14 @@ export interface RunCollectOptions {
82
89
  readonly pingIntervalMs?: number;
83
90
  /** Throw a {@link RunStateError} when the run does not succeed. Default false. */
84
91
  readonly throwOnFailure?: boolean;
92
+ /**
93
+ * Wait (bounded, ~60s) for the settle write after the turn parks, so the
94
+ * result carries the settle-stamped `costUsd`/`usage`/`errorMessage`. The
95
+ * settle lambda lands tens of seconds after the park event, so this trades
96
+ * latency for a complete record. Default false: return at park; read
97
+ * `sessions.get(runId)` later for the showback.
98
+ */
99
+ readonly settleConsistent?: boolean;
85
100
  }
86
101
  export type SessionInput = string | readonly string[];
87
102
  export interface SessionEnvironmentOptions extends Omit<PlatformEnvironmentInput, "envVars"> {
@@ -342,6 +357,7 @@ export declare class SessionClient {
342
357
  create(options: SessionCreateOptions): Promise<SessionHandle>;
343
358
  open(sessionId: string): Promise<SessionHandle>;
344
359
  get(sessionId: string): Promise<Session>;
360
+ delete(sessionId: string, options?: Pick<SessionSendOptions, "idempotencyKey">): Promise<void>;
345
361
  list(query?: SessionListQuery): Promise<SessionListPage>;
346
362
  /**
347
363
  * Accessor over one session's captured output files, addressed by id without