@aexhq/sdk 0.36.0 → 0.37.1

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 (43) hide show
  1. package/README.md +1 -1
  2. package/dist/_contracts/event-envelope.d.ts +22 -1
  3. package/dist/_contracts/event-envelope.js +26 -2
  4. package/dist/_contracts/event-stream-client.js +7 -1
  5. package/dist/_contracts/operations.d.ts +30 -1
  6. package/dist/_contracts/operations.js +54 -1
  7. package/dist/_contracts/run-config.d.ts +1 -1
  8. package/dist/_contracts/run-unit.d.ts +12 -0
  9. package/dist/_contracts/run-unit.js +55 -0
  10. package/dist/_contracts/runtime-sizes.d.ts +2 -2
  11. package/dist/_contracts/runtime-sizes.js +5 -5
  12. package/dist/_contracts/runtime-types.d.ts +98 -0
  13. package/dist/_contracts/submission.d.ts +4 -4
  14. package/dist/cli.mjs +554 -69
  15. package/dist/cli.mjs.sha256 +1 -1
  16. package/dist/client.d.ts +40 -1
  17. package/dist/client.js +90 -5
  18. package/dist/client.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/docs/authentication.md +92 -0
  24. package/docs/billing.md +112 -0
  25. package/docs/concepts/agent-tools.md +4 -4
  26. package/docs/concepts/providers-and-runtimes.md +4 -1
  27. package/docs/concepts/runs.md +2 -1
  28. package/docs/concepts/subagents.md +85 -0
  29. package/docs/credentials.md +27 -3
  30. package/docs/defaults.md +9 -7
  31. package/docs/errors.md +132 -0
  32. package/docs/events.md +36 -23
  33. package/docs/limits-and-quotas.md +29 -13
  34. package/docs/limits.md +2 -2
  35. package/docs/mcp.md +1 -1
  36. package/docs/networking.md +68 -42
  37. package/docs/outputs.md +4 -3
  38. package/docs/public-surface.json +1 -1
  39. package/docs/quickstart.md +9 -6
  40. package/docs/run-config.md +1 -1
  41. package/docs/secrets.md +5 -0
  42. package/docs/webhooks.md +132 -0
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@ The package ships:
16
16
  ## Install
17
17
 
18
18
  ```bash
19
- bun add @aexhq/sdk
19
+ npm i @aexhq/sdk
20
20
  ```
21
21
 
22
22
  This installs the TypeScript SDK exports and the bundled `aex` CLI. Set both
@@ -195,6 +195,21 @@ export declare function isRunFinished(e: AexEvent): boolean;
195
195
  export declare function isRunError(e: AexEvent): boolean;
196
196
  /** A terminal event of either flavour (finished or error). */
197
197
  export declare function isRunTerminal(e: AexEvent): boolean;
198
+ /**
199
+ * The CUSTOM `data.name`s the MANAGED runtime emits as a run/turn's terminal.
200
+ * A managed one-shot run PARKS (session_parked.v1 → `aex.session.idle` / `.error`
201
+ * / `.suspended`) rather than writing a `session_finished` → RUN_FINISHED, so
202
+ * these — not just the AG-UI RUN_FINISHED/RUN_ERROR — are what actually ends a
203
+ * managed run's event stream. Kept in sync with the platform journal projection
204
+ * (`journal-project.ts` session_parked.v1 mapping).
205
+ */
206
+ export declare const AEX_SESSION_PARKED_NAMES: readonly ["aex.session.idle", "aex.session.error", "aex.session.suspended"];
207
+ /**
208
+ * True for a managed-runtime session-park terminal (idle/error/suspended). The
209
+ * turn's work is done and the record has reached its terminal status; a stream
210
+ * consumer should stop here exactly as it would on RUN_FINISHED/RUN_ERROR.
211
+ */
212
+ export declare function isSessionParked(e: AexEvent): boolean;
198
213
  export declare function isTextMessage(e: AexEvent): boolean;
199
214
  export declare function isToolCallStart(e: AexEvent): boolean;
200
215
  export declare function isToolCallResult(e: AexEvent): boolean;
@@ -213,7 +228,13 @@ export declare function customName(e: AexEvent): string | null;
213
228
  * platform mirrors this constant in `@aexhq/shared`.
214
229
  */
215
230
  export declare const AEX_RUN_SETTLED_NAME = "aex.run.settled";
216
- /** True for the settle-consistency barrier event (post-mirror, read-consistent). */
231
+ /**
232
+ * True for the settle-consistency barrier event (post-mirror, read-consistent).
233
+ * Also true for a managed-runtime session-park terminal: the managed plane does
234
+ * NOT broadcast the `aex.run.settled` barrier, and by the time a run parks its
235
+ * record has reached a terminal status — so a `settleConsistent` stream ends at
236
+ * the park instead of hanging forever waiting for a barrier that never arrives.
237
+ */
217
238
  export declare function isRunSettled(e: AexEvent): boolean;
218
239
  export declare function isFromSource(e: AexEvent, source: AexEventSource): boolean;
219
240
  /** The channel a record rides, defaulting an absent value to `"event"`. */
@@ -212,6 +212,24 @@ export function isRunError(e) {
212
212
  export function isRunTerminal(e) {
213
213
  return e.type === "RUN_FINISHED" || e.type === "RUN_ERROR";
214
214
  }
215
+ /**
216
+ * The CUSTOM `data.name`s the MANAGED runtime emits as a run/turn's terminal.
217
+ * A managed one-shot run PARKS (session_parked.v1 → `aex.session.idle` / `.error`
218
+ * / `.suspended`) rather than writing a `session_finished` → RUN_FINISHED, so
219
+ * these — not just the AG-UI RUN_FINISHED/RUN_ERROR — are what actually ends a
220
+ * managed run's event stream. Kept in sync with the platform journal projection
221
+ * (`journal-project.ts` session_parked.v1 mapping).
222
+ */
223
+ export const AEX_SESSION_PARKED_NAMES = ["aex.session.idle", "aex.session.error", "aex.session.suspended"];
224
+ /**
225
+ * True for a managed-runtime session-park terminal (idle/error/suspended). The
226
+ * turn's work is done and the record has reached its terminal status; a stream
227
+ * consumer should stop here exactly as it would on RUN_FINISHED/RUN_ERROR.
228
+ */
229
+ export function isSessionParked(e) {
230
+ const name = customName(e);
231
+ return name !== null && AEX_SESSION_PARKED_NAMES.includes(name);
232
+ }
215
233
  export function isTextMessage(e) {
216
234
  return e.type === "TEXT_MESSAGE_CONTENT";
217
235
  }
@@ -240,9 +258,15 @@ export function customName(e) {
240
258
  * platform mirrors this constant in `@aexhq/shared`.
241
259
  */
242
260
  export const AEX_RUN_SETTLED_NAME = "aex.run.settled";
243
- /** True for the settle-consistency barrier event (post-mirror, read-consistent). */
261
+ /**
262
+ * True for the settle-consistency barrier event (post-mirror, read-consistent).
263
+ * Also true for a managed-runtime session-park terminal: the managed plane does
264
+ * NOT broadcast the `aex.run.settled` barrier, and by the time a run parks its
265
+ * record has reached a terminal status — so a `settleConsistent` stream ends at
266
+ * the park instead of hanging forever waiting for a barrier that never arrives.
267
+ */
244
268
  export function isRunSettled(e) {
245
- return customName(e) === AEX_RUN_SETTLED_NAME;
269
+ return customName(e) === AEX_RUN_SETTLED_NAME || isSessionParked(e);
246
270
  }
247
271
  export function isFromSource(e, source) {
248
272
  return e.source === source;
@@ -24,7 +24,13 @@
24
24
  * The WebSocket is injectable so the SDK/CLI use the global `WebSocket`
25
25
  * (Bun and Node 22+ ship it; no dependency) and tests drive a fake.
26
26
  */
27
- const isTerminalType = (e) => e.type === "RUN_FINISHED" || e.type === "RUN_ERROR";
27
+ import { isSessionParked } from "./event-envelope.js";
28
+ // The default terminal predicate ends the stream on the AG-UI terminals AND on
29
+ // the managed runtime's CUSTOM session-park terminal (aex.session.idle/.error/
30
+ // .suspended). A managed one-shot run parks instead of emitting RUN_FINISHED, so
31
+ // WITHOUT the session-park arm a `streamEnvelopes()` over a finished managed run
32
+ // never sees a terminal and hangs on the idle watchdog forever.
33
+ const isTerminalType = (e) => e.type === "RUN_FINISHED" || e.type === "RUN_ERROR" || isSessionParked(e);
28
34
  /**
29
35
  * Keep-alive ping the client sends; the coordinator answers it with the matching
30
36
  * pong in its WebSocket message handler. Must stay byte-identical to
@@ -1,6 +1,6 @@
1
1
  import type { HttpClient } from "./http.js";
2
2
  import type { RunUnit } from "./run-unit.js";
3
- import type { AgentsMdRecord, FileRecord, Output, OutputLink, OutputLinkOptions, OutputFileDownload, OutputFileSelector, OutputFileType, OutputQuery, OutputText, ReadOutputTextOptions, Run, RunEvent, RunListPage, RunListQuery, Session, SessionCreateRequest, SessionEvent, SessionListPage, SessionListQuery, SessionMessageAccepted, SessionMessageRequest, SessionMessagesPage, SessionMessagesQuery, SessionStateChangeAccepted, RunWebhookDelivery, SecretRecord, SecretReveal, WhoAmI } from "./runtime-types.js";
3
+ import type { AgentsMdRecord, BillingCheckoutRequest, BillingHostedSession, BillingLedgerPage, BillingLedgerQuery, BillingPortalRequest, BillingSummary, FileRecord, Output, OutputLink, OutputLinkOptions, OutputFileDownload, OutputFileSelector, OutputFileType, OutputQuery, OutputText, ReadOutputTextOptions, Run, RunEvent, RunListPage, RunListQuery, Session, SessionCreateRequest, SessionEvent, SessionListPage, SessionListQuery, SessionMessageAccepted, SessionMessageRequest, SessionMessagesPage, SessionMessagesQuery, SessionStateChangeAccepted, RunWebhookDelivery, SecretRecord, SecretReveal, WebhookSigningSecret, WhoAmI } from "./runtime-types.js";
4
4
  import type { PlatformRunSubmissionInput } from "./submission.js";
5
5
  /**
6
6
  * The single source of truth for SDK<->BFF transport. The SDK class
@@ -116,6 +116,35 @@ export declare function redeliverRunWebhook(http: HttpClient, runId: string, del
116
116
  */
117
117
  export declare function deleteWorkspaceAsset(http: HttpClient, hash: string): Promise<void>;
118
118
  export declare function whoami(http: HttpClient): Promise<WhoAmI>;
119
+ /**
120
+ * Read the workspace billing summary (`GET /api/billing`, scope `billing:read`):
121
+ * prepaid balance, current-month spend, spend cap, and plan fields. The result
122
+ * is additive-tolerant — server fields this SDK does not know yet pass through.
123
+ */
124
+ export declare function getBilling(http: HttpClient): Promise<BillingSummary>;
125
+ /**
126
+ * Create a hosted checkout session for a paid plan. Returns only the hosted
127
+ * URL; plan activation happens after checkout completes.
128
+ */
129
+ export declare function createBillingCheckout(http: HttpClient, request: BillingCheckoutRequest): Promise<BillingHostedSession>;
130
+ /**
131
+ * Create a hosted billing-portal session for the workspace customer.
132
+ * Returns only the hosted URL.
133
+ */
134
+ export declare function createBillingPortal(http: HttpClient, request?: BillingPortalRequest): Promise<BillingHostedSession>;
135
+ /**
136
+ * Read recent workspace credit-ledger rows (`GET /api/billing/ledger`, scope
137
+ * `billing:read`), newest first. `limit` is clamped server-side to [1, 100]
138
+ * (default 25); the read is not cursor-paged.
139
+ */
140
+ export declare function getBillingLedger(http: HttpClient, query?: BillingLedgerQuery): Promise<BillingLedgerPage>;
141
+ /**
142
+ * Reveal the workspace webhook signing secret (`POST /api/webhook/signing-secret`),
143
+ * CREATING one on first use. Repeat calls return the same `whsec_<base64>` value —
144
+ * the hosted API does not rotate it. POST (not GET) so a reveal is a logged action.
145
+ * Pass the returned `whsec` to `verifyAexWebhook` as `secret`.
146
+ */
147
+ export declare function getWebhookSigningSecret(http: HttpClient): Promise<WebhookSigningSecret>;
119
148
  export declare function filterOutputs(outputs: readonly Output[], query: OutputQuery): readonly Output[];
120
149
  export declare function classifyOutput(output: Pick<Output, "filename" | "contentType">): OutputFileType;
121
150
  export declare function normalizeOutputLinkExpiresIn(input?: OutputLinkOptions["expiresIn"]): number;
@@ -1,4 +1,5 @@
1
1
  import { strToU8, zipSync } from "fflate";
2
+ import { normalizeRunUnit } from "./run-unit.js";
2
3
  import { RunStateError } from "./sdk-errors.js";
3
4
  import { assertRunRecordArchivePublicSafeV1, buildRunRecordDownloadManifestV1 } from "./run-record.js";
4
5
  /**
@@ -28,7 +29,11 @@ export async function getRun(http, runId) {
28
29
  * stays for callers that only need the loose record.
29
30
  */
30
31
  export async function getRunUnit(http, runId) {
31
- return http.request(`/api/runs/${encodeURIComponent(runId)}`);
32
+ // Normalize so the RunUnit type contract holds at runtime: the managed plane
33
+ // returns a lean record and omits the aggregate collections (F25). The
34
+ // aggregates default to empty (safe array/page access) — read outputs()/events()
35
+ // for the authoritative per-run data on that plane.
36
+ return normalizeRunUnit(await http.request(`/api/runs/${encodeURIComponent(runId)}`));
32
37
  }
33
38
  /**
34
39
  * List the runs in the token's workspace, most-recent first, one page at a time.
@@ -383,6 +388,54 @@ export async function deleteWorkspaceAsset(http, hash) {
383
388
  export async function whoami(http) {
384
389
  return http.request("/api/whoami");
385
390
  }
391
+ /**
392
+ * Read the workspace billing summary (`GET /api/billing`, scope `billing:read`):
393
+ * prepaid balance, current-month spend, spend cap, and plan fields. The result
394
+ * is additive-tolerant — server fields this SDK does not know yet pass through.
395
+ */
396
+ export async function getBilling(http) {
397
+ return http.request("/api/billing");
398
+ }
399
+ /**
400
+ * Create a hosted checkout session for a paid plan. Returns only the hosted
401
+ * URL; plan activation happens after checkout completes.
402
+ */
403
+ export async function createBillingCheckout(http, request) {
404
+ return http.request("/api/billing/checkout", {
405
+ method: "POST",
406
+ body: JSON.stringify(request)
407
+ });
408
+ }
409
+ /**
410
+ * Create a hosted billing-portal session for the workspace customer.
411
+ * Returns only the hosted URL.
412
+ */
413
+ export async function createBillingPortal(http, request = {}) {
414
+ return http.request("/api/billing/portal", {
415
+ method: "POST",
416
+ body: JSON.stringify(request)
417
+ });
418
+ }
419
+ /**
420
+ * Read recent workspace credit-ledger rows (`GET /api/billing/ledger`, scope
421
+ * `billing:read`), newest first. `limit` is clamped server-side to [1, 100]
422
+ * (default 25); the read is not cursor-paged.
423
+ */
424
+ export async function getBillingLedger(http, query) {
425
+ const params = {};
426
+ if (query?.limit !== undefined)
427
+ params.limit = String(query.limit);
428
+ return http.request("/api/billing/ledger", {}, params);
429
+ }
430
+ /**
431
+ * Reveal the workspace webhook signing secret (`POST /api/webhook/signing-secret`),
432
+ * CREATING one on first use. Repeat calls return the same `whsec_<base64>` value —
433
+ * the hosted API does not rotate it. POST (not GET) so a reveal is a logged action.
434
+ * Pass the returned `whsec` to `verifyAexWebhook` as `secret`.
435
+ */
436
+ export async function getWebhookSigningSecret(http) {
437
+ return http.request("/api/webhook/signing-secret", { method: "POST" });
438
+ }
386
439
  /**
387
440
  * Download each artifact's bytes into a zip-file map keyed by
388
441
  * `<zipPrefix><relative-path>`, fetched from the `outputs`
@@ -303,7 +303,7 @@ export interface RunRequestConfig {
303
303
  readonly environment?: PlatformEnvironment;
304
304
  /** Managed runtime size preset (see {@link RuntimeSize}). */
305
305
  readonly runtimeSize?: RuntimeSize;
306
- /** Run deadline as a duration string (`"1h"`, `"30m"`); bounded [1m, 6h] server-side. */
306
+ /** Run deadline as a duration string (`"1h"`, `"30m"`); bounded [1m, 8h] server-side. */
307
307
  readonly timeout?: string;
308
308
  readonly metadata?: Readonly<Record<string, JsonValue>>;
309
309
  }
@@ -136,3 +136,15 @@ export interface RunUnit {
136
136
  * the whole detail page.
137
137
  */
138
138
  export declare function parseRunUnitSubmission(input: unknown): RunUnitSubmission;
139
+ /**
140
+ * Normalize a `GET /api/runs/:runId` payload into a RunUnit whose type contract
141
+ * holds AT RUNTIME. The managed (AWS) plane returns a LEAN record (scalars +
142
+ * costTelemetry only) and omits the aggregate collections; the RunUnit type
143
+ * declares those non-optional, so a naive cast leaves `unit.outputs` /
144
+ * `unit.events.totalCount` `undefined` and a typed consumer crashes on
145
+ * `.map()` / `.totalCount` (pre-launch edge-sweep F25). We fill the aggregates
146
+ * with their empty defaults so array/page access is always safe. NOTE: on the
147
+ * managed plane these summaries are best-effort — read `outputs()` / `events()`
148
+ * / `messages()` for the authoritative per-run data.
149
+ */
150
+ export declare function normalizeRunUnit(raw: unknown): RunUnit;
@@ -18,6 +18,7 @@
18
18
  * detail response stays bounded). The archive zip carries the bytes.
19
19
  */
20
20
  import { parseMcpServerRef } from "./run-config.js";
21
+ import { CLEANUP_STATUSES } from "./status.js";
21
22
  import { Models, parseRunModel } from "./models.js";
22
23
  import { PLATFORM_PACKAGE_ECOSYSTEMS } from "./submission.js";
23
24
  // ---------------------------------------------------------------------------
@@ -119,6 +120,60 @@ function fallbackFlat() {
119
120
  function isRecord(value) {
120
121
  return typeof value === "object" && value !== null && !Array.isArray(value);
121
122
  }
123
+ /**
124
+ * Normalize a `GET /api/runs/:runId` payload into a RunUnit whose type contract
125
+ * holds AT RUNTIME. The managed (AWS) plane returns a LEAN record (scalars +
126
+ * costTelemetry only) and omits the aggregate collections; the RunUnit type
127
+ * declares those non-optional, so a naive cast leaves `unit.outputs` /
128
+ * `unit.events.totalCount` `undefined` and a typed consumer crashes on
129
+ * `.map()` / `.totalCount` (pre-launch edge-sweep F25). We fill the aggregates
130
+ * with their empty defaults so array/page access is always safe. NOTE: on the
131
+ * managed plane these summaries are best-effort — read `outputs()` / `events()`
132
+ * / `messages()` for the authoritative per-run data.
133
+ */
134
+ export function normalizeRunUnit(raw) {
135
+ const r = isRecord(raw) ? raw : {};
136
+ const eventsRaw = isRecord(r.events) ? r.events : {};
137
+ const str = (v) => (typeof v === "string" ? v : undefined);
138
+ const arr = (v) => (Array.isArray(v) ? v : []);
139
+ return {
140
+ id: str(r.id) ?? "",
141
+ workspaceId: str(r.workspaceId) ?? "",
142
+ status: str(r.status) ?? "unknown",
143
+ ...(str(r.lifecyclePhase) ? { lifecyclePhase: r.lifecyclePhase } : {}),
144
+ cleanupStatus: CLEANUP_STATUSES.includes(r.cleanupStatus)
145
+ ? r.cleanupStatus
146
+ : "not_started",
147
+ createdAt: str(r.createdAt) ?? "",
148
+ updatedAt: str(r.updatedAt) ?? "",
149
+ ...(str(r.startedAt) ? { startedAt: r.startedAt } : {}),
150
+ ...(str(r.terminalAt) ? { terminalAt: r.terminalAt } : {}),
151
+ ...(str(r.deletedAt) ? { deletedAt: r.deletedAt } : {}),
152
+ attemptCount: typeof r.attemptCount === "number"
153
+ ? r.attemptCount
154
+ : Array.isArray(r.attempts)
155
+ ? r.attempts.length
156
+ : 0,
157
+ submission: parseRunUnitSubmission(r.submission),
158
+ ...(isRecord(r.capsSnapshot) ? { capsSnapshot: r.capsSnapshot } : {}),
159
+ attempts: arr(r.attempts),
160
+ events: {
161
+ entries: arr(eventsRaw.entries),
162
+ totalCount: typeof eventsRaw.totalCount === "number" ? eventsRaw.totalCount : 0,
163
+ truncated: eventsRaw.truncated === true,
164
+ ...(str(eventsRaw.nextCursor) ? { nextCursor: eventsRaw.nextCursor } : {})
165
+ },
166
+ rawEventPages: arr(r.rawEventPages),
167
+ outputs: arr(r.outputs),
168
+ outputCaptureFailures: arr(r.outputCaptureFailures),
169
+ ...(isRecord(r.costTelemetry)
170
+ ? { costTelemetry: r.costTelemetry }
171
+ : {}),
172
+ ...(isRecord(r.runtimeManifest)
173
+ ? { runtimeManifest: r.runtimeManifest }
174
+ : {})
175
+ };
176
+ }
122
177
  function coerceRunUnitModel(value) {
123
178
  if (typeof value !== "string")
124
179
  return Models.CLAUDE_HAIKU_4_5;
@@ -65,9 +65,9 @@ export declare function runtimeResources(size: RuntimeSize): RuntimeResources;
65
65
  * consumers apply {@link DEFAULT_RUNTIME_SIZE}.
66
66
  */
67
67
  export declare function parseRuntimeSize(input: unknown): RuntimeSize | undefined;
68
- /** Default run deadline when `timeout` is omitted (1 hour). */
68
+ /** Default run deadline when `timeout` is omitted (8 hours). */
69
69
  export declare const DEFAULT_RUN_TIMEOUT_MS: number;
70
- /** Hard ceiling on a run deadline (6 hours). */
70
+ /** Hard ceiling on a run deadline (8 hours). */
71
71
  export declare const MAX_RUN_TIMEOUT_MS: number;
72
72
  /** Floor on a run deadline (1 minute). */
73
73
  export declare const MIN_RUN_TIMEOUT_MS: number;
@@ -54,10 +54,10 @@ export function parseRuntimeSize(input) {
54
54
  // ===========================================================================
55
55
  // Run timeout
56
56
  // ===========================================================================
57
- /** Default run deadline when `timeout` is omitted (1 hour). */
58
- export const DEFAULT_RUN_TIMEOUT_MS = 60 * 60 * 1000;
59
- /** Hard ceiling on a run deadline (6 hours). */
60
- export const MAX_RUN_TIMEOUT_MS = 6 * 60 * 60 * 1000;
57
+ /** Default run deadline when `timeout` is omitted (8 hours). */
58
+ export const DEFAULT_RUN_TIMEOUT_MS = 8 * 60 * 60 * 1000;
59
+ /** Hard ceiling on a run deadline (8 hours). */
60
+ export const MAX_RUN_TIMEOUT_MS = 8 * 60 * 60 * 1000;
61
61
  /** Floor on a run deadline (1 minute). */
62
62
  export const MIN_RUN_TIMEOUT_MS = 60 * 1000;
63
63
  const DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/;
@@ -95,7 +95,7 @@ export function parseRunTimeout(input) {
95
95
  throw new Error(`timeout must be at least ${MIN_RUN_TIMEOUT_MS}ms (1m); got ${ms}ms`);
96
96
  }
97
97
  if (ms > MAX_RUN_TIMEOUT_MS) {
98
- throw new Error(`timeout must be at most ${MAX_RUN_TIMEOUT_MS}ms (6h); got ${ms}ms`);
98
+ throw new Error(`timeout must be at most ${MAX_RUN_TIMEOUT_MS}ms (8h); got ${ms}ms`);
99
99
  }
100
100
  return ms;
101
101
  }
@@ -398,6 +398,29 @@ export interface WhoAmI {
398
398
  */
399
399
  readonly maxRunDurationMs?: number | null;
400
400
  };
401
+ /**
402
+ * ADDITIVE effective per-workspace limits returned by `GET /whoami` on
403
+ * current platform deployments — everything a caller needs to anticipate a
404
+ * `429` / `402` submit rejection before hitting it. Every value is the same
405
+ * one the platform's admission gates enforce. Optional: older deployments
406
+ * omit the field entirely.
407
+ */
408
+ readonly limits?: {
409
+ /** Effective live-run concurrency cap. One more live run past it fails with `429 workspace_concurrency_exceeded`. */
410
+ readonly maxConcurrentRuns: number;
411
+ /** Effective submit-velocity cap per minute; `0` = unlimited (disabled). Past it: `429 workspace_submit_rate_exceeded`. */
412
+ readonly submitRatePerMinute: number;
413
+ /** Effective monthly spend cap in USD; `0` = unlimited. Once `monthSpendUsd` reaches it: `402 workspace_spend_cap_exceeded`. */
414
+ readonly spendCapUsd: number;
415
+ /** Accrued spend in the current UTC calendar month — the value the spend gate compares. */
416
+ readonly monthSpendUsd: number;
417
+ /** Prepaid balance read-model — the value the balance gate compares. */
418
+ readonly balanceUsd: number;
419
+ /** Effective (payment-method-aware) submit floor: submits fail with `402 insufficient_balance` once `balanceUsd` is at or below it. */
420
+ readonly balanceGraceFloorUsd: number;
421
+ /** `"active"` means a bounded card overdraft is already folded into `balanceGraceFloorUsd`. */
422
+ readonly paymentMethodStatus: "none" | "active";
423
+ };
401
424
  readonly [key: string]: unknown;
402
425
  }
403
426
  /**
@@ -505,3 +528,78 @@ export interface SecretReveal {
505
528
  readonly name: string;
506
529
  readonly value: string;
507
530
  }
531
+ /**
532
+ * Customer-facing billing summary — `GET /api/billing` (scope `billing:read`).
533
+ * All money fields are USD numbers. The index signature keeps the shape tolerant
534
+ * of ADDITIVE server fields (e.g. a deployment newer than this SDK reporting
535
+ * extra plan attributes) — unknown keys are preserved, never rejected.
536
+ */
537
+ export interface BillingSummary {
538
+ /** Prepaid balance (authoritative ledger sum). */
539
+ readonly balanceUsd: number;
540
+ /** Accrued spend for the current calendar month. */
541
+ readonly monthSpendUsd: number;
542
+ /** Monthly spend cap enforced on new runs. */
543
+ readonly spendCapUsd: number;
544
+ readonly planKey: string;
545
+ readonly subscriptionStatus: string;
546
+ /** `"active"` once a payment method is bound; older deployments omit it. */
547
+ readonly paymentMethodStatus?: string;
548
+ readonly [key: string]: unknown;
549
+ }
550
+ /** Self-serve paid plans exposed through hosted checkout. */
551
+ export type BillingCheckoutPlanKey = "pro" | "team";
552
+ export interface BillingCheckoutRequest {
553
+ readonly planKey: BillingCheckoutPlanKey;
554
+ /** Optional return URL after successful hosted checkout. */
555
+ readonly successUrl?: string;
556
+ /** Optional return URL after checkout cancellation. */
557
+ readonly cancelUrl?: string;
558
+ /** Optional caller-stable key so a retry does not create a second hosted session. */
559
+ readonly idempotencyKey?: string;
560
+ }
561
+ export interface BillingPortalRequest {
562
+ /** Optional return URL after leaving the hosted billing portal. */
563
+ readonly returnUrl?: string;
564
+ }
565
+ /** Hosted checkout/portal session. The client should open `url`. */
566
+ export interface BillingHostedSession {
567
+ readonly url: string;
568
+ readonly [key: string]: unknown;
569
+ }
570
+ /**
571
+ * One row of the workspace credit ledger as returned by
572
+ * `GET /api/billing/ledger` (newest first). `amountUsd` is signed: top-ups are
573
+ * positive, run charges negative. Tolerant of additive server fields.
574
+ */
575
+ export interface BillingLedgerEntry {
576
+ readonly id: string;
577
+ /** e.g. `top_up`, `run_charge`. Open server vocabulary. */
578
+ readonly entryType: string;
579
+ readonly amountUsd: number;
580
+ readonly currency: string;
581
+ /** The run this entry charges, `null` for non-run entries. */
582
+ readonly runId?: string | null;
583
+ readonly description?: string | null;
584
+ readonly createdBy?: string;
585
+ readonly createdAt: string;
586
+ readonly [key: string]: unknown;
587
+ }
588
+ /** Query for the billing ledger read. `limit` is clamped server-side to [1, 100] (default 25). */
589
+ export interface BillingLedgerQuery {
590
+ readonly limit?: number;
591
+ }
592
+ /** One page of recent ledger rows (newest first). Not cursor-paged — `limit` bounds the read. */
593
+ export interface BillingLedgerPage {
594
+ readonly entries: readonly BillingLedgerEntry[];
595
+ }
596
+ /**
597
+ * The workspace webhook signing secret reveal — `POST /api/webhook/signing-secret`.
598
+ * `whsec` is the Standard-Webhooks style `whsec_<base64>` string that
599
+ * `verifyAexWebhook` accepts as `secret`. The endpoint reveals the current
600
+ * secret, CREATING one on first use; it does not rotate (a repeat call returns
601
+ * the same value). POST (not GET) so every reveal is a logged action.
602
+ */
603
+ export interface WebhookSigningSecret {
604
+ readonly whsec: string;
605
+ }
@@ -329,8 +329,8 @@ export interface PlatformRunSubmissionRequest {
329
329
  readonly runtimeSize?: RuntimeSize;
330
330
  /**
331
331
  * Run deadline in milliseconds, normalised by the parser from the wire
332
- * `timeout` duration string (bounded to [1m, 6h]). Absent ⇒
333
- * {@link DEFAULT_RUN_TIMEOUT_MS} (1h). Applies to the managed runner's
332
+ * `timeout` duration string (bounded to [1m, 8h]). Absent ⇒
333
+ * {@link DEFAULT_RUN_TIMEOUT_MS} (8h). Applies to the managed runner's
334
334
  * terminal wait window and self-kill deadline.
335
335
  */
336
336
  readonly timeoutMs?: number;
@@ -412,8 +412,8 @@ export type PlatformRunSubmissionInput = Omit<PlatformRunSubmissionRequest, "wor
412
412
  readonly provider?: RunProvider;
413
413
  /**
414
414
  * Run deadline as a human duration string (`"1h"`, `"90m"`, `"30s"`).
415
- * Parsed + bounded to [1m, 6h] server-side into
416
- * {@link PlatformRunSubmissionRequest.timeoutMs}. Absent ⇒ 1h default.
415
+ * Parsed + bounded to [1m, 8h] server-side into
416
+ * {@link PlatformRunSubmissionRequest.timeoutMs}. Absent ⇒ 8h default.
417
417
  */
418
418
  readonly timeout?: string;
419
419
  };