@aexhq/sdk 0.40.16 → 0.41.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.
- package/README.md +25 -25
- package/dist/_contracts/api-key.d.ts +6 -4
- package/dist/_contracts/api-key.js +20 -7
- package/dist/_contracts/connection-ticket.d.ts +5 -5
- package/dist/_contracts/connection-ticket.js +7 -7
- package/dist/_contracts/error-codes.d.ts +1 -1
- package/dist/_contracts/error-codes.js +4 -4
- package/dist/_contracts/error-factory.js +1 -1
- package/dist/_contracts/event-envelope.d.ts +41 -41
- package/dist/_contracts/event-envelope.js +49 -49
- package/dist/_contracts/event-stream-client.d.ts +9 -9
- package/dist/_contracts/event-stream-client.js +5 -5
- package/dist/_contracts/event-view.d.ts +6 -6
- package/dist/_contracts/event-view.js +12 -12
- package/dist/_contracts/http.js +1 -1
- package/dist/_contracts/index.d.ts +10 -10
- package/dist/_contracts/index.js +9 -9
- package/dist/_contracts/models.d.ts +17 -17
- package/dist/_contracts/models.js +14 -14
- package/dist/_contracts/operations.d.ts +79 -82
- package/dist/_contracts/operations.js +233 -243
- package/dist/_contracts/provider-support.d.ts +2 -2
- package/dist/_contracts/runner-event.d.ts +8 -8
- package/dist/_contracts/runner-event.js +9 -9
- package/dist/_contracts/runtime-manifest.d.ts +12 -12
- package/dist/_contracts/runtime-manifest.js +1 -1
- package/dist/_contracts/runtime-sizes.d.ts +12 -12
- package/dist/_contracts/runtime-sizes.js +18 -18
- package/dist/_contracts/runtime-types.d.ts +117 -117
- package/dist/_contracts/runtime-types.js +2 -2
- package/dist/_contracts/sdk-errors.d.ts +3 -3
- package/dist/_contracts/sdk-errors.js +4 -4
- package/dist/_contracts/sdk-secrets.js +10 -10
- package/dist/_contracts/session-artifacts.d.ts +151 -0
- package/dist/_contracts/session-artifacts.js +234 -0
- package/dist/_contracts/{run-config.d.ts → session-config.d.ts} +29 -29
- package/dist/_contracts/{run-config.js → session-config.js} +37 -37
- package/dist/_contracts/session-cost.d.ts +140 -0
- package/dist/_contracts/{run-cost.js → session-cost.js} +94 -94
- package/dist/_contracts/{run-custody.d.ts → session-custody.d.ts} +16 -16
- package/dist/_contracts/{run-custody.js → session-custody.js} +42 -42
- package/dist/_contracts/session-record.d.ts +112 -0
- package/dist/_contracts/{run-record.js → session-record.js} +23 -23
- package/dist/_contracts/session-retention.d.ts +209 -0
- package/dist/_contracts/{run-retention.js → session-retention.js} +116 -116
- package/dist/_contracts/{run-unit.d.ts → session-unit.d.ts} +38 -38
- package/dist/_contracts/{run-unit.js → session-unit.js} +33 -33
- package/dist/_contracts/side-effect-audit.d.ts +14 -14
- package/dist/_contracts/side-effect-audit.js +38 -38
- package/dist/_contracts/sse.d.ts +1 -1
- package/dist/_contracts/sse.js +0 -0
- package/dist/_contracts/stable.d.ts +1 -1
- package/dist/_contracts/stable.js +1 -1
- package/dist/_contracts/status.d.ts +18 -18
- package/dist/_contracts/status.js +19 -19
- package/dist/_contracts/submission.d.ts +101 -99
- package/dist/_contracts/submission.js +96 -96
- package/dist/_contracts/{run-trace.d.ts → turn-trace.d.ts} +1 -1
- package/dist/_contracts/webhook-verify.d.ts +1 -1
- package/dist/_contracts/webhook-verify.js +1 -1
- package/dist/agents-md.d.ts +4 -4
- package/dist/agents-md.js +5 -5
- package/dist/agents-md.js.map +1 -1
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.js +1 -1
- package/dist/bundle.js.map +1 -1
- package/dist/cli.mjs +578 -621
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +141 -155
- package/dist/client.js +236 -245
- package/dist/client.js.map +1 -1
- package/dist/fetch-archive.js +3 -3
- package/dist/file.d.ts +7 -7
- package/dist/file.js +8 -8
- package/dist/file.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts +1 -1
- package/dist/retry.d.ts +1 -1
- package/dist/secret.d.ts +6 -6
- package/dist/secret.js +6 -6
- package/dist/secret.js.map +1 -1
- package/dist/skill.d.ts +2 -2
- package/dist/skill.js +3 -3
- package/dist/skill.js.map +1 -1
- package/dist/tool.js +2 -2
- package/dist/tool.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/authentication.md +11 -11
- package/docs/billing.md +3 -3
- package/docs/cleanup.md +5 -5
- package/docs/concepts/agent-tools.md +5 -5
- package/docs/concepts/composition.md +3 -3
- package/docs/concepts/providers-and-runtimes.md +3 -3
- package/docs/concepts/{runs.md → sessions.md} +8 -8
- package/docs/concepts/subagents.md +16 -16
- package/docs/credentials.md +7 -7
- package/docs/defaults.md +14 -14
- package/docs/errors.md +10 -10
- package/docs/events.md +26 -26
- package/docs/files.md +267 -0
- package/docs/limits-and-quotas.md +35 -35
- package/docs/limits.md +6 -6
- package/docs/mcp.md +3 -3
- package/docs/networking.md +24 -24
- package/docs/provider-runtime-capabilities.md +1 -1
- package/docs/public-surface.json +6 -6
- package/docs/quickstart.md +13 -13
- package/docs/release.md +5 -5
- package/docs/retries.md +3 -3
- package/docs/secrets.md +16 -16
- package/docs/session-config.md +58 -0
- package/docs/{run-record.md → session-record.md} +16 -16
- package/docs/skills.md +9 -9
- package/docs/vision-skills.md +7 -7
- package/docs/webhooks.md +11 -11
- package/examples/feature-tour.ts +17 -17
- package/examples/spike-settle-latency.ts +11 -11
- package/package.json +1 -1
- package/dist/_contracts/run-artifacts.d.ts +0 -49
- package/dist/_contracts/run-artifacts.js +0 -102
- package/dist/_contracts/run-cost.d.ts +0 -140
- package/dist/_contracts/run-record.d.ts +0 -116
- package/dist/_contracts/run-retention.d.ts +0 -209
- package/docs/outputs.md +0 -267
- package/docs/run-config.md +0 -58
- /package/dist/_contracts/{run-trace.js → turn-trace.js} +0 -0
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ aex is an agent execution platform for launching autonomous agents from a simple
|
|
|
8
8
|
|
|
9
9
|
The package ships:
|
|
10
10
|
|
|
11
|
-
- `Aex` for sessions, one-shot
|
|
11
|
+
- `Aex` for sessions, one-shot sessions, inspect, download, cancel, and delete.
|
|
12
12
|
- `sessions` / `openSession()` for durable, resumable agent sessions.
|
|
13
13
|
- Typed run primitives: `Models`, `Providers`, `Sizes`, `Skill`, `Tool` / `Tools`, `AgentsMd`, `File`, `McpServer`, and `Secret`.
|
|
14
|
-
- A bundled `aex` CLI with the same run, status, events,
|
|
14
|
+
- A bundled `aex` CLI with the same run, status, events, files, download, cancel, delete, and whoami operations.
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -55,24 +55,24 @@ const resumed = await aex.openSession(session.id);
|
|
|
55
55
|
await resumed.send("Continue with the follow-up validation.").done();
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Need a one-shot convenience? `
|
|
59
|
-
turn, and returns the collected result. The returned `
|
|
60
|
-
so it can be resumed later with `openSession(
|
|
58
|
+
Need a one-shot convenience? `start()` opens a session, sends `message` as one
|
|
59
|
+
turn, and returns the collected result. The returned `sessionId` is the session id,
|
|
60
|
+
so it can be resumed later with `openSession(sessionId)`.
|
|
61
61
|
|
|
62
62
|
```ts
|
|
63
|
-
const result = await aex.
|
|
63
|
+
const result = await aex.start({
|
|
64
64
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
65
65
|
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! },
|
|
66
|
-
message: "Write the report and save
|
|
66
|
+
message: "Write the report and save files."
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
console.log(result.
|
|
69
|
+
console.log(result.sessionId, result.status, result.text);
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
For multiple providers, include each BYOK key in `apiKeys`:
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
-
await aex.
|
|
75
|
+
await aex.start({
|
|
76
76
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
77
77
|
apiKeys: {
|
|
78
78
|
anthropic: process.env.ANTHROPIC_API_KEY!,
|
|
@@ -85,11 +85,11 @@ await aex.run({
|
|
|
85
85
|
The same request can run from the bundled CLI (`npx aex` on a local install):
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
npx aex
|
|
88
|
+
npx aex start \
|
|
89
89
|
--api-key "$AEX_API_KEY" \
|
|
90
90
|
--anthropic-api-key "$ANTHROPIC_API_KEY" \
|
|
91
91
|
--model claude-haiku-4-5 \
|
|
92
|
-
--prompt "Write the report and save
|
|
92
|
+
--prompt "Write the report and save files." \
|
|
93
93
|
--follow
|
|
94
94
|
```
|
|
95
95
|
|
|
@@ -115,44 +115,44 @@ by default, machine JSON under `--json`). Per-verb `--help` is also key-free:
|
|
|
115
115
|
npx aex models list # canonical models + their default provider
|
|
116
116
|
npx aex providers list # providers + the models each serves
|
|
117
117
|
npx aex tools list # complete builtin tool set
|
|
118
|
-
npx aex
|
|
119
|
-
npx aex
|
|
118
|
+
npx aex starttime-sizes list # managed runtime presets (cpus / memory / default)
|
|
119
|
+
npx aex start --help # flags for one verb (no API key required)
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Errors are typed and actionable. Every `openSession()` / `
|
|
123
|
-
failure throws a `
|
|
122
|
+
Errors are typed and actionable. Every `openSession()` / `start()` config-validation
|
|
123
|
+
failure throws a `SessionConfigValidationError` (`err.code === "SESSION_CONFIG_INVALID"`)
|
|
124
124
|
you can `catch` by code; CLI failures print a JSON envelope carrying the HTTP
|
|
125
|
-
`status`, a one-line `remedy`, and the `
|
|
125
|
+
`status`, a one-line `remedy`, and the `sessionId` where known, and a wrong `--model`,
|
|
126
126
|
`--provider`, or `--runtime-size` gets a "did you mean?" suggestion.
|
|
127
127
|
|
|
128
128
|
## Continue with sessions
|
|
129
129
|
|
|
130
|
-
Use sessions for conversational flows. `
|
|
130
|
+
Use sessions for conversational flows. `start()` is the one-shot convenience; a
|
|
131
131
|
`SessionHandle` is the lower-level surface when you want multiple turns,
|
|
132
|
-
streaming, messages, events,
|
|
132
|
+
streaming, messages, events, files, or downloads.
|
|
133
133
|
|
|
134
134
|
```ts
|
|
135
|
-
const session = await aex.openSession(result.
|
|
135
|
+
const session = await aex.openSession(result.sessionId);
|
|
136
136
|
const next = await session.send("Turn this into a checklist.").done();
|
|
137
137
|
console.log(next.text);
|
|
138
138
|
|
|
139
139
|
const messages = await session.messages().list();
|
|
140
|
-
const
|
|
140
|
+
const files = await aex.sessions.files(session.id).list();
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
## Feature Areas
|
|
144
144
|
|
|
145
|
-
- **Agent runtime:** managed autonomous
|
|
146
|
-
- **Durable infrastructure:**
|
|
145
|
+
- **Agent runtime:** managed autonomous sessions with filesystem read/edit, grep/glob/head/tail, open web fetch/search, background commands, code execution, git, and subagents.
|
|
146
|
+
- **Durable infrastructure:** session records, status, wait/cancel/delete, idempotency, typed events, file capture, downloads, timeouts, and runtime sizes.
|
|
147
147
|
- **Agent composition:** skills, files, AGENTS.md, remote MCP servers, environment variables, packages, and networking controls.
|
|
148
|
-
- **Subagents:** typed parent/child lineage for async child
|
|
148
|
+
- **Subagents:** typed parent/child lineage for async child sessions, file handoff, and bounded agent delegation.
|
|
149
149
|
- **Models and providers:** Anthropic, DeepSeek, OpenAI, Gemini, Mistral, OpenRouter, Doubao, and Doubao China behind one submission shape.
|
|
150
|
-
- **Typed control surface:** strongly typed SDK inputs, CLI parity, BYOK provider keys, workspace secrets, redaction, and
|
|
150
|
+
- **Typed control surface:** strongly typed SDK inputs, CLI parity, BYOK provider keys, workspace secrets, redaction, assistant text modes, and file capture policy.
|
|
151
151
|
|
|
152
152
|
## Docs
|
|
153
153
|
|
|
154
154
|
- [Quickstart](docs/quickstart.md)
|
|
155
|
-
- [
|
|
155
|
+
- [SessionRecord configuration](docs/session-config.md)
|
|
156
156
|
- [Composition](docs/concepts/composition.md)
|
|
157
157
|
- [Secrets](docs/secrets.md)
|
|
158
158
|
- [Limits](docs/limits.md)
|
|
@@ -24,9 +24,10 @@ export interface ParsedApiKey {
|
|
|
24
24
|
readonly workspaceId: string;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Canonical form of a workspaceId for EMBEDDING in a key
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Canonical form of a workspaceId for EMBEDDING in a key. Public workspace ids
|
|
28
|
+
* are `wsp_<uuidhex>`, while storage rows still use dashed UUIDs. Keys embed only
|
|
29
|
+
* the hex field so the token remains underscore-delimited and double-click
|
|
30
|
+
* selectable.
|
|
30
31
|
*/
|
|
31
32
|
export declare function normalizeWorkspaceId(workspaceId: string): string;
|
|
32
33
|
/**
|
|
@@ -39,7 +40,8 @@ export declare function parseApiKey(token: string): ParsedApiKey | null;
|
|
|
39
40
|
* Assemble a valid API key from its parts (the inverse of {@link parseApiKey}).
|
|
40
41
|
* Unlike the server's `mintApiKeyValue` this takes an EXPLICIT `secret` so it is
|
|
41
42
|
* deterministic — used by codec round-trip / cross-repo parity tests. The
|
|
42
|
-
* embedded workspace id is
|
|
43
|
+
* embedded workspace id is normalized; public `wsp_...` ids are accepted and
|
|
44
|
+
* embedded as their hex field.
|
|
43
45
|
*/
|
|
44
46
|
export declare function formatApiKey(input: {
|
|
45
47
|
readonly plane: ApiKeyPlane;
|
|
@@ -21,13 +21,24 @@ export const API_KEY_REGION_TO_CODE = {
|
|
|
21
21
|
};
|
|
22
22
|
const CODE_TO_REGION = Object.fromEntries(Object.entries(API_KEY_REGION_TO_CODE).map(([region, code]) => [code, region]));
|
|
23
23
|
const API_KEY_PLANE_SET = new Set(API_KEY_PLANES);
|
|
24
|
+
const PUBLIC_WORKSPACE_ID_RE = /^wsp_([0-9a-f]{32})$/i;
|
|
25
|
+
const DASHLESS_UUID_RE = /^[0-9a-f]{32}$/i;
|
|
26
|
+
const UUID_RE = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i;
|
|
24
27
|
/**
|
|
25
|
-
* Canonical form of a workspaceId for EMBEDDING in a key
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
+
* Canonical form of a workspaceId for EMBEDDING in a key. Public workspace ids
|
|
29
|
+
* are `wsp_<uuidhex>`, while storage rows still use dashed UUIDs. Keys embed only
|
|
30
|
+
* the hex field so the token remains underscore-delimited and double-click
|
|
31
|
+
* selectable.
|
|
28
32
|
*/
|
|
29
33
|
export function normalizeWorkspaceId(workspaceId) {
|
|
30
|
-
|
|
34
|
+
const trimmed = workspaceId.trim();
|
|
35
|
+
const publicMatch = PUBLIC_WORKSPACE_ID_RE.exec(trimmed);
|
|
36
|
+
if (publicMatch)
|
|
37
|
+
return publicMatch[1].toLowerCase();
|
|
38
|
+
if (DASHLESS_UUID_RE.test(trimmed))
|
|
39
|
+
return trimmed.toLowerCase();
|
|
40
|
+
const uuidMatch = UUID_RE.exec(trimmed);
|
|
41
|
+
return uuidMatch ? uuidMatch.slice(1).join("").toLowerCase() : trimmed.replace(/-/g, "");
|
|
31
42
|
}
|
|
32
43
|
/**
|
|
33
44
|
* Parse a self-describing API key, or `null` for any legacy/opaque/tampered
|
|
@@ -55,20 +66,22 @@ export function parseApiKey(token) {
|
|
|
55
66
|
* Assemble a valid API key from its parts (the inverse of {@link parseApiKey}).
|
|
56
67
|
* Unlike the server's `mintApiKeyValue` this takes an EXPLICIT `secret` so it is
|
|
57
68
|
* deterministic — used by codec round-trip / cross-repo parity tests. The
|
|
58
|
-
* embedded workspace id is
|
|
69
|
+
* embedded workspace id is normalized; public `wsp_...` ids are accepted and
|
|
70
|
+
* embedded as their hex field.
|
|
59
71
|
*/
|
|
60
72
|
export function formatApiKey(input) {
|
|
61
73
|
const code = API_KEY_REGION_TO_CODE[input.region];
|
|
62
74
|
if (code === undefined) {
|
|
63
75
|
throw new Error(`API key region is not supported: ${input.region}`);
|
|
64
76
|
}
|
|
65
|
-
|
|
77
|
+
const workspaceField = normalizeWorkspaceId(input.workspaceId);
|
|
78
|
+
if (!workspaceField || workspaceField.includes("_")) {
|
|
66
79
|
throw new Error("workspaceId must be non-empty and contain no '_'");
|
|
67
80
|
}
|
|
68
81
|
if (!input.secret || input.secret.includes("_")) {
|
|
69
82
|
throw new Error("secret must be non-empty and contain no '_'");
|
|
70
83
|
}
|
|
71
|
-
const body = ["aex", input.plane, code,
|
|
84
|
+
const body = ["aex", input.plane, code, workspaceField, input.secret].join("_");
|
|
72
85
|
return `${body}_${crc32Base36(body)}`;
|
|
73
86
|
}
|
|
74
87
|
function crc32Base36(input) {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* A browser/runtime WebSocket handshake cannot carry an Authorization header,
|
|
5
5
|
* so a subscriber first obtains a short-lived ticket from an authenticated
|
|
6
6
|
* HTTP endpoint, then presents it as a `?ticket=` query parameter on the WS
|
|
7
|
-
* upgrade. The ticket is an HMAC over `${
|
|
8
|
-
* the coordinator secret — it binds the grant to one
|
|
7
|
+
* upgrade. The ticket is an HMAC over `${sessionId}.${channel}.${exp}` keyed by
|
|
8
|
+
* the coordinator secret — it binds the grant to one session, one stream channel,
|
|
9
9
|
* and one expiry and is verified without any per-ticket storage.
|
|
10
10
|
*
|
|
11
11
|
* This lives in shared so the coordinator (which verifies) and the API
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export type ConnectionTicketChannel = "event" | "log" | "all";
|
|
16
16
|
/** Mint a `${channel}.${exp}.${mac}` ticket valid for `ttlMs` from `nowMs`. */
|
|
17
|
-
export declare function mintConnectionTicket(
|
|
17
|
+
export declare function mintConnectionTicket(sessionId: string, secret: string, nowMs: number, ttlMs?: number, channel?: ConnectionTicketChannel): Promise<{
|
|
18
18
|
ticket: string;
|
|
19
19
|
expiresAtMs: number;
|
|
20
20
|
}>;
|
|
21
|
-
/** Verify a ticket against `
|
|
22
|
-
export declare function verifyConnectionTicket(ticket: string,
|
|
21
|
+
/** Verify a ticket against `sessionId`, channel, and current time. Constant-time MAC compare. */
|
|
22
|
+
export declare function verifyConnectionTicket(ticket: string, sessionId: string, secret: string, nowMs: number, channel?: ConnectionTicketChannel): Promise<boolean>;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* A browser/runtime WebSocket handshake cannot carry an Authorization header,
|
|
5
5
|
* so a subscriber first obtains a short-lived ticket from an authenticated
|
|
6
6
|
* HTTP endpoint, then presents it as a `?ticket=` query parameter on the WS
|
|
7
|
-
* upgrade. The ticket is an HMAC over `${
|
|
8
|
-
* the coordinator secret — it binds the grant to one
|
|
7
|
+
* upgrade. The ticket is an HMAC over `${sessionId}.${channel}.${exp}` keyed by
|
|
8
|
+
* the coordinator secret — it binds the grant to one session, one stream channel,
|
|
9
9
|
* and one expiry and is verified without any per-ticket storage.
|
|
10
10
|
*
|
|
11
11
|
* This lives in shared so the coordinator (which verifies) and the API
|
|
@@ -23,14 +23,14 @@ async function hmacHex(secret, message) {
|
|
|
23
23
|
return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
24
24
|
}
|
|
25
25
|
/** Mint a `${channel}.${exp}.${mac}` ticket valid for `ttlMs` from `nowMs`. */
|
|
26
|
-
export async function mintConnectionTicket(
|
|
26
|
+
export async function mintConnectionTicket(sessionId, secret, nowMs, ttlMs = DEFAULT_TICKET_TTL_MS, channel = "event") {
|
|
27
27
|
const boundChannel = normalizeTicketChannel(channel);
|
|
28
28
|
const exp = nowMs + ttlMs;
|
|
29
|
-
const mac = await hmacHex(secret, `${
|
|
29
|
+
const mac = await hmacHex(secret, `${sessionId}.${boundChannel}.${exp}`);
|
|
30
30
|
return { ticket: `${boundChannel}.${exp}.${mac}`, expiresAtMs: exp };
|
|
31
31
|
}
|
|
32
|
-
/** Verify a ticket against `
|
|
33
|
-
export async function verifyConnectionTicket(ticket,
|
|
32
|
+
/** Verify a ticket against `sessionId`, channel, and current time. Constant-time MAC compare. */
|
|
33
|
+
export async function verifyConnectionTicket(ticket, sessionId, secret, nowMs, channel = "event") {
|
|
34
34
|
const [ticketChannel, expRaw, mac, ...rest] = ticket.split(".");
|
|
35
35
|
if (rest.length > 0 || !ticketChannel || !expRaw || !mac)
|
|
36
36
|
return false;
|
|
@@ -40,7 +40,7 @@ export async function verifyConnectionTicket(ticket, runId, secret, nowMs, chann
|
|
|
40
40
|
const exp = Number(expRaw);
|
|
41
41
|
if (!Number.isFinite(exp) || exp < nowMs)
|
|
42
42
|
return false;
|
|
43
|
-
const expected = await hmacHex(secret, `${
|
|
43
|
+
const expected = await hmacHex(secret, `${sessionId}.${boundChannel}.${exp}`);
|
|
44
44
|
return timingSafeEqual(mac, expected);
|
|
45
45
|
}
|
|
46
46
|
/** Length-then-constant-time comparison of two hex strings. */
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* from the human `message`. `idempotency_conflict` and `insufficient_scope`
|
|
12
12
|
* (previously message-less bare 409/403 bodies) are first-class here.
|
|
13
13
|
*/
|
|
14
|
-
export declare const AEX_API_ERROR_CODES: readonly ["unauthorized", "forbidden", "insufficient_scope", "token_invalid", "token_revoked", "token_expired", "malformed_token", "not_found", "idempotency_conflict", "session_busy", "
|
|
14
|
+
export declare const AEX_API_ERROR_CODES: readonly ["unauthorized", "forbidden", "insufficient_scope", "token_invalid", "token_revoked", "token_expired", "malformed_token", "not_found", "idempotency_conflict", "session_busy", "session_not_terminal", "unknown_workspace", "workspace_concurrency_exceeded", "workspace_submit_rate_exceeded", "workspace_spend_cap_exceeded", "insufficient_balance", "rate_limited", "upstream_error", "internal_error"];
|
|
15
15
|
export type AexApiErrorCode = (typeof AEX_API_ERROR_CODES)[number];
|
|
16
16
|
/**
|
|
17
17
|
* Human message per stable code. A `Record` (not `Partial`) so a code added to
|
|
@@ -22,7 +22,7 @@ export const AEX_API_ERROR_CODES = [
|
|
|
22
22
|
"not_found",
|
|
23
23
|
"idempotency_conflict",
|
|
24
24
|
"session_busy",
|
|
25
|
-
"
|
|
25
|
+
"session_not_terminal",
|
|
26
26
|
"unknown_workspace",
|
|
27
27
|
"workspace_concurrency_exceeded",
|
|
28
28
|
"workspace_submit_rate_exceeded",
|
|
@@ -49,12 +49,12 @@ export const AEX_API_ERROR_MESSAGES = {
|
|
|
49
49
|
not_found: "The requested resource was not found.",
|
|
50
50
|
idempotency_conflict: "This idempotency key was already used with a different request body.",
|
|
51
51
|
session_busy: "The session is busy handling another turn.",
|
|
52
|
-
|
|
52
|
+
session_not_terminal: "The session has not reached a terminal state yet.",
|
|
53
53
|
unknown_workspace: "The workspace could not be resolved from the API key.",
|
|
54
54
|
workspace_concurrency_exceeded: "The workspace has reached its concurrent-run limit.",
|
|
55
55
|
workspace_submit_rate_exceeded: "The workspace submit-rate limit was exceeded.",
|
|
56
56
|
workspace_spend_cap_exceeded: "The workspace monthly spend cap was reached.",
|
|
57
|
-
insufficient_balance: "The workspace prepaid balance is insufficient to submit this
|
|
57
|
+
insufficient_balance: "The workspace prepaid balance is insufficient to submit this session.",
|
|
58
58
|
rate_limited: "Too many requests — retry after a short backoff.",
|
|
59
59
|
upstream_error: "An upstream provider returned an error.",
|
|
60
60
|
internal_error: "The aex API encountered an internal error."
|
|
@@ -68,7 +68,7 @@ export const AEX_API_ERROR_REMEDIES = {
|
|
|
68
68
|
token_revoked: "Mint a new API key.",
|
|
69
69
|
workspace_spend_cap_exceeded: "Raise the workspace spend cap or wait for the next billing cycle.",
|
|
70
70
|
insufficient_balance: "Top up the workspace balance or add a payment method.",
|
|
71
|
-
workspace_concurrency_exceeded: "Wait for in-flight
|
|
71
|
+
workspace_concurrency_exceeded: "Wait for in-flight sessions to finish or raise the concurrency limit.",
|
|
72
72
|
workspace_submit_rate_exceeded: "Slow the submit rate or raise the workspace submit-rate limit."
|
|
73
73
|
};
|
|
74
74
|
const API_ERROR_CODE_SET = new Set(AEX_API_ERROR_CODES);
|
|
@@ -34,7 +34,7 @@ export function apiErrorKindForCode(code) {
|
|
|
34
34
|
case "workspace_submit_rate_exceeded":
|
|
35
35
|
return "rate_limit";
|
|
36
36
|
case "session_busy":
|
|
37
|
-
case "
|
|
37
|
+
case "session_not_terminal":
|
|
38
38
|
case "unknown_workspace":
|
|
39
39
|
case "workspace_spend_cap_exceeded":
|
|
40
40
|
case "insufficient_balance":
|
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
* the shape the coordinator (Phase 2) appends, broadcasts, and archives.
|
|
8
8
|
*
|
|
9
9
|
* - **CloudEvents-shaped** self-describing envelope: a stable `id`, the
|
|
10
|
-
* coarse `source`, the AG-UI-aligned `type`, the `subject` (
|
|
10
|
+
* coarse `source`, the AG-UI-aligned `type`, the `subject` (session), a
|
|
11
11
|
* `time`, the `sequence` cursor, and the typed `data`.
|
|
12
12
|
* - **AG-UI vocabulary** for `type` where it maps; aex-specific events
|
|
13
13
|
* ride AG-UI's reserved `CUSTOM` carrier under an `aex.*` name, so an
|
|
14
|
-
* off-the-shelf AG-UI client reads an aex
|
|
14
|
+
* off-the-shelf AG-UI client reads an aex start with no glue.
|
|
15
15
|
* - Two aex extensions: a coarse `source` (filter first by origin) and
|
|
16
16
|
* an optional human `message` (log / CLI / dashboard rendering).
|
|
17
17
|
*
|
|
18
18
|
* Unified observability spine:
|
|
19
19
|
* the envelope additionally carries four ordering attributes so BOTH the typed
|
|
20
|
-
* event stream and the high-volume hosted-platform log stream ride one per-
|
|
20
|
+
* event stream and the high-volume hosted-platform log stream ride one per-session
|
|
21
21
|
* coordinator:
|
|
22
22
|
* - `channel` — "event" (the typed AG-UI stream) or "log" (a verbose log
|
|
23
23
|
* line). The single axis a consumer splits the unified stream
|
|
@@ -62,7 +62,7 @@ export declare const AEX_EVENT_MAP_VERSION: 1;
|
|
|
62
62
|
export declare const AEX_EVENT_SOURCES: readonly ["agent", "api", "runtime", "mcp", "aex", "workflow", "host"];
|
|
63
63
|
export type AexEventSource = (typeof AEX_EVENT_SOURCES)[number];
|
|
64
64
|
/**
|
|
65
|
-
* The channel a record rides on the unified per-
|
|
65
|
+
* The channel a record rides on the unified per-session stream:
|
|
66
66
|
* - `event` — the typed, low-volume, fully-replayed AG-UI event stream.
|
|
67
67
|
* - `log` — a high-volume verbose log line (level + message + fields). The
|
|
68
68
|
* coordinator prunes flushed log rows after evidence archival
|
|
@@ -84,7 +84,7 @@ export type AexLogLevel = (typeof AEX_LOG_LEVELS)[number];
|
|
|
84
84
|
* full AG-UI protocol — the events aex actually produces today — plus
|
|
85
85
|
* `CUSTOM`, AG-UI's reserved carrier for aex-native events.
|
|
86
86
|
*/
|
|
87
|
-
export declare const AEX_EVENT_TYPES: readonly ["
|
|
87
|
+
export declare const AEX_EVENT_TYPES: readonly ["TURN_STARTED", "TURN_FINISHED", "TURN_ERROR", "TEXT_MESSAGE_CONTENT", "TOOL_CALL_START", "TOOL_CALL_RESULT", "CUSTOM", "LOG"];
|
|
88
88
|
export type AexEventType = (typeof AEX_EVENT_TYPES)[number];
|
|
89
89
|
/**
|
|
90
90
|
* One event on the unified log. CloudEvents core attributes (`specversion`,
|
|
@@ -94,18 +94,18 @@ export type AexEventType = (typeof AEX_EVENT_TYPES)[number];
|
|
|
94
94
|
export interface AexEvent {
|
|
95
95
|
/** CloudEvents specversion. Always {@link AEX_EVENT_SPECVERSION}. */
|
|
96
96
|
readonly specversion: typeof AEX_EVENT_SPECVERSION;
|
|
97
|
-
/** Stable, globally-unique event id: `${
|
|
97
|
+
/** Stable, globally-unique event id: `${sessionId}:${sequence}`. Dedupe key. */
|
|
98
98
|
readonly id: string;
|
|
99
99
|
/** Coarse origin classifier. */
|
|
100
100
|
readonly source: AexEventSource;
|
|
101
101
|
/** AG-UI-aligned event type. */
|
|
102
102
|
readonly type: AexEventType;
|
|
103
|
-
/** The
|
|
103
|
+
/** The session this event belongs to (CloudEvents `subject`). */
|
|
104
104
|
readonly subject: string;
|
|
105
|
-
/** ISO-8601 event time (
|
|
105
|
+
/** ISO-8601 event time (session base + the RunnerEvent's relative `tMs`). */
|
|
106
106
|
readonly time: string;
|
|
107
107
|
/**
|
|
108
|
-
* Monotonic ordering cursor within the
|
|
108
|
+
* Monotonic ordering cursor within the session — the GLOBAL `seq` the coordinator
|
|
109
109
|
* assigns on arrival. This is the canonical stream order and the dedupe key.
|
|
110
110
|
*/
|
|
111
111
|
readonly sequence: number;
|
|
@@ -126,12 +126,12 @@ export interface AexEvent {
|
|
|
126
126
|
* The DO's authoritative receive time (wall-clock ms), stamped at ingest
|
|
127
127
|
* alongside the global {@link sequence}. This is the authoritative wall-clock
|
|
128
128
|
* companion to `sequence` — distinct from `emittedAt` (the SOURCE's clock) and
|
|
129
|
-
* `time` (the LOGICAL time =
|
|
129
|
+
* `time` (the LOGICAL time = session base + relative tMs).
|
|
130
130
|
*
|
|
131
131
|
* NOTE: some edge clocks are coarsened/frozen-at-I/O, so `receivedAt` is
|
|
132
132
|
* precisely "the coordinator's last-I/O wall-clock at ingest" — that is fine
|
|
133
133
|
* as the authoritative receive marker. It does NOT need to exceed `emittedAt`:
|
|
134
|
-
* the source
|
|
134
|
+
* the source sessions on a different host with an independent clock, so cross-host
|
|
135
135
|
* drift can leave `receivedAt < emittedAt` and that is expected, not an error.
|
|
136
136
|
*/
|
|
137
137
|
readonly receivedAt?: number;
|
|
@@ -152,10 +152,10 @@ export interface AexEvent {
|
|
|
152
152
|
}
|
|
153
153
|
/** Context the mapper needs to stamp absolute identity/time onto an event. */
|
|
154
154
|
export interface AexEventContext {
|
|
155
|
-
readonly
|
|
155
|
+
readonly sessionId: string;
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
158
|
-
* `time = new Date(baseMs + tMs)`. Pass the
|
|
157
|
+
* SessionRecord-start epoch ms. The RunnerEvent's `tMs` is relative to this, so
|
|
158
|
+
* `time = new Date(baseMs + tMs)`. Pass the session's `createdAtMs`.
|
|
159
159
|
*/
|
|
160
160
|
readonly baseMs: number;
|
|
161
161
|
}
|
|
@@ -190,22 +190,22 @@ export type AexInboundLog = Omit<AexEvent, "specversion" | "id" | "subject" | "s
|
|
|
190
190
|
* and the `LOG` payload — is supplied here.
|
|
191
191
|
*/
|
|
192
192
|
export declare function logToInbound(source: AexEventSource, line: AexLogLine): AexInboundLog;
|
|
193
|
-
export declare function
|
|
194
|
-
export declare function
|
|
195
|
-
export declare function
|
|
193
|
+
export declare function isTurnStarted(e: AexEvent): boolean;
|
|
194
|
+
export declare function isTurnFinished(e: AexEvent): boolean;
|
|
195
|
+
export declare function isTurnError(e: AexEvent): boolean;
|
|
196
196
|
/** A terminal event of either flavour (finished or error). */
|
|
197
|
-
export declare function
|
|
197
|
+
export declare function isTurnTerminal(e: AexEvent): boolean;
|
|
198
198
|
/**
|
|
199
|
-
* The CUSTOM `data.name`s the MANAGED runtime emits as a
|
|
200
|
-
* terminal. A managed one-shot
|
|
201
|
-
* rather than writing a `session_finished` →
|
|
202
|
-
* the AG-UI
|
|
199
|
+
* The CUSTOM `data.name`s the MANAGED runtime emits as a session/turn's SETTLED
|
|
200
|
+
* terminal. A managed one-shot session PARKS (`session_parked.v1` → one of these)
|
|
201
|
+
* rather than writing a `session_finished` → TURN_FINISHED, so these — not just
|
|
202
|
+
* the AG-UI TURN_FINISHED/TURN_ERROR — are what actually end a managed session's event
|
|
203
203
|
* stream. Clean-cut (WS1): the bare `aex.session.error` park is retired in favour
|
|
204
204
|
* of the terminal OUTCOME vocabulary — a failed turn is `failed`, a wall-clock
|
|
205
205
|
* kill `timed_out`, a cancel `cancelled`, a clean finish `succeeded`; `idle`/
|
|
206
206
|
* `suspended` remain the resumable parks. Held `awaiting_approval` is NOT here:
|
|
207
207
|
* it ends the TURN stream (see `isSessionTurnTerminalEvent` in the SDK) but the
|
|
208
|
-
*
|
|
208
|
+
* session is not settled. Kept byte-in-sync with `@aexhq/shared` `AEX_SESSION_PARKED_NAMES`
|
|
209
209
|
* and the platform journal projection (`journal-project.ts` session_parked.v1).
|
|
210
210
|
*/
|
|
211
211
|
export declare const AEX_SESSION_PARKED_NAMES: readonly ["aex.session.idle", "aex.session.suspended", "aex.session.succeeded", "aex.session.failed", "aex.session.timed_out", "aex.session.cancelled"];
|
|
@@ -213,8 +213,8 @@ export declare const AEX_SESSION_PARKED_NAMES: readonly ["aex.session.idle", "ae
|
|
|
213
213
|
* True for a managed-runtime session-park terminal (a resumable idle/suspended
|
|
214
214
|
* park OR a terminal succeeded/failed/timed_out/cancelled outcome). The turn's
|
|
215
215
|
* work is done and the record has reached its terminal status; a stream consumer
|
|
216
|
-
* should stop here exactly as it would on
|
|
217
|
-
* `awaiting_approval` is deliberately excluded — the
|
|
216
|
+
* should stop here exactly as it would on TURN_FINISHED/TURN_ERROR. (Held
|
|
217
|
+
* `awaiting_approval` is deliberately excluded — the session is paused, not settled.)
|
|
218
218
|
*/
|
|
219
219
|
export declare function isSessionParked(e: AexEvent): boolean;
|
|
220
220
|
export declare function isTextMessage(e: AexEvent): boolean;
|
|
@@ -225,25 +225,25 @@ export declare function isCustom(e: AexEvent): boolean;
|
|
|
225
225
|
export declare function customName(e: AexEvent): string | null;
|
|
226
226
|
/**
|
|
227
227
|
* The `data.name` of the settle-consistency barrier event. The coordinator
|
|
228
|
-
* broadcasts ONE such CUSTOM event as a
|
|
229
|
-
* Postgres mirror lands — so observing it ⇒ a subsequent `
|
|
230
|
-
* and `
|
|
231
|
-
* typed
|
|
232
|
-
* `streamEnvelopes(
|
|
233
|
-
* Unlike
|
|
228
|
+
* broadcasts ONE such CUSTOM event as a session's LAST stream event, after the
|
|
229
|
+
* Postgres mirror lands — so observing it ⇒ a subsequent `getSessionRecord` is terminal
|
|
230
|
+
* and `listSessionFiles` is complete. It is intentionally a CUSTOM event (not a
|
|
231
|
+
* typed SESSION_* event): off-the-shelf AG-UI clients ignore it, while
|
|
232
|
+
* `streamEnvelopes(sessionId, { settleConsistent: true })` ends the iterator on it.
|
|
233
|
+
* Unlike TURN_FINISHED (the AG-UI render-complete UX signal, emitted by the
|
|
234
234
|
* runner BEFORE the platform learns the outcome), this is settle-gated. The
|
|
235
235
|
* platform mirrors this constant in `@aexhq/shared`.
|
|
236
236
|
*/
|
|
237
|
-
export declare const
|
|
237
|
+
export declare const AEX_SESSION_SETTLED_NAME = "aex.session.settled";
|
|
238
238
|
/**
|
|
239
239
|
* True for the settle-consistency barrier event (post-mirror, read-consistent).
|
|
240
240
|
* Also true for a managed-runtime session-park terminal: the current plane emits
|
|
241
|
-
* an `aex.
|
|
241
|
+
* an `aex.session.settled` barrier after settle, but a stream may still observe the
|
|
242
242
|
* park as the terminal event when no later barrier is delivered.
|
|
243
243
|
*/
|
|
244
|
-
export declare function
|
|
244
|
+
export declare function isSessionSettled(e: AexEvent): boolean;
|
|
245
245
|
/**
|
|
246
|
-
* The CUSTOM `data.name` of the HITL write-gate park: the
|
|
246
|
+
* The CUSTOM `data.name` of the HITL write-gate park: the session has reached the
|
|
247
247
|
* `awaiting_approval` state before a gated action and is holding for an
|
|
248
248
|
* `approve()`/`deny()`. Structural (independent of model prose).
|
|
249
249
|
*/
|
|
@@ -285,17 +285,17 @@ export declare function exceedsRowBudget(e: AexEvent, max?: number): boolean;
|
|
|
285
285
|
* that want it.
|
|
286
286
|
*/
|
|
287
287
|
export type AguiEvent = {
|
|
288
|
-
type: "
|
|
288
|
+
type: "TURN_STARTED";
|
|
289
289
|
timestamp: number;
|
|
290
290
|
threadId: string;
|
|
291
|
-
|
|
291
|
+
sessionId: string;
|
|
292
292
|
} | {
|
|
293
|
-
type: "
|
|
293
|
+
type: "TURN_FINISHED";
|
|
294
294
|
timestamp: number;
|
|
295
295
|
threadId: string;
|
|
296
|
-
|
|
296
|
+
sessionId: string;
|
|
297
297
|
} | {
|
|
298
|
-
type: "
|
|
298
|
+
type: "TURN_ERROR";
|
|
299
299
|
timestamp: number;
|
|
300
300
|
message: string;
|
|
301
301
|
code?: string;
|
|
@@ -323,7 +323,7 @@ export type AguiEvent = {
|
|
|
323
323
|
};
|
|
324
324
|
/**
|
|
325
325
|
* Project an aex envelope to a strict AG-UI event so an off-the-shelf
|
|
326
|
-
* AG-UI client can consume an aex
|
|
326
|
+
* AG-UI client can consume an aex start with no glue. This is the
|
|
327
327
|
* client-side projection the SDK exposes.
|
|
328
328
|
*/
|
|
329
329
|
export declare function toAGUI(e: AexEvent): AguiEvent;
|