@aexhq/sdk 0.38.1 → 0.40.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.
- package/README.md +23 -17
- package/dist/_contracts/api-key.d.ts +49 -0
- package/dist/_contracts/api-key.js +87 -0
- package/dist/_contracts/bundle-manifest.d.ts +86 -0
- package/dist/_contracts/bundle-manifest.js +157 -0
- package/dist/_contracts/error-codes.d.ts +26 -0
- package/dist/_contracts/error-codes.js +79 -0
- package/dist/_contracts/error-factory.d.ts +32 -0
- package/dist/_contracts/error-factory.js +142 -0
- package/dist/_contracts/event-envelope.d.ts +33 -10
- package/dist/_contracts/event-envelope.js +46 -10
- package/dist/_contracts/event-view.d.ts +123 -0
- package/dist/_contracts/event-view.js +120 -0
- package/dist/_contracts/http.js +12 -3
- package/dist/_contracts/index.d.ts +8 -4
- package/dist/_contracts/index.js +11 -7
- package/dist/_contracts/models.d.ts +15 -0
- package/dist/_contracts/models.js +33 -0
- package/dist/_contracts/operations.d.ts +70 -2
- package/dist/_contracts/operations.js +143 -7
- package/dist/_contracts/run-config.d.ts +35 -10
- package/dist/_contracts/run-config.js +17 -5
- package/dist/_contracts/run-record.d.ts +3 -2
- package/dist/_contracts/runtime-types.d.ts +148 -36
- package/dist/_contracts/runtime-types.js +33 -1
- package/dist/_contracts/sdk-errors.d.ts +61 -2
- package/dist/_contracts/sdk-errors.js +83 -3
- package/dist/_contracts/sdk-secrets.js +31 -6
- package/dist/_contracts/stable.d.ts +14 -0
- package/dist/_contracts/stable.js +14 -0
- package/dist/_contracts/status.d.ts +28 -1
- package/dist/_contracts/status.js +48 -3
- package/dist/_contracts/submission.d.ts +157 -14
- package/dist/_contracts/submission.js +319 -55
- package/dist/_contracts/suggest.d.ts +15 -0
- package/dist/_contracts/suggest.js +54 -0
- package/dist/asset-upload.d.ts +27 -1
- package/dist/asset-upload.js +219 -4
- package/dist/asset-upload.js.map +1 -1
- package/dist/bundle.d.ts +23 -14
- package/dist/bundle.js +39 -20
- package/dist/bundle.js.map +1 -1
- package/dist/canonical-zip.d.ts +68 -0
- package/dist/canonical-zip.js +307 -0
- package/dist/canonical-zip.js.map +1 -0
- package/dist/cli.mjs +1927 -326
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +273 -70
- package/dist/client.js +885 -335
- package/dist/client.js.map +1 -1
- package/dist/fetch-archive.js +14 -14
- package/dist/fetch-archive.js.map +1 -1
- package/dist/file.d.ts +33 -6
- package/dist/file.js +120 -54
- package/dist/file.js.map +1 -1
- package/dist/index.d.ts +23 -14
- package/dist/index.js +31 -14
- package/dist/index.js.map +1 -1
- package/dist/node-fs.d.ts +26 -9
- package/dist/node-fs.js +13 -38
- package/dist/node-fs.js.map +1 -1
- package/dist/node-walk.d.ts +69 -0
- package/dist/node-walk.js +146 -0
- package/dist/node-walk.js.map +1 -0
- package/dist/retry.d.ts +9 -13
- package/dist/retry.js +18 -17
- package/dist/retry.js.map +1 -1
- package/dist/skill.d.ts +151 -0
- package/dist/skill.js +298 -0
- package/dist/skill.js.map +1 -0
- package/dist/tool.d.ts +14 -2
- package/dist/tool.js +33 -7
- package/dist/tool.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/authentication.md +29 -5
- package/docs/billing.md +6 -0
- package/docs/concepts/agent-tools.md +11 -0
- package/docs/concepts/composition.md +3 -3
- package/docs/defaults.md +1 -0
- package/docs/errors.md +64 -4
- package/docs/events.md +84 -49
- package/docs/limits-and-quotas.md +24 -0
- package/docs/mcp.md +3 -2
- package/docs/networking.md +7 -1
- package/docs/outputs.md +36 -7
- package/docs/provider-runtime-capabilities.md +1 -1
- package/docs/quickstart.md +17 -7
- package/docs/run-config.md +3 -3
- package/docs/secrets.md +14 -0
- package/docs/skills.md +74 -44
- package/docs/vision-skills.md +3 -3
- package/examples/feature-tour.ts +4 -6
- package/examples/spike-settle-latency.ts +125 -0
- package/package.json +4 -3
- package/dist/_contracts/event-guards.d.ts +0 -67
- package/dist/_contracts/event-guards.js +0 -36
- package/dist/skill-tool.d.ts +0 -102
- package/dist/skill-tool.js +0 -190
- package/dist/skill-tool.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { SessionStatus } from "./status.js";
|
|
1
|
+
import type { SessionStatus, SessionTerminalOutcome } from "./status.js";
|
|
2
|
+
import type { RunCostProviderUsage } from "./run-cost.js";
|
|
2
3
|
import type { PlatformRunSubmissionInput, PlatformSubmission } from "./submission.js";
|
|
3
4
|
/**
|
|
4
5
|
* Loose record describing a run as the dashboard BFF returns it. Concrete
|
|
@@ -37,6 +38,12 @@ export interface Run {
|
|
|
37
38
|
*/
|
|
38
39
|
readonly usage?: UsageSummary;
|
|
39
40
|
readonly costTelemetry?: import("./run-cost.js").RunCostTelemetry;
|
|
41
|
+
/**
|
|
42
|
+
* The authoritative terminal OUTCOME of the run's last turn — the settle-
|
|
43
|
+
* written outcome (`succeeded`/`failed`/`timed_out`/`cancelled`), distinct
|
|
44
|
+
* from the resumable lifecycle {@link status}. Absent until the run settles.
|
|
45
|
+
*/
|
|
46
|
+
readonly lastTurnOutcome?: SessionTerminalOutcome;
|
|
40
47
|
readonly runtimeManifest?: import("./runtime-manifest.js").RuntimeManifest;
|
|
41
48
|
readonly [key: string]: unknown;
|
|
42
49
|
}
|
|
@@ -68,9 +75,17 @@ export interface Session {
|
|
|
68
75
|
readonly retainedStorageBytes?: number;
|
|
69
76
|
readonly usage?: UsageSummary;
|
|
70
77
|
readonly costUsd?: number;
|
|
78
|
+
/**
|
|
79
|
+
* The authoritative terminal OUTCOME of the session's last turn — the
|
|
80
|
+
* settle-written outcome (`succeeded`/`failed`/`timed_out`/`cancelled`),
|
|
81
|
+
* distinct from the resumable lifecycle {@link status} (`idle`/`suspended`).
|
|
82
|
+
* A cancelled turn reads `cancelled` here even though the session may park
|
|
83
|
+
* `idle`; absent until the turn settles.
|
|
84
|
+
*/
|
|
85
|
+
readonly lastTurnOutcome?: SessionTerminalOutcome;
|
|
71
86
|
readonly errorMessage?: string | null;
|
|
72
87
|
/**
|
|
73
|
-
* Settle-written failure taxonomy for
|
|
88
|
+
* Settle-written failure taxonomy for a `failed` session (e.g.
|
|
74
89
|
* `provider-permanent`, `budget_exhausted`) — the class a caller can branch
|
|
75
90
|
* on, complementing the human-readable `errorMessage`.
|
|
76
91
|
*/
|
|
@@ -158,6 +173,77 @@ export interface UsageSummary {
|
|
|
158
173
|
readonly cacheCreationInputTokens?: number;
|
|
159
174
|
readonly totalTokens?: number;
|
|
160
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Project a {@link UsageSummary} from the settle-written
|
|
178
|
+
* {@link RunCostProviderUsage} entries — the SINGLE server source of token
|
|
179
|
+
* usage ({@link Run.costTelemetry}`.providerUsage`). Sums each field across all
|
|
180
|
+
* provider entries; a field is present only when at least one entry carried it.
|
|
181
|
+
* This retires the dead `session.usage` / `aex.usage`-event usage path: the SDK
|
|
182
|
+
* derives usage from cost telemetry, never re-reads a dual-written top-level
|
|
183
|
+
* `usage`. Pure.
|
|
184
|
+
*/
|
|
185
|
+
export declare function usageFromProviderUsage(providerUsage: readonly RunCostProviderUsage[] | undefined): UsageSummary;
|
|
186
|
+
/**
|
|
187
|
+
* The unified SETTLED-RESULT contract shared by `run()` and `done()`. Because
|
|
188
|
+
* both await the settle commit by default, these fields are ALWAYS present at a
|
|
189
|
+
* terminal read — they are NON-optional, so a code path that forgets to
|
|
190
|
+
* populate `costUsd`/`usage`/the terminal `status` fails to typecheck. The SDK
|
|
191
|
+
* `RunResult`/`SessionTurnResult` extend this one shape so `done()` == `run()`.
|
|
192
|
+
*
|
|
193
|
+
* `costUsd` is the AEX showback estimate in USD (>= 0) and EXCLUDES the
|
|
194
|
+
* customer's BYOK provider spend — price BYOK from `usage` tokens.
|
|
195
|
+
*/
|
|
196
|
+
export interface SettledResult {
|
|
197
|
+
/** The terminal outcome (never a bare resumable `idle`). */
|
|
198
|
+
readonly status: SessionTerminalOutcome;
|
|
199
|
+
/** `succeeded` ⇒ true; `failed`/`timed_out`/`cancelled` ⇒ false. */
|
|
200
|
+
readonly ok: boolean;
|
|
201
|
+
/** AEX showback estimate (USD, >= 0). Excludes BYOK provider spend. */
|
|
202
|
+
readonly costUsd: number;
|
|
203
|
+
/** Aggregate token usage, derived from `costTelemetry.providerUsage`. */
|
|
204
|
+
readonly usage: UsageSummary;
|
|
205
|
+
/** Terminal failure message (from the terminal `RUN_ERROR` event) when `!ok`. */
|
|
206
|
+
readonly error?: string;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The reason a schema-constrained decode did not yield a value:
|
|
210
|
+
* - `schema_violation` — the model output failed schema validation.
|
|
211
|
+
* - `uncertain` — the model signalled low confidence / declined to commit.
|
|
212
|
+
* - `refused` — the model refused the request.
|
|
213
|
+
*/
|
|
214
|
+
export type RunRefusalReason = "schema_violation" | "uncertain" | "refused";
|
|
215
|
+
/**
|
|
216
|
+
* The typed outcome of a `run<T>({ responseFormat })`: EITHER a schema-valid
|
|
217
|
+
* decoded value OR a typed refusal — there is no untyped path that silently
|
|
218
|
+
* yields a hallucinated object. `T` is the decoded value type.
|
|
219
|
+
*/
|
|
220
|
+
export type RunOutcome<T = unknown> = {
|
|
221
|
+
readonly kind: "decoded";
|
|
222
|
+
readonly value: T;
|
|
223
|
+
} | {
|
|
224
|
+
readonly kind: "refused";
|
|
225
|
+
readonly reason: RunRefusalReason;
|
|
226
|
+
readonly detail?: string;
|
|
227
|
+
};
|
|
228
|
+
/** One item's settled result inside a {@link BatchResult}. */
|
|
229
|
+
export interface BatchItemResult<T = unknown> extends SettledResult {
|
|
230
|
+
readonly runId: string;
|
|
231
|
+
/** Present only for a `responseFormat`-decoded item. */
|
|
232
|
+
readonly outcome?: RunOutcome<T>;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The result of `aex.batch(items, …)`: every item's settled result PLUS a real
|
|
236
|
+
* rollup. The rollup is honest because `run()` awaits settle, so every item's
|
|
237
|
+
* `costUsd`/`usage` is populated — a missing cost is a typed absent, not a
|
|
238
|
+
* silent `$0`.
|
|
239
|
+
*/
|
|
240
|
+
export interface BatchResult<T = unknown> {
|
|
241
|
+
readonly results: readonly BatchItemResult<T>[];
|
|
242
|
+
readonly totalCostUsd: number;
|
|
243
|
+
readonly totalUsage: UsageSummary;
|
|
244
|
+
readonly okCount: number;
|
|
245
|
+
readonly failed: readonly BatchItemResult<T>[];
|
|
246
|
+
}
|
|
161
247
|
/**
|
|
162
248
|
* Filters for {@link import("./operations.js").listRuns} / the CLI's `aex runs`.
|
|
163
249
|
* Every field is optional; omitting all of them lists the most recent runs in the
|
|
@@ -192,6 +278,37 @@ export interface RunListPage {
|
|
|
192
278
|
readonly runs: readonly RunSummary[];
|
|
193
279
|
readonly nextCursor?: string;
|
|
194
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* The minimal capability a value must carry to be RESOLVABLE through the run
|
|
283
|
+
* facade — `getRun` / `listRunEvents` / `listOutputs` all key on this `id`.
|
|
284
|
+
* Encodes the "handed ⇒ resolvable" invariant at the type level: anything the
|
|
285
|
+
* platform hands you as a run reference exposes a resolvable `id`, so a run can
|
|
286
|
+
* never be surfaced as a bare unresolvable string.
|
|
287
|
+
*/
|
|
288
|
+
export interface ResolvableRunRef {
|
|
289
|
+
readonly id: string;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* A subagent CHILD run, enumerated under its parent via `GET /runs/:id/children`.
|
|
293
|
+
* A first-class, lineage-discoverable run reference: it {@link ResolvableRunRef}
|
|
294
|
+
* (its `id` resolves through the run facade — events/outputs/getRun), carries the
|
|
295
|
+
* lineage (`parentRunId`/`depth`) and the terminal outcome/cost, so a child is
|
|
296
|
+
* observable exactly like a top-level run.
|
|
297
|
+
*/
|
|
298
|
+
export interface ChildRunRef extends ResolvableRunRef {
|
|
299
|
+
/** The parent run this child was spawned by. */
|
|
300
|
+
readonly parentRunId: string;
|
|
301
|
+
/** The child's run status. */
|
|
302
|
+
readonly status: string;
|
|
303
|
+
/** Subagent nesting depth (1 = direct child of the top-level run). */
|
|
304
|
+
readonly depth?: number;
|
|
305
|
+
/** Settled AEX showback estimate (USD) for the child, when present. */
|
|
306
|
+
readonly costUsd?: number;
|
|
307
|
+
readonly createdAt?: string;
|
|
308
|
+
readonly terminalAt?: string | null;
|
|
309
|
+
/** The child's terminal outcome once settled. */
|
|
310
|
+
readonly lastTurnOutcome?: SessionTerminalOutcome;
|
|
311
|
+
}
|
|
195
312
|
/**
|
|
196
313
|
* Cross-run output search query (`Aex.sessions.searchOutputs`). Restrict to a
|
|
197
314
|
* corpus with `runIds`; filter by filename substring / extension / content type.
|
|
@@ -202,8 +319,13 @@ export interface RunListPage {
|
|
|
202
319
|
export interface OutputSearchQuery {
|
|
203
320
|
/** Restrict the search to these runs (the chat corpus allow-list). */
|
|
204
321
|
readonly runIds?: readonly string[];
|
|
205
|
-
/**
|
|
206
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Filename match. A string is a case-insensitive SUBSTRING match; a RegExp is
|
|
324
|
+
* tested as given. Unified with {@link OutputQuery.filename} (`string | RegExp`)
|
|
325
|
+
* so the same value works on `find()` and `search()` — feed it through
|
|
326
|
+
* {@link import("./operations.js").toFilenameMatcher} rather than assuming a string.
|
|
327
|
+
*/
|
|
328
|
+
readonly filename?: string | RegExp;
|
|
207
329
|
/** File extension, with or without a leading dot. Case-insensitive. */
|
|
208
330
|
readonly extension?: string;
|
|
209
331
|
/** Exact content type or a prefix wildcard such as `image/*`. */
|
|
@@ -223,38 +345,6 @@ export interface OutputSearchHit {
|
|
|
223
345
|
export interface OutputSearchPage {
|
|
224
346
|
readonly hits: readonly OutputSearchHit[];
|
|
225
347
|
}
|
|
226
|
-
/**
|
|
227
|
-
* A run event as recorded by the dashboard. Includes the `type` field
|
|
228
|
-
* that the `is*Event` type guards narrow on plus any provider payload.
|
|
229
|
-
*
|
|
230
|
-
* The unified-stream discriminators (`channel`, `source`, `sourceSeq`,
|
|
231
|
-
* `emittedAt`, `receivedAt`, `level`) carry through from the coordinator
|
|
232
|
-
* envelope (see `event-envelope.ts` —
|
|
233
|
-
* {@link import("./event-envelope.js").AexEvent}) so SDK/CLI consumers can
|
|
234
|
-
* split/filter the one stream by channel or source. All are OPTIONAL: archived
|
|
235
|
-
* events from before the unification (and any producer that omits an ordering
|
|
236
|
-
* attribute) lack them, an absent `channel` means `"event"` (see `channelOf`),
|
|
237
|
-
* and `level` is present only on `log`-channel records.
|
|
238
|
-
*/
|
|
239
|
-
export interface RunEvent {
|
|
240
|
-
readonly id: string;
|
|
241
|
-
readonly type: string;
|
|
242
|
-
readonly runId?: string;
|
|
243
|
-
readonly recordedAt?: string;
|
|
244
|
-
/** Which sub-stream this record rides — `"event"` (typed) or `"log"`. Absent ⇒ `"event"`. */
|
|
245
|
-
readonly channel?: import("./event-envelope.js").AexEventChannel;
|
|
246
|
-
/** Coarse origin classifier. See {@link AexEventSource}. */
|
|
247
|
-
readonly source?: import("./event-envelope.js").AexEventSource;
|
|
248
|
-
/** Per-source monotonic counter assigned at the source (carried, not re-ordered). */
|
|
249
|
-
readonly sourceSeq?: number;
|
|
250
|
-
/** Source wall-clock ms at emit (carried for a best-effort client time view). */
|
|
251
|
-
readonly emittedAt?: number;
|
|
252
|
-
/** The DO's authoritative receive-time (wall-clock ms) stamped at ingest, companion to `seq`. */
|
|
253
|
-
readonly receivedAt?: number;
|
|
254
|
-
/** Log severity, first-class on a `channel: "log"` record ("info" | "warn" | "error"). */
|
|
255
|
-
readonly level?: import("./event-envelope.js").AexLogLevel;
|
|
256
|
-
readonly [key: string]: unknown;
|
|
257
|
-
}
|
|
258
348
|
/** Status of a per-run webhook delivery. Terminal: delivered/exhausted/invalid. */
|
|
259
349
|
export type RunWebhookDeliveryStatus = "pending" | "delivering" | "retrying" | "delivered" | "exhausted" | "invalid";
|
|
260
350
|
/**
|
|
@@ -508,6 +598,28 @@ export interface FileRecord {
|
|
|
508
598
|
readonly finalizedAt?: string | null;
|
|
509
599
|
readonly [key: string]: unknown;
|
|
510
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Wire-level metadata record for a workspace skill as returned by the BFF.
|
|
603
|
+
*
|
|
604
|
+
* Workspace skills are named, mutable, by-name-bound bundles: `skill.upload()`
|
|
605
|
+
* upserts one under a stable `name`; a run references it by that name and the
|
|
606
|
+
* platform resolves it to the CURRENT bytes at submit time. This record is
|
|
607
|
+
* METADATA ONLY — the bytes live in the content-addressed asset store keyed by
|
|
608
|
+
* `contentHash`. `version` bumps each time the bytes change under the name.
|
|
609
|
+
*/
|
|
610
|
+
export interface SkillRecord {
|
|
611
|
+
readonly id?: string;
|
|
612
|
+
readonly kind?: "skill";
|
|
613
|
+
readonly name: string;
|
|
614
|
+
readonly contentHash: string;
|
|
615
|
+
readonly description: string;
|
|
616
|
+
readonly sizeBytes?: number | null;
|
|
617
|
+
readonly version?: number;
|
|
618
|
+
readonly createdAt?: string;
|
|
619
|
+
readonly updatedAt?: string;
|
|
620
|
+
readonly deletedAt?: string | null;
|
|
621
|
+
readonly [key: string]: unknown;
|
|
622
|
+
}
|
|
511
623
|
/**
|
|
512
624
|
* Wire-level record for a workspace secret as returned by the BFF.
|
|
513
625
|
*
|
|
@@ -1,2 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Project a {@link UsageSummary} from the settle-written
|
|
3
|
+
* {@link RunCostProviderUsage} entries — the SINGLE server source of token
|
|
4
|
+
* usage ({@link Run.costTelemetry}`.providerUsage`). Sums each field across all
|
|
5
|
+
* provider entries; a field is present only when at least one entry carried it.
|
|
6
|
+
* This retires the dead `session.usage` / `aex.usage`-event usage path: the SDK
|
|
7
|
+
* derives usage from cost telemetry, never re-reads a dual-written top-level
|
|
8
|
+
* `usage`. Pure.
|
|
9
|
+
*/
|
|
10
|
+
export function usageFromProviderUsage(providerUsage) {
|
|
11
|
+
if (!providerUsage || providerUsage.length === 0)
|
|
12
|
+
return {};
|
|
13
|
+
let inputTokens;
|
|
14
|
+
let outputTokens;
|
|
15
|
+
let cacheReadInputTokens;
|
|
16
|
+
let cacheCreationInputTokens;
|
|
17
|
+
let totalTokens;
|
|
18
|
+
const add = (acc, value) => value === undefined ? acc : (acc ?? 0) + value;
|
|
19
|
+
for (const entry of providerUsage) {
|
|
20
|
+
inputTokens = add(inputTokens, entry.inputTokens);
|
|
21
|
+
outputTokens = add(outputTokens, entry.outputTokens);
|
|
22
|
+
cacheReadInputTokens = add(cacheReadInputTokens, entry.cacheReadInputTokens);
|
|
23
|
+
cacheCreationInputTokens = add(cacheCreationInputTokens, entry.cacheCreationInputTokens);
|
|
24
|
+
totalTokens = add(totalTokens, entry.totalTokens);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
28
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
29
|
+
...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
|
|
30
|
+
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
31
|
+
...(totalTokens !== undefined ? { totalTokens } : {})
|
|
32
|
+
};
|
|
33
|
+
}
|
|
2
34
|
//# sourceMappingURL=runtime-types.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AexApiErrorCode } from "./error-codes.js";
|
|
1
2
|
export type AexErrorCode = "RUN_CONFIG_INVALID" | "CREDENTIAL_INVALID" | "PROVIDER_ERROR" | "RUN_STATE_ERROR" | "CLEANUP_ERROR" | "RUNTIME_UNSUPPORTED" | "API_ERROR" | "NETWORK_ERROR";
|
|
2
3
|
export declare class AexError extends Error {
|
|
3
4
|
readonly code: AexErrorCode;
|
|
@@ -27,13 +28,69 @@ export declare class CleanupError extends AexError {
|
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Thrown by SDK and CLI operations when the dashboard BFF returns a non-2xx
|
|
30
|
-
* response. Carries the HTTP status
|
|
31
|
+
* response. Carries the HTTP status, the redacted parsed body, the server's
|
|
32
|
+
* STABLE {@link AexApiErrorCode} (when present), and a `requestId` for support.
|
|
33
|
+
* Construct via {@link import("./error-factory.js").apiErrorFromResponse} — the
|
|
34
|
+
* single wire→exception mapping — which dispatches to a typed subclass
|
|
35
|
+
* ({@link AexAuthError} / {@link AexIdempotencyConflictError} /
|
|
36
|
+
* {@link AexNotFoundError} / {@link AexRateLimitError}).
|
|
31
37
|
*/
|
|
32
38
|
export declare class AexApiError extends AexError {
|
|
33
39
|
readonly status: number;
|
|
34
40
|
readonly body: unknown;
|
|
35
|
-
|
|
41
|
+
/** The server's stable error code, when the body carried a known one. */
|
|
42
|
+
readonly apiCode: AexApiErrorCode | undefined;
|
|
43
|
+
/** Request id (body `requestId` or a response header) for support correlation. */
|
|
44
|
+
readonly requestId: string | undefined;
|
|
45
|
+
constructor(status: number, message: string, body: unknown, options?: {
|
|
46
|
+
readonly apiCode?: AexApiErrorCode | undefined;
|
|
47
|
+
readonly requestId?: string | undefined;
|
|
48
|
+
readonly cause?: unknown;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Shared construction shape for the typed {@link AexApiError} subclasses. */
|
|
52
|
+
export interface AexApiErrorInit {
|
|
53
|
+
readonly status: number;
|
|
54
|
+
readonly message: string;
|
|
55
|
+
readonly body: unknown;
|
|
56
|
+
readonly apiCode?: AexApiErrorCode | undefined;
|
|
57
|
+
readonly requestId?: string | undefined;
|
|
58
|
+
readonly cause?: unknown;
|
|
59
|
+
}
|
|
60
|
+
/** 401/403 auth failure (token invalid/revoked/expired, forbidden, insufficient scope). */
|
|
61
|
+
export declare class AexAuthError extends AexApiError {
|
|
62
|
+
/** The scope the endpoint required, when the server named it (insufficient_scope). */
|
|
63
|
+
readonly requiredScope: string | undefined;
|
|
64
|
+
constructor(init: AexApiErrorInit & {
|
|
65
|
+
readonly requiredScope?: string | undefined;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** 409 — the idempotency key was reused with a different request body. */
|
|
69
|
+
export declare class AexIdempotencyConflictError extends AexApiError {
|
|
70
|
+
constructor(init: AexApiErrorInit);
|
|
71
|
+
}
|
|
72
|
+
/** 404 — the requested resource was not found. */
|
|
73
|
+
export declare class AexNotFoundError extends AexApiError {
|
|
74
|
+
constructor(init: AexApiErrorInit);
|
|
75
|
+
}
|
|
76
|
+
/** 429 — the workspace hit a rate/concurrency limit; retry after a backoff. */
|
|
77
|
+
export declare class AexRateLimitError extends AexApiError {
|
|
78
|
+
/** Suggested backoff (ms), when the server advertised one (Retry-After). */
|
|
79
|
+
readonly retryAfterMs: number | undefined;
|
|
80
|
+
constructor(init: AexApiErrorInit & {
|
|
81
|
+
readonly retryAfterMs?: number | undefined;
|
|
82
|
+
});
|
|
36
83
|
}
|
|
84
|
+
/** True for a 401/403 authentication/authorization failure. */
|
|
85
|
+
export declare function isAuthError(err: unknown): err is AexAuthError;
|
|
86
|
+
/** True for a 403 whose cause is a missing scope (`insufficient_scope`). */
|
|
87
|
+
export declare function isInsufficientScope(err: unknown): boolean;
|
|
88
|
+
/** True for a 409 idempotency-key reuse conflict. */
|
|
89
|
+
export declare function isIdempotencyConflict(err: unknown): err is AexIdempotencyConflictError;
|
|
90
|
+
/** True for a 404 not-found error. */
|
|
91
|
+
export declare function isNotFound(err: unknown): err is AexNotFoundError;
|
|
92
|
+
/** True for a 429 rate/concurrency-limit error. */
|
|
93
|
+
export declare function isRateLimited(err: unknown): err is AexRateLimitError;
|
|
37
94
|
/**
|
|
38
95
|
* Thrown when a BFF-bound request fails BEFORE any HTTP response exists — DNS
|
|
39
96
|
* failure, connection refused, TLS error, socket reset. Wraps the raw fetch
|
|
@@ -52,6 +109,8 @@ export declare class AexNetworkError extends AexError {
|
|
|
52
109
|
readonly causeCode: string | undefined;
|
|
53
110
|
/** Attempts made when a retry layer exhausted its budget; `1` otherwise. */
|
|
54
111
|
readonly attempts: number;
|
|
112
|
+
/** Total elapsed time (ms) across all attempts, when the retry layer set it. */
|
|
113
|
+
readonly elapsedMs: number | undefined;
|
|
55
114
|
constructor(args: {
|
|
56
115
|
readonly method: string;
|
|
57
116
|
readonly host: string;
|
|
@@ -38,17 +38,94 @@ export class CleanupError extends AexError {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Thrown by SDK and CLI operations when the dashboard BFF returns a non-2xx
|
|
41
|
-
* response. Carries the HTTP status
|
|
41
|
+
* response. Carries the HTTP status, the redacted parsed body, the server's
|
|
42
|
+
* STABLE {@link AexApiErrorCode} (when present), and a `requestId` for support.
|
|
43
|
+
* Construct via {@link import("./error-factory.js").apiErrorFromResponse} — the
|
|
44
|
+
* single wire→exception mapping — which dispatches to a typed subclass
|
|
45
|
+
* ({@link AexAuthError} / {@link AexIdempotencyConflictError} /
|
|
46
|
+
* {@link AexNotFoundError} / {@link AexRateLimitError}).
|
|
42
47
|
*/
|
|
43
48
|
export class AexApiError extends AexError {
|
|
44
49
|
status;
|
|
45
50
|
body;
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
/** The server's stable error code, when the body carried a known one. */
|
|
52
|
+
apiCode;
|
|
53
|
+
/** Request id (body `requestId` or a response header) for support correlation. */
|
|
54
|
+
requestId;
|
|
55
|
+
constructor(status, message, body, options) {
|
|
56
|
+
super("API_ERROR", message, body, options?.cause === undefined ? undefined : { cause: options.cause });
|
|
48
57
|
this.status = status;
|
|
49
58
|
this.body = redactSecrets(body);
|
|
59
|
+
this.apiCode = options?.apiCode;
|
|
60
|
+
this.requestId = options?.requestId;
|
|
50
61
|
}
|
|
51
62
|
}
|
|
63
|
+
/** 401/403 auth failure (token invalid/revoked/expired, forbidden, insufficient scope). */
|
|
64
|
+
export class AexAuthError extends AexApiError {
|
|
65
|
+
/** The scope the endpoint required, when the server named it (insufficient_scope). */
|
|
66
|
+
requiredScope;
|
|
67
|
+
constructor(init) {
|
|
68
|
+
super(init.status, init.message, init.body, {
|
|
69
|
+
apiCode: init.apiCode,
|
|
70
|
+
requestId: init.requestId,
|
|
71
|
+
cause: init.cause
|
|
72
|
+
});
|
|
73
|
+
this.requiredScope = init.requiredScope;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** 409 — the idempotency key was reused with a different request body. */
|
|
77
|
+
export class AexIdempotencyConflictError extends AexApiError {
|
|
78
|
+
constructor(init) {
|
|
79
|
+
super(init.status, init.message, init.body, {
|
|
80
|
+
apiCode: init.apiCode,
|
|
81
|
+
requestId: init.requestId,
|
|
82
|
+
cause: init.cause
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** 404 — the requested resource was not found. */
|
|
87
|
+
export class AexNotFoundError extends AexApiError {
|
|
88
|
+
constructor(init) {
|
|
89
|
+
super(init.status, init.message, init.body, {
|
|
90
|
+
apiCode: init.apiCode,
|
|
91
|
+
requestId: init.requestId,
|
|
92
|
+
cause: init.cause
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** 429 — the workspace hit a rate/concurrency limit; retry after a backoff. */
|
|
97
|
+
export class AexRateLimitError extends AexApiError {
|
|
98
|
+
/** Suggested backoff (ms), when the server advertised one (Retry-After). */
|
|
99
|
+
retryAfterMs;
|
|
100
|
+
constructor(init) {
|
|
101
|
+
super(init.status, init.message, init.body, {
|
|
102
|
+
apiCode: init.apiCode,
|
|
103
|
+
requestId: init.requestId,
|
|
104
|
+
cause: init.cause
|
|
105
|
+
});
|
|
106
|
+
this.retryAfterMs = init.retryAfterMs;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** True for a 401/403 authentication/authorization failure. */
|
|
110
|
+
export function isAuthError(err) {
|
|
111
|
+
return err instanceof AexAuthError;
|
|
112
|
+
}
|
|
113
|
+
/** True for a 403 whose cause is a missing scope (`insufficient_scope`). */
|
|
114
|
+
export function isInsufficientScope(err) {
|
|
115
|
+
return err instanceof AexAuthError && err.apiCode === "insufficient_scope";
|
|
116
|
+
}
|
|
117
|
+
/** True for a 409 idempotency-key reuse conflict. */
|
|
118
|
+
export function isIdempotencyConflict(err) {
|
|
119
|
+
return err instanceof AexIdempotencyConflictError;
|
|
120
|
+
}
|
|
121
|
+
/** True for a 404 not-found error. */
|
|
122
|
+
export function isNotFound(err) {
|
|
123
|
+
return err instanceof AexNotFoundError;
|
|
124
|
+
}
|
|
125
|
+
/** True for a 429 rate/concurrency-limit error. */
|
|
126
|
+
export function isRateLimited(err) {
|
|
127
|
+
return err instanceof AexRateLimitError;
|
|
128
|
+
}
|
|
52
129
|
/**
|
|
53
130
|
* Thrown when a BFF-bound request fails BEFORE any HTTP response exists — DNS
|
|
54
131
|
* failure, connection refused, TLS error, socket reset. Wraps the raw fetch
|
|
@@ -67,6 +144,8 @@ export class AexNetworkError extends AexError {
|
|
|
67
144
|
causeCode;
|
|
68
145
|
/** Attempts made when a retry layer exhausted its budget; `1` otherwise. */
|
|
69
146
|
attempts;
|
|
147
|
+
/** Total elapsed time (ms) across all attempts, when the retry layer set it. */
|
|
148
|
+
elapsedMs;
|
|
70
149
|
constructor(args) {
|
|
71
150
|
const causeCode = extractErrorCode(args.cause);
|
|
72
151
|
super("NETWORK_ERROR", networkErrorMessage(args, causeCode), { method: args.method, host: args.host, path: args.path, ...(causeCode ? { code: causeCode } : {}) }, { cause: args.cause });
|
|
@@ -75,6 +154,7 @@ export class AexNetworkError extends AexError {
|
|
|
75
154
|
this.path = args.path;
|
|
76
155
|
this.causeCode = causeCode;
|
|
77
156
|
this.attempts = args.attempts ?? 1;
|
|
157
|
+
this.elapsedMs = args.elapsedMs;
|
|
78
158
|
}
|
|
79
159
|
}
|
|
80
160
|
function networkErrorMessage(args, causeCode) {
|
|
@@ -194,17 +194,42 @@ export function createRedactingStream(known = []) {
|
|
|
194
194
|
function isSecretKey(key) {
|
|
195
195
|
return /(?:api[_-]?key|authorization|token|secret|password|credential)/i.test(key);
|
|
196
196
|
}
|
|
197
|
-
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
197
|
+
/**
|
|
198
|
+
* A candidate run is a high-entropy secret if it contains an opaque
|
|
199
|
+
* secret-shaped SEGMENT. The candidate class already excludes `_` so
|
|
200
|
+
* snake_case names split into sub-candidates; we ADDITIONALLY treat `-` as a
|
|
201
|
+
* segment boundary here, at the entropy layer, so a kebab-case human name or a
|
|
202
|
+
* timestamped slug is judged by its PARTS — dictionary words + a decimal number,
|
|
203
|
+
* each individually low-entropy — not by its (misleadingly high) whole-run
|
|
204
|
+
* entropy. Concretely a customer secret NAME like `spike17-race-1720000000000`
|
|
205
|
+
* (and its real-`Date.now()` variants, whose whole-run entropy is ~4.1) has no
|
|
206
|
+
* opaque segment and SURVIVES — a name is not a credential, and masking it makes
|
|
207
|
+
* `secret_not_found` unactionable.
|
|
208
|
+
*
|
|
209
|
+
* Splitting at the entropy layer (not in the candidate regex) is the precise,
|
|
210
|
+
* non-weakening choice: an OPAQUE base64url secret that merely CONTAINS a `-`
|
|
211
|
+
* still masks, because at least one of its `-`-segments is itself a dense,
|
|
212
|
+
* class-diverse run and trips the gate. A `-`-free candidate is a single
|
|
213
|
+
* segment, i.e. byte-identical to the pre-fix whole-run check.
|
|
214
|
+
*/
|
|
200
215
|
function looksHighEntropySecret(value) {
|
|
216
|
+
return value.split("-").some(isOpaqueSecretRun);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* True when a delimiter-free run is a dense, character-class-diverse opaque
|
|
220
|
+
* secret: an entropy floor AND ≥2 character classes, and either a digit or an
|
|
221
|
+
* extreme length. The digit/length clause keeps digit-free mixed-case
|
|
222
|
+
* identifiers (stack-trace frames, API symbol names the diagnostic bundle
|
|
223
|
+
* captures) legible while real secret shapes still match. A segment below ~8
|
|
224
|
+
* chars cannot clear the entropy floor, so dictionary words and short slug parts
|
|
225
|
+
* fall out on entropy alone — no separate length gate needed here (the
|
|
226
|
+
* ≥24-char {@link HIGH_ENTROPY_CANDIDATE} bounds the whole candidate).
|
|
227
|
+
*/
|
|
228
|
+
function isOpaqueSecretRun(value) {
|
|
201
229
|
if (charClassCount(value) < MIN_CHAR_CLASSES)
|
|
202
230
|
return false;
|
|
203
231
|
if (shannonEntropyBits(value) < ENTROPY_BITS_PER_CHAR)
|
|
204
232
|
return false;
|
|
205
|
-
// Require a digit OR extreme length so digit-free mixed-case identifiers
|
|
206
|
-
// (stack-trace frames, API symbol names) are not destroyed in diagnostic
|
|
207
|
-
// output, while real secret shapes (alnum-mixed, or very long) still match.
|
|
208
233
|
return /[0-9]/.test(value) || value.length >= HIGH_ENTROPY_NO_DIGIT_MIN_LEN;
|
|
209
234
|
}
|
|
210
235
|
/** How many of {lowercase, uppercase, digit} appear in `value` (0–3). */
|
|
@@ -22,5 +22,19 @@
|
|
|
22
22
|
* exact default API plane.
|
|
23
23
|
*/
|
|
24
24
|
export declare const AEX_DEFAULT_BASE_URL = "https://api.aex.dev";
|
|
25
|
+
/**
|
|
26
|
+
* Plane → API base URL. When the SDK constructor is given no explicit `baseUrl`
|
|
27
|
+
* it DERIVES the target from the API key's embedded plane (see
|
|
28
|
+
* {@link import("./api-key.js").parseApiKey}) rather than blindly defaulting to
|
|
29
|
+
* prod — so a dev key never silently 401s against `api.aex.dev`.
|
|
30
|
+
*
|
|
31
|
+
* `prd` is {@link AEX_DEFAULT_BASE_URL}. `dev` is `null`: the dev plane has no
|
|
32
|
+
* stable public hostname yet, so a dev key still requires an explicit `baseUrl`
|
|
33
|
+
* — but the plane-mismatch guard (dev key + prd baseUrl) still fires.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PLANE_BASE_URLS: {
|
|
36
|
+
readonly dev: null;
|
|
37
|
+
readonly prd: "https://api.aex.dev";
|
|
38
|
+
};
|
|
25
39
|
export declare function stableStringify(value: unknown): string;
|
|
26
40
|
export declare function sha256(value: unknown): string;
|
|
@@ -23,6 +23,20 @@ import { createHash } from "node:crypto";
|
|
|
23
23
|
* exact default API plane.
|
|
24
24
|
*/
|
|
25
25
|
export const AEX_DEFAULT_BASE_URL = "https://api.aex.dev";
|
|
26
|
+
/**
|
|
27
|
+
* Plane → API base URL. When the SDK constructor is given no explicit `baseUrl`
|
|
28
|
+
* it DERIVES the target from the API key's embedded plane (see
|
|
29
|
+
* {@link import("./api-key.js").parseApiKey}) rather than blindly defaulting to
|
|
30
|
+
* prod — so a dev key never silently 401s against `api.aex.dev`.
|
|
31
|
+
*
|
|
32
|
+
* `prd` is {@link AEX_DEFAULT_BASE_URL}. `dev` is `null`: the dev plane has no
|
|
33
|
+
* stable public hostname yet, so a dev key still requires an explicit `baseUrl`
|
|
34
|
+
* — but the plane-mismatch guard (dev key + prd baseUrl) still fires.
|
|
35
|
+
*/
|
|
36
|
+
export const PLANE_BASE_URLS = {
|
|
37
|
+
dev: null,
|
|
38
|
+
prd: AEX_DEFAULT_BASE_URL
|
|
39
|
+
};
|
|
26
40
|
export function stableStringify(value) {
|
|
27
41
|
return JSON.stringify(sortValue(value));
|
|
28
42
|
}
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
export declare const RUN_STATUSES: readonly ["queued", "claiming", "provisioning", "session_created", "dispatched", "provider_running", "provider_idle", "provider_rescheduled", "idle", "suspending", "suspended", "deleting", "deleted", "expired", "cancelling", "capturing_outputs", "cleaning_up", "succeeded", "failed", "timed_out", "cancelled", "cleanup_failed"];
|
|
2
2
|
export type RunStatus = typeof RUN_STATUSES[number];
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The RESUMABLE / in-flight lifecycle half of a session's status. A session
|
|
5
|
+
* whose status is one of these is either transitioning or parked resumable
|
|
6
|
+
* (`idle`/`suspended`) — it is NOT a terminal outcome. The terminal outcome
|
|
7
|
+
* half is {@link SESSION_TERMINAL_OUTCOMES}, derived from the run outcome SSoT.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SESSION_LIFECYCLE_STATUSES: readonly ["creating", "running", "idle", "suspending", "suspended", "cancelling", "deleting", "deleted", "expired"];
|
|
10
|
+
export type SessionLifecycleStatus = typeof SESSION_LIFECYCLE_STATUSES[number];
|
|
11
|
+
/**
|
|
12
|
+
* The terminal OUTCOME half of a session's status — DERIVED from the run
|
|
13
|
+
* outcome SSoT via `satisfies readonly RunTerminalOutcome[]`, so a new run
|
|
14
|
+
* outcome fails to compile until it is accounted for here (and therefore in
|
|
15
|
+
* {@link SESSION_STATUSES}). This is the compile-time binding that stops the
|
|
16
|
+
* session and run terminal vocabularies from drifting: the bare session
|
|
17
|
+
* `error` is gone — a failed turn is `failed`, a wall-clock kill `timed_out`,
|
|
18
|
+
* a cancel `cancelled`, a clean finish `succeeded`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SESSION_TERMINAL_OUTCOMES: readonly ["succeeded", "failed", "timed_out", "cancelled"];
|
|
21
|
+
export type SessionTerminalOutcome = typeof SESSION_TERMINAL_OUTCOMES[number];
|
|
22
|
+
/**
|
|
23
|
+
* The full closed set of session statuses: the resumable lifecycle half, the
|
|
24
|
+
* terminal-outcome half (bound to {@link RUN_TERMINAL_OUTCOMES}), and the
|
|
25
|
+
* first-class HITL write-gate `awaiting_approval`. Composed — never
|
|
26
|
+
* hand-listed — so the outcome vocabulary can only be extended at the run SSoT.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SESSION_STATUSES: readonly ["creating", "running", "idle", "suspending", "suspended", "cancelling", "deleting", "deleted", "expired", "succeeded", "failed", "timed_out", "cancelled", "awaiting_approval"];
|
|
4
29
|
export type SessionStatus = typeof SESSION_STATUSES[number];
|
|
30
|
+
/** True when a session status is terminal (an outcome, or deleted/expired). */
|
|
31
|
+
export declare function isTerminalSessionStatus(status: SessionStatus): boolean;
|
|
5
32
|
export type RunStatusKind = "active" | "terminal";
|
|
6
33
|
export declare const TERMINAL_RUN_STATUSES: readonly ["succeeded", "failed", "timed_out", "cancelled", "deleted", "expired", "cleanup_failed"];
|
|
7
34
|
export declare function isTerminalRunStatus(status: RunStatus): boolean;
|
|
@@ -22,7 +22,13 @@ export const RUN_STATUSES = [
|
|
|
22
22
|
"cancelled",
|
|
23
23
|
"cleanup_failed"
|
|
24
24
|
];
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The RESUMABLE / in-flight lifecycle half of a session's status. A session
|
|
27
|
+
* whose status is one of these is either transitioning or parked resumable
|
|
28
|
+
* (`idle`/`suspended`) — it is NOT a terminal outcome. The terminal outcome
|
|
29
|
+
* half is {@link SESSION_TERMINAL_OUTCOMES}, derived from the run outcome SSoT.
|
|
30
|
+
*/
|
|
31
|
+
export const SESSION_LIFECYCLE_STATUSES = [
|
|
26
32
|
"creating",
|
|
27
33
|
"running",
|
|
28
34
|
"idle",
|
|
@@ -31,9 +37,48 @@ export const SESSION_STATUSES = [
|
|
|
31
37
|
"cancelling",
|
|
32
38
|
"deleting",
|
|
33
39
|
"deleted",
|
|
34
|
-
"expired"
|
|
35
|
-
"error"
|
|
40
|
+
"expired"
|
|
36
41
|
];
|
|
42
|
+
/**
|
|
43
|
+
* The terminal OUTCOME half of a session's status — DERIVED from the run
|
|
44
|
+
* outcome SSoT via `satisfies readonly RunTerminalOutcome[]`, so a new run
|
|
45
|
+
* outcome fails to compile until it is accounted for here (and therefore in
|
|
46
|
+
* {@link SESSION_STATUSES}). This is the compile-time binding that stops the
|
|
47
|
+
* session and run terminal vocabularies from drifting: the bare session
|
|
48
|
+
* `error` is gone — a failed turn is `failed`, a wall-clock kill `timed_out`,
|
|
49
|
+
* a cancel `cancelled`, a clean finish `succeeded`.
|
|
50
|
+
*/
|
|
51
|
+
export const SESSION_TERMINAL_OUTCOMES = [
|
|
52
|
+
"succeeded",
|
|
53
|
+
"failed",
|
|
54
|
+
"timed_out",
|
|
55
|
+
"cancelled"
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* The full closed set of session statuses: the resumable lifecycle half, the
|
|
59
|
+
* terminal-outcome half (bound to {@link RUN_TERMINAL_OUTCOMES}), and the
|
|
60
|
+
* first-class HITL write-gate `awaiting_approval`. Composed — never
|
|
61
|
+
* hand-listed — so the outcome vocabulary can only be extended at the run SSoT.
|
|
62
|
+
*/
|
|
63
|
+
export const SESSION_STATUSES = [
|
|
64
|
+
...SESSION_LIFECYCLE_STATUSES,
|
|
65
|
+
...SESSION_TERMINAL_OUTCOMES,
|
|
66
|
+
"awaiting_approval"
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* The terminal session statuses: the four outcomes plus the lifecycle-terminal
|
|
70
|
+
* `deleted`/`expired`. `idle`/`suspended` are resumable (NOT terminal) and
|
|
71
|
+
* `awaiting_approval` is a held gate (NOT terminal).
|
|
72
|
+
*/
|
|
73
|
+
const SESSION_TERMINAL_STATUSES = new Set([
|
|
74
|
+
...SESSION_TERMINAL_OUTCOMES,
|
|
75
|
+
"deleted",
|
|
76
|
+
"expired"
|
|
77
|
+
]);
|
|
78
|
+
/** True when a session status is terminal (an outcome, or deleted/expired). */
|
|
79
|
+
export function isTerminalSessionStatus(status) {
|
|
80
|
+
return SESSION_TERMINAL_STATUSES.has(status);
|
|
81
|
+
}
|
|
37
82
|
export const TERMINAL_RUN_STATUSES = [
|
|
38
83
|
"succeeded",
|
|
39
84
|
"failed",
|