@aexhq/sdk 0.37.3 → 0.38.0

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.
Files changed (59) hide show
  1. package/README.md +8 -8
  2. package/dist/_contracts/event-stream-client.d.ts +11 -0
  3. package/dist/_contracts/event-stream-client.js +45 -5
  4. package/dist/_contracts/http.d.ts +1 -1
  5. package/dist/_contracts/http.js +85 -13
  6. package/dist/_contracts/operations.d.ts +1 -1
  7. package/dist/_contracts/operations.js +43 -4
  8. package/dist/_contracts/provider-support.d.ts +2 -2
  9. package/dist/_contracts/provider-support.js +1 -1
  10. package/dist/_contracts/run-retention.d.ts +1 -1
  11. package/dist/_contracts/run-unit.d.ts +1 -1
  12. package/dist/_contracts/run-unit.js +12 -9
  13. package/dist/_contracts/runtime-types.d.ts +21 -9
  14. package/dist/_contracts/sdk-errors.d.ts +44 -2
  15. package/dist/_contracts/sdk-errors.js +104 -2
  16. package/dist/_contracts/sdk-secrets.js +18 -1
  17. package/dist/_contracts/side-effect-audit.d.ts +4 -4
  18. package/dist/_contracts/side-effect-audit.js +6 -6
  19. package/dist/_contracts/submission.d.ts +1 -1
  20. package/dist/asset-upload.js +8 -41
  21. package/dist/asset-upload.js.map +1 -1
  22. package/dist/cli.mjs +327 -87
  23. package/dist/cli.mjs.sha256 +1 -1
  24. package/dist/client.d.ts +20 -6
  25. package/dist/client.js +148 -18
  26. package/dist/client.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/retry.js +66 -6
  31. package/dist/retry.js.map +1 -1
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/docs/authentication.md +16 -7
  35. package/docs/billing.md +3 -3
  36. package/docs/concepts/composition.md +1 -1
  37. package/docs/concepts/providers-and-runtimes.md +1 -1
  38. package/docs/concepts/runs.md +1 -1
  39. package/docs/concepts/subagents.md +6 -3
  40. package/docs/credentials.md +7 -9
  41. package/docs/defaults.md +3 -3
  42. package/docs/errors.md +8 -4
  43. package/docs/events.md +5 -5
  44. package/docs/limits-and-quotas.md +3 -3
  45. package/docs/networking.md +1 -1
  46. package/docs/outputs.md +2 -2
  47. package/docs/provider-runtime-capabilities.md +3 -3
  48. package/docs/public-surface.json +2 -2
  49. package/docs/quickstart.md +4 -4
  50. package/docs/retries.md +1 -1
  51. package/docs/run-config.md +1 -1
  52. package/docs/run-record.md +1 -1
  53. package/docs/secrets.md +4 -4
  54. package/docs/skills.md +1 -1
  55. package/docs/testing.md +1 -1
  56. package/docs/vision-skills.md +1 -1
  57. package/docs/webhooks.md +4 -4
  58. package/examples/feature-tour.ts +3 -3
  59. package/package.json +2 -2
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();
@@ -1465,17 +1581,21 @@ var encoder2 = new TextEncoder();
1465
1581
  // ../contracts/dist/http.js
1466
1582
  var HttpClient = class {
1467
1583
  #baseUrl;
1468
- #apiToken;
1584
+ #apiKey;
1469
1585
  #fetch;
1470
1586
  #debug;
1471
1587
  constructor(options) {
1472
- if (!options.apiToken) {
1473
- throw new Error("HttpClient: apiToken is required");
1588
+ if (!options.apiKey) {
1589
+ throw new Error("HttpClient: apiKey is required");
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);
1478
- this.#apiToken = options.apiToken;
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
+ }
1598
+ this.#apiKey = options.apiKey;
1479
1599
  this.#fetch = options.fetch ?? fetch;
1480
1600
  this.#debug = options.debug;
1481
1601
  }
@@ -1490,7 +1610,7 @@ var HttpClient = class {
1490
1610
  }
1491
1611
  const headers = {
1492
1612
  accept: "application/json",
1493
- authorization: `Bearer ${this.#apiToken}`,
1613
+ authorization: `Bearer ${this.#apiKey}`,
1494
1614
  ...normalizeHeaders(init.headers)
1495
1615
  };
1496
1616
  if (init.body !== void 0 && init.body !== null && !headers["content-type"]) {
@@ -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
  }
@@ -1513,19 +1639,37 @@ var HttpClient = class {
1513
1639
  url.searchParams.set(key, value);
1514
1640
  }
1515
1641
  const headers = {
1516
- authorization: `Bearer ${this.#apiToken}`,
1642
+ authorization: `Bearer ${this.#apiKey}`,
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) {
@@ -3060,7 +3252,7 @@ function isSessionOk(status2) {
3060
3252
  return status2 === "idle" || status2 === "suspended" || status2 === "succeeded";
3061
3253
  }
3062
3254
  function extractCommonHostFlags(argv) {
3063
- let apiToken = null;
3255
+ let apiKey = null;
3064
3256
  let aexUrl = null;
3065
3257
  let debug2 = false;
3066
3258
  const rest = [];
@@ -3070,11 +3262,11 @@ function extractCommonHostFlags(argv) {
3070
3262
  debug2 = true;
3071
3263
  continue;
3072
3264
  }
3073
- if (arg === "--api-token") {
3265
+ if (arg === "--api-key") {
3074
3266
  const v = argv[++i2];
3075
3267
  if (v === void 0)
3076
- return { ok: false, reason: "--api-token requires a value" };
3077
- apiToken = v;
3268
+ return { ok: false, reason: "--api-key requires a value" };
3269
+ apiKey = v;
3078
3270
  continue;
3079
3271
  }
3080
3272
  if (arg === "--aex-url") {
@@ -3087,18 +3279,18 @@ function extractCommonHostFlags(argv) {
3087
3279
  if (arg === "--workspace" || arg === "--workspace-id") {
3088
3280
  return {
3089
3281
  ok: false,
3090
- reason: `unknown flag ${arg}: workspace is derived from --api-token on the server; drop this flag`
3282
+ reason: `unknown flag ${arg}: workspace is derived from --api-key on the server; drop this flag`
3091
3283
  };
3092
3284
  }
3093
3285
  rest.push(arg);
3094
3286
  }
3095
- return { ok: true, flags: { apiToken, aexUrl, debug: debug2, rest } };
3287
+ return { ok: true, flags: { apiKey, aexUrl, debug: debug2, rest } };
3096
3288
  }
3097
3289
  async function resolveCommonHostFlags(io2, argv) {
3098
3290
  const extracted = extractCommonHostFlags(argv);
3099
3291
  if (!extracted.ok)
3100
3292
  return extracted;
3101
- const { apiToken: flagToken, aexUrl: flagUrl, debug: debug2, rest } = extracted.flags;
3293
+ const { apiKey: flagToken, aexUrl: flagUrl, debug: debug2, rest } = extracted.flags;
3102
3294
  let token = flagToken;
3103
3295
  let url = flagUrl;
3104
3296
  let source = flagToken ? "flag" : "none";
@@ -3106,8 +3298,8 @@ async function resolveCommonHostFlags(io2, argv) {
3106
3298
  if (!token && io2.configStore) {
3107
3299
  const stored = await io2.configStore.read();
3108
3300
  storedLocation = io2.configStore.location();
3109
- if (stored?.apiToken) {
3110
- token = stored.apiToken;
3301
+ if (stored?.apiKey) {
3302
+ token = stored.apiKey;
3111
3303
  source = "stored";
3112
3304
  if (!url && stored.aexUrl)
3113
3305
  url = stored.aexUrl;
@@ -3115,35 +3307,56 @@ async function resolveCommonHostFlags(io2, argv) {
3115
3307
  }
3116
3308
  const resolvedUrl = url ?? AEX_DEFAULT_BASE_URL;
3117
3309
  if (debug2) {
3118
- const where = source === "flag" ? "--api-token flag" : source === "stored" ? `stored token (${storedLocation})` : "none";
3310
+ const where = source === "flag" ? "--api-key flag" : source === "stored" ? `stored token (${storedLocation})` : "none";
3119
3311
  io2.stderr(`[aex] auth: ${where}; aex-url=${resolvedUrl}
3120
3312
  `);
3121
3313
  }
3122
3314
  if (!token) {
3123
- return { ok: false, reason: "no API token \u2014 pass --api-token or run `aex login`" };
3315
+ return { ok: false, reason: "no API key \u2014 pass --api-key or run `aex login`" };
3124
3316
  }
3125
- return { ok: true, flags: { apiToken: token, aexUrl: resolvedUrl, debug: debug2 }, rest };
3317
+ return { ok: true, flags: { apiKey: token, aexUrl: resolvedUrl, debug: debug2 }, rest };
3126
3318
  }
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
- return "malformed request \u2014 if this is an auth failure, check --api-token or run `aex login`";
3357
+ return "malformed request \u2014 if this is an auth failure, check --api-key or run `aex login`";
3145
3358
  if (status2 === 401)
3146
- return "check --api-token, or run `aex login`";
3359
+ return "check --api-key, or run `aex login`";
3147
3360
  if (status2 === 403)
3148
3361
  return "token lacks permission for this workspace/action";
3149
3362
  if (status2 === 404)
@@ -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)
@@ -3195,7 +3428,7 @@ function levenshtein(a, b) {
3195
3428
  function makeHttpClient(io2, flags) {
3196
3429
  return new HttpClient({
3197
3430
  baseUrl: flags.aexUrl,
3198
- apiToken: flags.apiToken,
3431
+ apiKey: flags.apiKey,
3199
3432
  fetch: io2.fetchImpl,
3200
3433
  // `--debug`: route the transport's redacted per-request traces to stderr.
3201
3434
  ...flags.debug ? { debug: (line) => io2.stderr(`${line}
@@ -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);
@@ -5101,7 +5335,7 @@ function makeAwsSources(region) {
5101
5335
  }
5102
5336
  };
5103
5337
  }
5104
- var USAGE = "usage: aex debug <run-id> [--plane dev|prd] [--region eu-west-2] [--out dir] [--account <id>] [--cloudwatch] [--since <dur>] [--with-outputs]\n operator command \u2014 uses the standard AWS SDK credential chain (env/profile), NOT --api-token\n";
5338
+ var USAGE = "usage: aex debug <run-id> [--plane dev|prd] [--region eu-west-2] [--out dir] [--account <id>] [--cloudwatch] [--since <dur>] [--with-outputs]\n operator command \u2014 uses the standard AWS SDK credential chain (env/profile), NOT --api-key\n";
5105
5339
  async function runDebugCmd(io2, argv) {
5106
5340
  if (await refuseInsideManagedRun(io2, "debug"))
5107
5341
  return USAGE_ERR;
@@ -5203,19 +5437,19 @@ async function runLoginCmd(io2, argv) {
5203
5437
  `);
5204
5438
  return USAGE_ERR;
5205
5439
  }
5206
- const { apiToken, aexUrl, debug: debug2, rest } = extracted.flags;
5440
+ const { apiKey, aexUrl, debug: debug2, rest } = extracted.flags;
5207
5441
  const positional = rest.filter((a) => !a.startsWith("--"));
5208
5442
  if (positional.length > 0) {
5209
5443
  io2.stderr(`unexpected arguments: ${positional.join(" ")}
5210
5444
  `);
5211
5445
  return USAGE_ERR;
5212
5446
  }
5213
- if (!apiToken) {
5214
- io2.stderr("usage: aex login --api-token <token> [--aex-url <url>]\n");
5447
+ if (!apiKey) {
5448
+ io2.stderr("usage: aex login --api-key <token> [--aex-url <url>]\n");
5215
5449
  return USAGE_ERR;
5216
5450
  }
5217
5451
  const resolvedUrl = aexUrl ?? AEX_DEFAULT_BASE_URL;
5218
- const http = makeHttpClient(io2, { apiToken, aexUrl: resolvedUrl, debug: debug2 });
5452
+ const http = makeHttpClient(io2, { apiKey, aexUrl: resolvedUrl, debug: debug2 });
5219
5453
  let workspaceId;
5220
5454
  try {
5221
5455
  const me = await operations_exports.whoami(http);
@@ -5232,7 +5466,7 @@ async function runLoginCmd(io2, argv) {
5232
5466
  ...described.remedy ? { remedy: described.remedy } : {}
5233
5467
  });
5234
5468
  }
5235
- await io2.configStore.write({ schemaVersion: 1, apiToken, ...aexUrl ? { aexUrl: resolvedUrl } : {} });
5469
+ await io2.configStore.write({ schemaVersion: 1, apiKey, ...aexUrl ? { aexUrl: resolvedUrl } : {} });
5236
5470
  if (debug2)
5237
5471
  io2.stderr(`[aex] login: persisted to ${io2.configStore.location()}
5238
5472
  `);
@@ -5273,8 +5507,8 @@ async function runAuthStatusCmd(io2, argv) {
5273
5507
  return USAGE_ERR;
5274
5508
  }
5275
5509
  const stored = await io2.configStore.read();
5276
- const hasToken = Boolean(stored?.apiToken);
5277
- const tokenSuffix = hasToken ? stored.apiToken.slice(-4) : void 0;
5510
+ const hasToken = Boolean(stored?.apiKey);
5511
+ const tokenSuffix = hasToken ? stored.apiKey.slice(-4) : void 0;
5278
5512
  io2.stdout(JSON.stringify({
5279
5513
  configPath: io2.configStore.location(),
5280
5514
  hasToken,
@@ -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": {
@@ -5863,28 +6103,28 @@ async function dispatch(io2, args) {
5863
6103
  async function printGlobalHelp(io2) {
5864
6104
  io2.stdout("aex \u2014 unified CLI for the aex platform (mirrors the SDK 1:1)\n\n");
5865
6105
  io2.stdout("Usage:\n");
5866
- io2.stdout(" aex run --config <run.json> --<provider>-api-key K --api-token T [flags]\n");
5867
- io2.stdout(" aex run --model M --prompt P [--system S] [--mcp name=url ...] --<provider>-api-key K --api-token T [flags]\n");
5868
- io2.stdout(" aex status <session-id> --api-token T\n");
5869
- io2.stdout(" aex deliveries <session-id> --api-token T\n");
5870
- io2.stdout(" aex wait <session-id> [--timeout 8m] [--interval 2s] --api-token T\n");
5871
- io2.stdout(" aex events <session-id> [--follow] [--timeout 8m] --api-token T\n");
5872
- io2.stdout(" aex tail <session-id> [--json] [--filter <type|source>] [--logs] [--settle] [--timeout 8m] --api-token T\n");
5873
- io2.stdout(" aex inspect <session-id> [--json] [--filter <type|source>] [--logs] [--timeout 8m] --api-token T\n");
5874
- io2.stdout(" aex outputs <session-id> --api-token T\n");
5875
- io2.stdout(" aex download <session-id> [--only outputs|events|metadata] [--out path] --api-token T\n");
5876
- io2.stdout(" aex cancel <session-id> --api-token T\n");
5877
- io2.stdout(" aex delete <session-id> --api-token T\n");
5878
- io2.stdout(" aex delete-asset <assetId|hash> --api-token T\n");
5879
- io2.stdout(" aex runs [--limit N] [--since ISO] --api-token T List the workspace's runs (newest first, JSON)\n");
5880
- io2.stdout(" aex sessions [--limit N] --api-token T List the workspace's sessions (newest first, JSON)\n");
5881
- io2.stdout(" aex whoami --api-token T\n");
5882
- io2.stdout(" aex billing [--json] --api-token T Show prepaid balance, month spend, and spend cap\n");
5883
- io2.stdout(" aex billing ledger [--limit N] --api-token T Recent credit-ledger entries (newest first, JSON)\n");
5884
- io2.stdout(" aex billing upgrade pro|team --api-token T Create a hosted checkout session and print its URL\n");
5885
- io2.stdout(" aex billing portal --api-token T Create a hosted billing portal session and print its URL\n");
5886
- io2.stdout(" aex webhooks secret --api-token T Reveal (create on first use) the webhook signing secret\n");
5887
- io2.stdout(" aex login --api-token T [--aex-url U] Persist token + url (then other verbs need no --api-token)\n");
6106
+ io2.stdout(" aex run --config <run.json> --<provider>-api-key K --api-key T [flags]\n");
6107
+ io2.stdout(" aex run --model M --prompt P [--system S] [--mcp name=url ...] --<provider>-api-key K --api-key T [flags]\n");
6108
+ io2.stdout(" aex status <session-id> --api-key T\n");
6109
+ io2.stdout(" aex deliveries <session-id> --api-key T\n");
6110
+ io2.stdout(" aex wait <session-id> [--timeout 8m] [--interval 2s] --api-key T\n");
6111
+ io2.stdout(" aex events <session-id> [--follow] [--timeout 8m] --api-key T\n");
6112
+ io2.stdout(" aex tail <session-id> [--json] [--filter <type|source>] [--logs] [--settle] [--timeout 8m] --api-key T\n");
6113
+ io2.stdout(" aex inspect <session-id> [--json] [--filter <type|source>] [--logs] [--timeout 8m] --api-key T\n");
6114
+ io2.stdout(" aex outputs <session-id> --api-key T\n");
6115
+ io2.stdout(" aex download <session-id> [--only outputs|events|metadata] [--out path] --api-key T\n");
6116
+ io2.stdout(" aex cancel <session-id> --api-key T\n");
6117
+ io2.stdout(" aex delete <session-id> --api-key T\n");
6118
+ io2.stdout(" aex delete-asset <assetId|hash> --api-key T\n");
6119
+ io2.stdout(" aex runs [--limit N] [--since ISO] --api-key T List the workspace's runs (newest first, JSON)\n");
6120
+ io2.stdout(" aex sessions [--limit N] --api-key T List the workspace's sessions (newest first, JSON)\n");
6121
+ io2.stdout(" aex whoami --api-key T\n");
6122
+ io2.stdout(" aex billing [--json] --api-key T Show prepaid balance, month spend, and spend cap\n");
6123
+ io2.stdout(" aex billing ledger [--limit N] --api-key T Recent credit-ledger entries (newest first, JSON)\n");
6124
+ io2.stdout(" aex billing upgrade pro|team --api-key T Create a hosted checkout session and print its URL\n");
6125
+ io2.stdout(" aex billing portal --api-key T Create a hosted billing portal session and print its URL\n");
6126
+ io2.stdout(" aex webhooks secret --api-key T Reveal (create on first use) the webhook signing secret\n");
6127
+ io2.stdout(" aex login --api-key T [--aex-url U] Persist token + url (then other verbs need no --api-key)\n");
5888
6128
  io2.stdout(" aex logout Clear the stored token\n");
5889
6129
  io2.stdout(" aex auth status Show the resolved config (token never printed)\n");
5890
6130
  io2.stdout(" aex models list [--json] List models + default provider (no token needed)\n");
@@ -5894,7 +6134,7 @@ async function printGlobalHelp(io2) {
5894
6134
  io2.stdout(" aex debug <run-id> [--plane dev|prd] [--region eu-west-2] [--cloudwatch] [--with-outputs] (operator; AWS creds)\n");
5895
6135
  io2.stdout(" aex --help\n\n");
5896
6136
  io2.stdout("Common flags on every host subcommand:\n");
5897
- io2.stdout(" --api-token <token> REQUIRED \u2014 aex SDK API token (workspace is derived from it)\n");
6137
+ io2.stdout(" --api-key <token> REQUIRED \u2014 aex SDK API key (workspace is derived from it)\n");
5898
6138
  io2.stdout(" --aex-url <url> Optional; defaults to https://api.aex.dev (local/staging/hosted plane)\n");
5899
6139
  io2.stdout(" --debug Optional; print a redacted per-request trace to stderr (uploads nothing)\n\n");
5900
6140
  io2.stdout("aex run flags:\n");