@aexhq/sdk 0.33.1 → 0.34.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 +19 -27
- package/dist/_contracts/operations.d.ts +2 -54
- package/dist/_contracts/operations.js +2 -87
- package/dist/_contracts/run-config.d.ts +19 -13
- package/dist/_contracts/run-config.js +6 -33
- package/dist/_contracts/run-unit.d.ts +1 -33
- package/dist/_contracts/run-unit.js +2 -21
- package/dist/_contracts/runtime-sizes.d.ts +2 -2
- package/dist/_contracts/runtime-sizes.js +2 -2
- package/dist/_contracts/status.d.ts +2 -2
- package/dist/_contracts/status.js +3 -0
- package/dist/_contracts/submission.d.ts +22 -18
- package/dist/_contracts/submission.js +60 -42
- package/dist/agents-md.d.ts +5 -5
- package/dist/agents-md.js +7 -7
- package/dist/agents-md.js.map +1 -1
- package/dist/asset-upload.d.ts +4 -4
- package/dist/asset-upload.js +4 -4
- package/dist/bundle.d.ts +2 -2
- package/dist/bundle.js +2 -2
- package/dist/cli.mjs +354 -12982
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +218 -386
- package/dist/client.js +347 -645
- package/dist/client.js.map +1 -1
- package/dist/data-tools.d.ts +25 -22
- package/dist/data-tools.js +75 -62
- package/dist/data-tools.js.map +1 -1
- package/dist/fetch-archive.js +16 -16
- package/dist/fetch-archive.js.map +1 -1
- package/dist/file.d.ts +5 -5
- package/dist/file.js +7 -7
- package/dist/file.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +14 -13
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts +4 -4
- package/dist/mcp-server.js +4 -4
- package/dist/proxy-endpoint.d.ts +4 -4
- package/dist/proxy-endpoint.js +1 -1
- package/dist/secret.d.ts +8 -8
- package/dist/secret.js +8 -8
- package/dist/secret.js.map +1 -1
- package/dist/skill-tool.d.ts +102 -0
- package/dist/skill-tool.js +190 -0
- package/dist/skill-tool.js.map +1 -0
- package/dist/tool.d.ts +1 -1
- package/dist/tool.js +3 -3
- package/dist/tool.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/cleanup.md +3 -3
- package/docs/concepts/agent-tools.md +6 -25
- package/docs/concepts/composition.md +15 -12
- package/docs/concepts/providers-and-runtimes.md +3 -3
- package/docs/concepts/runs.md +27 -22
- package/docs/credentials.md +52 -84
- package/docs/defaults.md +6 -6
- package/docs/events.md +65 -44
- package/docs/limits-and-quotas.md +3 -4
- package/docs/mcp.md +3 -3
- package/docs/networking.md +8 -8
- package/docs/outputs.md +44 -40
- package/docs/provider-runtime-capabilities.md +1 -1
- package/docs/public-surface.json +2 -2
- package/docs/quickstart.md +20 -10
- package/docs/run-config.md +12 -14
- package/docs/run-record.md +8 -8
- package/docs/secrets.md +16 -26
- package/docs/skills.md +55 -110
- package/docs/vision-skills.md +29 -40
- package/examples/chat-corpus.ts +8 -9
- package/package.json +1 -1
- package/dist/skill.d.ts +0 -149
- package/dist/skill.js +0 -198
- package/dist/skill.js.map +0 -1
package/docs/credentials.md
CHANGED
|
@@ -8,30 +8,28 @@ aex treats provider keys, MCP credentials, and proxy endpoint auth as per-run
|
|
|
8
8
|
credentials. Reusable env secrets are documented separately in
|
|
9
9
|
[Secrets](secrets.md).
|
|
10
10
|
|
|
11
|
-
The caller passes a workspace-scoped SDK token and the provider key inline on every `
|
|
11
|
+
The caller passes a workspace-scoped SDK token and the provider key inline on every `openSession` / `run` call. aex holds the bundle in run-scoped custody for the session lifecycle and attempts terminal cleanup/revocation for the aex-controlled references. MCP credentials and proxy endpoint auth values travel the same way.
|
|
12
12
|
|
|
13
|
-
A
|
|
14
|
-
key for it. Keys are supplied per-provider so a
|
|
13
|
+
A session selects one upstream `provider` (default `anthropic`) and must carry a BYOK
|
|
14
|
+
key for it. Keys are supplied per-provider so a session can also hold keys for the
|
|
15
15
|
**other** providers its subagents may use:
|
|
16
16
|
|
|
17
17
|
| Field | Required secret |
|
|
18
18
|
| --- | --- |
|
|
19
|
-
| Provider API keys | `
|
|
19
|
+
| Provider API keys | `apiKeys` (top-level, keyed by provider) |
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
// The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
deepseek: process.env.DEEPSEEK_API_KEY! // for a cross-provider subagent
|
|
27
|
-
}
|
|
22
|
+
// The session's own provider key, plus extra keys its subagents can use.
|
|
23
|
+
apiKeys: {
|
|
24
|
+
anthropic: process.env.ANTHROPIC_API_KEY!, // the session's provider
|
|
25
|
+
deepseek: process.env.DEEPSEEK_API_KEY! // for a cross-provider subagent
|
|
28
26
|
}
|
|
29
27
|
```
|
|
30
28
|
|
|
31
29
|
A `subagent` spawned with a different-family model **inherits the parent's keys
|
|
32
|
-
server-side** from the
|
|
33
|
-
container. If the parent holds no key for the child's provider, the child
|
|
34
|
-
|
|
30
|
+
server-side** from the session's vaulted bundle — the keys never transit the
|
|
31
|
+
container. If the parent holds no key for the child's provider, the child is
|
|
32
|
+
rejected with `parent_missing_provider_key`.
|
|
35
33
|
|
|
36
34
|
MCP credential types:
|
|
37
35
|
|
|
@@ -50,64 +48,46 @@ For managed-runtime runs, aex injects the matching BYOK provider key at the host
|
|
|
50
48
|
|
|
51
49
|
Some skills need to call non-MCP HTTP services (e.g. Stripe, internal APIs). Embedding the credential in the skill content puts the raw secret on disk in the agent container and in the model's context — both prompt-injection-readable.
|
|
52
50
|
|
|
53
|
-
The platform's managed HTTP proxy is the agent-first alternative.
|
|
51
|
+
The platform's managed HTTP proxy is the agent-first alternative. Declare each endpoint with a `ProxyEndpoint.*` constructor: the instance carries the non-secret **policy** (hashed for idempotency) and its **auth token** together at the call site. The SDK splits the token into the vaulted secrets channel server-side (not hashed, so key rotation does not collapse onto a stale run), and the raw credential value never enters the container.
|
|
54
52
|
|
|
55
53
|
```ts
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
validateProxyAuth,
|
|
60
|
-
buildPlatformAllowedHosts
|
|
61
|
-
} from "@aexhq/sdk";
|
|
62
|
-
|
|
63
|
-
const aex = new AgentExecutor({
|
|
54
|
+
import { Aex, Models, ProxyEndpoint } from "@aexhq/sdk";
|
|
55
|
+
|
|
56
|
+
const aex = new Aex({
|
|
64
57
|
apiToken: "ant_..."
|
|
65
58
|
});
|
|
66
59
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
respectRetryAfter: true
|
|
86
|
-
}
|
|
60
|
+
const stripe = ProxyEndpoint.bearer({
|
|
61
|
+
name: "stripe",
|
|
62
|
+
baseUrl: "https://api.stripe.com",
|
|
63
|
+
token: process.env.STRIPE_API_KEY!,
|
|
64
|
+
allowMethods: ["GET", "POST"],
|
|
65
|
+
allowPathPrefixes: ["/v1/charges", "/v1/refunds"],
|
|
66
|
+
maxRequestBytes: 65_536,
|
|
67
|
+
maxResponseBytes: 65_536,
|
|
68
|
+
timeoutMs: 10_000,
|
|
69
|
+
responseMode: "headers_only",
|
|
70
|
+
retry: {
|
|
71
|
+
maxAttempts: 3,
|
|
72
|
+
initialDelayMs: 250,
|
|
73
|
+
maxDelayMs: 5000,
|
|
74
|
+
jitter: "full",
|
|
75
|
+
retryOnStatuses: [408, 425, 429, 500, 502, 503, 504],
|
|
76
|
+
retryOnMethods: ["GET", "HEAD"],
|
|
77
|
+
respectRetryAfter: true
|
|
87
78
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const proxyEndpointAuth = [
|
|
91
|
-
{
|
|
92
|
-
name: "stripe",
|
|
93
|
-
value: { type: "bearer", token: process.env.STRIPE_API_KEY! }
|
|
94
|
-
}
|
|
95
|
-
] as const;
|
|
96
|
-
|
|
97
|
-
// Fail fast at submission time when policy and auth disagree.
|
|
98
|
-
validateProxyAuth(proxyEndpoints, proxyEndpointAuth);
|
|
79
|
+
});
|
|
99
80
|
|
|
100
|
-
|
|
81
|
+
await aex.run({
|
|
101
82
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
102
|
-
|
|
103
|
-
proxyEndpoints,
|
|
104
|
-
|
|
105
|
-
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! },
|
|
106
|
-
proxyEndpointAuth
|
|
107
|
-
}
|
|
83
|
+
message: "…",
|
|
84
|
+
proxyEndpoints: [stripe],
|
|
85
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
108
86
|
});
|
|
109
87
|
```
|
|
110
88
|
|
|
89
|
+
The five constructors — `ProxyEndpoint.none` / `bearer` / `header` / `basic` / `query` — put the auth secret on the same call as the policy, so any drift (wrong `responseMode`, misnamed auth field) is a TypeScript error at the call site instead of an HTTP 400 a round-trip later.
|
|
90
|
+
|
|
111
91
|
Inside the run container, every session has the platform CLI mounted at `/mnt/session/uploads/aex/aex` (a Bun-compatible ESM bundle) and a manifest at `/mnt/session/uploads/aex/index.json` describing the declared endpoints. The skill invokes the CLI through `bun` (the mount has no execute permission so direct invocation fails with `bad interpreter: Permission denied`):
|
|
112
92
|
|
|
113
93
|
```bash
|
|
@@ -123,40 +103,28 @@ Retries are declaration-based. Add `retry` to the endpoint policy when safe for
|
|
|
123
103
|
|
|
124
104
|
#### Keyless upstreams (`authShape: { type: "none" }`)
|
|
125
105
|
|
|
126
|
-
For public APIs that take no credential (Wikimedia Commons, Internet Archive, Library of Congress, NASA Images, NARA, GDELT, etc.), declare the endpoint with `
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
const proxyEndpoints = [
|
|
130
|
-
{
|
|
131
|
-
name: "wikimedia",
|
|
132
|
-
baseUrl: "https://commons.wikimedia.org",
|
|
133
|
-
authShape: { type: "none" },
|
|
134
|
-
allowMethods: ["GET"],
|
|
135
|
-
allowPathPrefixes: ["/wiki/", "/w/api.php"]
|
|
136
|
-
}
|
|
137
|
-
] as const;
|
|
138
|
-
|
|
139
|
-
const runId = await aex.submit({
|
|
140
|
-
model: Models.CLAUDE_HAIKU_4_5,
|
|
141
|
-
prompt: "…",
|
|
142
|
-
proxyEndpoints,
|
|
143
|
-
secrets: { apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! } }
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The keyless endpoint still routes through the aex managed proxy: every call is allow-listed, audited, and redacted. The hosted proxy injects no `Authorization` header and no query-string credential. Shipping a `proxyEndpointAuth` entry for a `none`-shape endpoint is rejected at submission time. Equivalent class-based form:
|
|
106
|
+
For public APIs that take no credential (Wikimedia Commons, Internet Archive, Library of Congress, NASA Images, NARA, GDELT, etc.), declare the endpoint with `ProxyEndpoint.none(...)` — it produces only a declaration, no auth token:
|
|
148
107
|
|
|
149
108
|
```ts
|
|
150
109
|
import { ProxyEndpoint } from "@aexhq/sdk";
|
|
151
110
|
|
|
152
|
-
ProxyEndpoint.none({
|
|
111
|
+
const wikimedia = ProxyEndpoint.none({
|
|
153
112
|
name: "wikimedia",
|
|
154
113
|
baseUrl: "https://commons.wikimedia.org",
|
|
155
114
|
allowMethods: ["GET"],
|
|
156
115
|
allowPathPrefixes: ["/wiki/", "/w/api.php"]
|
|
157
116
|
});
|
|
117
|
+
|
|
118
|
+
await aex.run({
|
|
119
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
120
|
+
message: "…",
|
|
121
|
+
proxyEndpoints: [wikimedia],
|
|
122
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
123
|
+
});
|
|
158
124
|
```
|
|
159
125
|
|
|
126
|
+
The keyless endpoint still routes through the aex managed proxy: every call is allow-listed, audited, and redacted. The hosted proxy injects no `Authorization` header and no query-string credential.
|
|
127
|
+
|
|
160
128
|
`bun /mnt/session/uploads/aex/aex --help` reads endpoint details from `/mnt/session/uploads/aex/index.json`. Runs that do not declare any `proxyEndpoints` still have the CLI and an empty manifest mounted, so agents never need to introspect whether the surface exists.
|
|
161
129
|
|
|
162
130
|
### Networking
|
|
@@ -174,4 +142,4 @@ const allowedHosts = buildPlatformAllowedHosts({
|
|
|
174
142
|
|
|
175
143
|
### Secrets are always explicit at the call site
|
|
176
144
|
|
|
177
|
-
There is no `defaultSecrets` and no client-held secret state. Every `
|
|
145
|
+
There is no `defaultSecrets` and no client-held secret state. Every `openSession` / `run` call carries its own credentials at the call site: the top-level `apiKeys` map (one provider key, plus any subagent keys), MCP auth on each `McpServer` instance, and proxy auth on each `ProxyEndpoint` instance. This is the agent-first invariant: the credentials being used on any given call are visible in the same code that opens the session.
|
package/docs/defaults.md
CHANGED
|
@@ -21,9 +21,9 @@ For the hard ceilings and who can raise them, see
|
|
|
21
21
|
|
|
22
22
|
| Option | Default | How to override | Source |
|
|
23
23
|
| --- | --- | --- | --- |
|
|
24
|
-
| `timeout` (run deadline) | 1 hour | Per-
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
24
|
+
| `timeout` (run deadline) | 1 hour | Per-session via `overrides.timeout` (e.g. `"30m"`, `"2h"`), clamped to the run-timeout floor/ceiling. | `RUN_DEFAULT_TIMEOUT_MS` |
|
|
25
|
+
| `runtime` (machine size) | `shared-0.25x-1gb` — 0.25 vCPU, 1 GB | Per-session via `runtime` (use `Sizes.*` in TypeScript). | `RUN_DEFAULT_RUNTIME_SIZE` |
|
|
26
|
+
| `overrides.maxSpendUsd` (per-session spend cap) | None — no spend cap (the session is still bounded by its `timeout` and any workspace-level cap) | Per-session via `overrides.maxSpendUsd` (a positive USD amount); the session is stopped once its spend would exceed the cap. | — |
|
|
27
27
|
|
|
28
28
|
## Tools
|
|
29
29
|
|
|
@@ -51,15 +51,15 @@ For the hard ceilings and who can raise them, see
|
|
|
51
51
|
|
|
52
52
|
| Option | Default | How to override | Source |
|
|
53
53
|
| --- | --- | --- | --- |
|
|
54
|
-
| Output link / signed-URL TTL | 300 seconds (5 minutes) at the storage layer; `
|
|
54
|
+
| Output link / signed-URL TTL | 300 seconds (5 minutes) at the storage layer; `session.outputs().link(...)` defaults to `"1h"` | Per-call via `expiresSeconds` (storage) or `expiresIn` on `session.outputs().link` / `session.outputs().fetch`. | `REQUEST_PRESIGN_URL_DEFAULT_TTL_SECONDS` |
|
|
55
55
|
| Event-stream connection ticket TTL | 60 seconds | Per-mint via the `ttlMs` argument. | `REQUEST_TICKET_DEFAULT_TTL_MS` |
|
|
56
56
|
|
|
57
57
|
## Subagents
|
|
58
58
|
|
|
59
59
|
| Option | Default | How to override | Source |
|
|
60
60
|
| --- | --- | --- | --- |
|
|
61
|
-
| Concurrent child runs per lineage root | 1000 (live, non-terminal child runs) |
|
|
62
|
-
| Max subagent depth | 5 |
|
|
61
|
+
| Concurrent child runs per lineage root | 1000 (live, non-terminal child runs) | Platform default (subagents run in-process; no public per-run override). Hard ceiling 4096. | `RUN_DEFAULT_MAX_CONCURRENT_CHILD_RUNS` |
|
|
62
|
+
| Max subagent depth | 5 | Platform default (subagents run in-process; no public per-run override). | `RUN_MAX_PUBLIC_SUBAGENT_DEPTH` |
|
|
63
63
|
|
|
64
64
|
## Workspace
|
|
65
65
|
|
package/docs/events.md
CHANGED
|
@@ -12,15 +12,20 @@ tool-approval hook.
|
|
|
12
12
|
|
|
13
13
|
## Two ways to consume events
|
|
14
14
|
|
|
15
|
+
A session's reads and streams are grouped under accessor sub-resources:
|
|
16
|
+
`session.events()` owns the event timeline, `session.messages()` owns the decoded
|
|
17
|
+
assistant text, and `session.outputs()` owns the captured files. Reach a verb by
|
|
18
|
+
chaining it off the accessor.
|
|
19
|
+
|
|
15
20
|
```ts
|
|
16
21
|
// Pull a snapshot of every event captured so far.
|
|
17
|
-
const events = await
|
|
22
|
+
const events = await session.events().list();
|
|
18
23
|
```
|
|
19
24
|
|
|
20
25
|
```ts
|
|
21
26
|
// Stream the RunEvent snapshot shape: yields each event once, stops when the
|
|
22
|
-
//
|
|
23
|
-
for await (const event of
|
|
27
|
+
// session parks. Backed by polling the aex events endpoint.
|
|
28
|
+
for await (const event of session.events().stream({ intervalMs: 1000 })) {
|
|
24
29
|
if (event.type === "TEXT_MESSAGE_CONTENT") {
|
|
25
30
|
// ...
|
|
26
31
|
}
|
|
@@ -30,42 +35,57 @@ for await (const event of aex.stream(runId, { intervalMs: 1000 })) {
|
|
|
30
35
|
For the canonical event envelope, use the coordinator WebSocket stream:
|
|
31
36
|
|
|
32
37
|
```ts
|
|
33
|
-
for await (const event of
|
|
38
|
+
for await (const event of session.events().streamEnvelopes({ from: 0 })) {
|
|
34
39
|
console.log(event.sequence, event.type, event.source);
|
|
35
40
|
}
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
`streamEnvelopes()` uses a short-lived ticket minted by the hosted API, then subscribes directly to the per-
|
|
43
|
+
`session.events().streamEnvelopes()` uses a short-lived ticket minted by the hosted API, then subscribes directly to the per-session coordinator. Subscribe means read-from-cursor plus tail: reconnects resume from the last sequence.
|
|
44
|
+
|
|
45
|
+
## Assistant text
|
|
46
|
+
|
|
47
|
+
To collect just the agent's assistant messages, use the `messages()` accessor —
|
|
48
|
+
`list()` returns every decoded `AssistantTextEntry` oldest-first, and
|
|
49
|
+
`last()`/`first()` return one entry (or `undefined` when empty). Read `.text`
|
|
50
|
+
for the string:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const lastText = (await session.messages().last())?.text;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`decodeAssistantText`, `textOf`, and `summarizeRunTrace` remain exported as the
|
|
57
|
+
power-user escape hatch over a raw `RunEvent` list, but "get the last message"
|
|
58
|
+
is now `await session.messages().last()`.
|
|
39
59
|
|
|
40
60
|
The CLI mirrors the same surface:
|
|
41
61
|
|
|
42
62
|
```bash
|
|
43
|
-
aex events <
|
|
44
|
-
aex events <
|
|
45
|
-
aex tail <
|
|
46
|
-
aex inspect <
|
|
47
|
-
aex wait <
|
|
63
|
+
aex events <session-id> --api-token … [--aex-url …] # snapshot (polling)
|
|
64
|
+
aex events <session-id> --follow [--timeout 8m] --api-token … [--aex-url …] # stream until the session parks (polling)
|
|
65
|
+
aex tail <session-id> [--json] [--filter <type|source>] [--logs] [--settle] [--timeout 8m] --api-token … # live, human-readable, over the WS envelope stream
|
|
66
|
+
aex inspect <session-id> [--json] [--filter <type|source>] [--logs] [--timeout 8m] --api-token … # one-shot full timeline + jump-to-failure + cost/usage
|
|
67
|
+
aex wait <session-id> [--timeout 8m] [--interval 2s] --api-token … # block, print final session
|
|
48
68
|
```
|
|
49
69
|
|
|
50
70
|
`aex tail` and `aex inspect` consume the same coordinator WebSocket envelope
|
|
51
|
-
stream as `streamEnvelopes()` (replay-from-cursor + tail +
|
|
52
|
-
so they are the low-latency equivalents of
|
|
53
|
-
is the raw-NDJSON escape hatch; `--filter`
|
|
54
|
-
(`TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START`, …)
|
|
55
|
-
a `RUN_ERROR` is surfaced as a jump-to-failure
|
|
56
|
-
a settle-consistent full timeline, and a
|
|
57
|
-
|
|
58
|
-
(Bun or Node ≥ 22).
|
|
59
|
-
|
|
60
|
-
`aex wait` is the host mirror of `
|
|
61
|
-
it polls until the
|
|
62
|
-
|
|
63
|
-
and `3` when `--timeout` elapses first
|
|
64
|
-
`run --follow` uses the same exit-`3`
|
|
65
|
-
suffixes or a bare millisecond integer.
|
|
66
|
-
|
|
67
|
-
Both surfaces observe the same events. A subscriber attached after
|
|
68
|
-
|
|
71
|
+
stream as `session.events().streamEnvelopes()` (replay-from-cursor + tail +
|
|
72
|
+
exactly-once resume), so they are the low-latency equivalents of
|
|
73
|
+
`events --follow`'s polling. `--json` is the raw-NDJSON escape hatch; `--filter`
|
|
74
|
+
keeps only the named AG-UI types (`TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START`, …)
|
|
75
|
+
or sources (`agent`/`runtime`/…); a `RUN_ERROR` is surfaced as a jump-to-failure
|
|
76
|
+
line. `aex inspect` adds a header, a settle-consistent full timeline, and a
|
|
77
|
+
cost/usage footer. Both exit `0` parked cleanly / `1` error park / `3` timeout.
|
|
78
|
+
They need a global `WebSocket` (Bun or Node ≥ 22).
|
|
79
|
+
|
|
80
|
+
`aex wait` is the host mirror of `session.wait()`:
|
|
81
|
+
it polls until the session parks and prints the final `Session` record. Exit `0`
|
|
82
|
+
when the session parked cleanly (`idle`/`suspended`), `1` for any other park
|
|
83
|
+
(`error` / a non-clean terminal status), and `3` when `--timeout` elapses first
|
|
84
|
+
(a `--timeout` on `events --follow` / `run --follow` uses the same exit-`3`
|
|
85
|
+
convention). Durations accept `ms`/`s`/`m`/`h` suffixes or a bare millisecond integer.
|
|
86
|
+
|
|
87
|
+
Both surfaces observe the same events. A subscriber attached after a session
|
|
88
|
+
message is accepted replays the events it missed, then continues live.
|
|
69
89
|
|
|
70
90
|
## Session turn events
|
|
71
91
|
|
|
@@ -90,36 +110,37 @@ collected session turn. The returned `runId` is the session id.
|
|
|
90
110
|
|
|
91
111
|
## Terminal events vs. the run record
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
A session turn emits a terminal **event** — `RUN_FINISHED`
|
|
94
114
|
(success) or `RUN_ERROR` — when
|
|
95
115
|
the agent's stream ends. This is an AG-UI *render-complete* signal: the runner
|
|
96
|
-
emits it **before** aex commits the authoritative
|
|
97
|
-
issued the instant you observe `RUN_FINISHED` can still
|
|
98
|
-
for a moment. Treat the terminal event as the
|
|
99
|
-
signal — **not** a read-consistency barrier.
|
|
116
|
+
emits it **before** aex commits the authoritative session record, so an
|
|
117
|
+
`aex.sessions.get(id)` issued the instant you observe `RUN_FINISHED` can still
|
|
118
|
+
read a non-parked status for a moment. Treat the terminal event as the
|
|
119
|
+
lowest-latency "stop the spinner" signal — **not** a read-consistency barrier.
|
|
100
120
|
|
|
101
121
|
Two facts make this easy to work with:
|
|
102
122
|
|
|
103
123
|
- **Outputs are already durable at the terminal event.** The runner uploads every
|
|
104
|
-
output before it emits the terminal event, and `
|
|
124
|
+
output before it emits the terminal event, and `session.outputs().list()` / downloads
|
|
105
125
|
read object storage directly — so the moment you see `RUN_FINISHED` the outputs
|
|
106
126
|
are complete and readable.
|
|
107
|
-
- **The
|
|
127
|
+
- **The session _record_ settles a beat later.** To read the authoritative status
|
|
108
128
|
consistently, don't key off the terminal event — use one of:
|
|
109
129
|
|
|
110
130
|
```ts
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
|
|
131
|
+
// Session record path: send a turn, then wait for the session to park.
|
|
132
|
+
const session = await aex.openSession(config);
|
|
133
|
+
await session.send("Continue the task.").done();
|
|
134
|
+
const record = await session.wait(); // the parked session record
|
|
114
135
|
```
|
|
115
136
|
|
|
116
137
|
```ts
|
|
117
138
|
// Live events AND a settle-consistent end: the iterator keeps reading past
|
|
118
139
|
// RUN_FINISHED until the post-mirror barrier, so the record is terminal when it ends.
|
|
119
|
-
for await (const event of
|
|
140
|
+
for await (const event of session.events().streamEnvelopes({ settleConsistent: true })) {
|
|
120
141
|
// render events live…
|
|
121
142
|
}
|
|
122
|
-
const
|
|
143
|
+
const settled = await aex.sessions.get(session.id); // guaranteed terminal here
|
|
123
144
|
```
|
|
124
145
|
|
|
125
146
|
Under the hood the coordinator broadcasts one `aex.run.settled` CUSTOM event as a
|
|
@@ -129,10 +150,10 @@ run's last stream event, immediately after the durable record commits.
|
|
|
129
150
|
|
|
130
151
|
## Temporary event archive links
|
|
131
152
|
|
|
132
|
-
For terminal runs, `
|
|
153
|
+
For terminal runs, `session.events().archiveLink(options?)` returns a temporary direct URL to `events.jsonl`, the same redacted customer-visible event export used by `session.events().download()`.
|
|
133
154
|
|
|
134
155
|
```ts
|
|
135
|
-
const link = await
|
|
156
|
+
const link = await session.events().archiveLink({ expiresIn: "1h" });
|
|
136
157
|
const response = await fetch(link.url);
|
|
137
158
|
const jsonl = await response.text();
|
|
138
159
|
```
|
|
@@ -141,7 +162,7 @@ const jsonl = await response.text();
|
|
|
141
162
|
|
|
142
163
|
## Event shape
|
|
143
164
|
|
|
144
|
-
Events are typed as the discriminated `RunEvent` union for compatibility and as the versioned coordinator envelope for live consumers. aex records raw runtime/provider payloads **after** secret redaction and structural sanitization, so the bytes you see never contain the provider key, MCP credentials, or proxy bearer that were supplied
|
|
165
|
+
Events are typed as the discriminated `RunEvent` union for compatibility and as the versioned coordinator envelope for live consumers. aex records raw runtime/provider payloads **after** secret redaction and structural sanitization, so the bytes you see never contain the provider key, MCP credentials, or proxy bearer that were supplied when the session was opened.
|
|
145
166
|
|
|
146
167
|
## Typed helpers
|
|
147
168
|
|
|
@@ -166,7 +187,7 @@ import {
|
|
|
166
187
|
|
|
167
188
|
All guards test the `type` discriminant at runtime. `isTextMessage`,
|
|
168
189
|
`isToolCallStart`, `isToolCallResult`, and `isRunFinished` operate on the loose
|
|
169
|
-
`RunEvent` snapshot (`
|
|
190
|
+
`RunEvent` snapshot (`session.events().list()` / `RunResult.events`) and additionally NARROW
|
|
170
191
|
`event.data` to the fields that event type carries — e.g. inside
|
|
171
192
|
`if (isTextMessage(e))`, `e.data.text` is typed `string`. The lifecycle/channel
|
|
172
193
|
guards (`isRunStarted`, `isRunError`, `isCustom`, `isLog`, …) operate on the
|
|
@@ -29,10 +29,9 @@ And whether you can **raise** it: per-run option, per-plan, or no.
|
|
|
29
29
|
| Maximum run timeout | 6 hours | aex policy | Per plan (billing-driven) | `RUN_MAX_TIMEOUT_MS` |
|
|
30
30
|
| Minimum run timeout | 1 minute | aex policy | No (floor) | `RUN_MIN_TIMEOUT_MS` |
|
|
31
31
|
| Per-call exec timeout (default) | 30 minutes | aex policy | Per-call via the tool call's `timeoutMs` | `RUN_DEFAULT_EXEC_TIMEOUT_MS` |
|
|
32
|
-
| Post-hook timeout (default) | 60 minutes | aex policy | Per-run via the hook's `timeoutMs` | `RUN_DEFAULT_POST_HOOK_TIMEOUT_MS` |
|
|
33
32
|
| MCP connect timeout (default) | 30 seconds | aex policy | Per-port via `connectTimeoutMs` | `RUN_DEFAULT_MCP_CONNECT_TIMEOUT_MS` |
|
|
34
33
|
| MCP call timeout (default) | 30 minutes | aex policy | Per-port via `callTimeoutMs` | `RUN_DEFAULT_MCP_CALL_TIMEOUT_MS` |
|
|
35
|
-
| Per-
|
|
34
|
+
| Per-session spend cap | None by default; when set, the session is stopped once its spend would exceed the cap | aex policy | Per-session via `overrides.maxSpendUsd` (a positive USD amount) | — |
|
|
36
35
|
|
|
37
36
|
### Output capture (per run)
|
|
38
37
|
|
|
@@ -61,8 +60,8 @@ silently lost.
|
|
|
61
60
|
|
|
62
61
|
| Limit | Value | Source | Raisable? | Constant |
|
|
63
62
|
| --- | --- | --- | --- | --- |
|
|
64
|
-
| Max subagent depth (
|
|
65
|
-
| Concurrent child runs per lineage root | 1000 live (non-terminal); hard ceiling 4096 | aex policy |
|
|
63
|
+
| Max subagent depth (`subagent` tool) | 5 (a depth-5 lineage may not spawn deeper) | aex policy | No public per-run override (subagents run in-process) | `RUN_MAX_PUBLIC_SUBAGENT_DEPTH` |
|
|
64
|
+
| Concurrent child runs per lineage root | 1000 live (non-terminal); hard ceiling 4096 | aex policy | No public per-run override (subagents run in-process) | `RUN_DEFAULT_MAX_CONCURRENT_CHILD_RUNS` |
|
|
66
65
|
|
|
67
66
|
### Retention (per run)
|
|
68
67
|
|
package/docs/mcp.md
CHANGED
|
@@ -13,7 +13,7 @@ Rules:
|
|
|
13
13
|
- Tool policy must be configured before session start.
|
|
14
14
|
- Enabled MCP tools use `always_allow` provider permissions.
|
|
15
15
|
- `always_ask` is not used by aex MVP.
|
|
16
|
-
- Bearer/OAuth-style auth is
|
|
16
|
+
- Bearer/OAuth-style auth is carried by the `McpServer` instance (its `headers`); the SDK splits it into the vaulted secrets channel server-side.
|
|
17
17
|
|
|
18
18
|
Use allowlists for sensitive servers whenever possible.
|
|
19
19
|
|
|
@@ -29,8 +29,8 @@ For ingestion-style tools that return large JSON blobs (search results,
|
|
|
29
29
|
catalogue dumps, bulk reads), use the **CLI-as-skill + managed proxy**
|
|
30
30
|
pattern instead of MCP:
|
|
31
31
|
|
|
32
|
-
1. Package the upstream as a `
|
|
33
|
-
with its bash tool.
|
|
32
|
+
1. Package the upstream as a skill-tool (`Tools.fromSkillDir` /
|
|
33
|
+
`Tools.fromSkillUrl`) — a CLI binary the agent invokes with its bash tool.
|
|
34
34
|
2. Route every upstream HTTPS call through a per-run `ProxyEndpoint`
|
|
35
35
|
(audit, byte caps, budget enforcement).
|
|
36
36
|
3. Have the CLI write the full payload to the session filesystem. By default,
|
package/docs/networking.md
CHANGED
|
@@ -47,21 +47,21 @@ without you listing them.
|
|
|
47
47
|
### TypeScript
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
|
-
import {
|
|
50
|
+
import { Aex, Models, Providers } from "@aexhq/sdk";
|
|
51
51
|
|
|
52
|
-
const aex = new
|
|
52
|
+
const aex = new Aex({ apiToken: process.env.AEX_API_TOKEN! });
|
|
53
53
|
|
|
54
|
-
await aex.
|
|
54
|
+
await aex.run({
|
|
55
55
|
provider: Providers.ANTHROPIC,
|
|
56
56
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
57
|
-
|
|
57
|
+
message: "Fetch the public status page and summarize it.",
|
|
58
58
|
environment: {
|
|
59
59
|
networking: {
|
|
60
60
|
mode: "limited",
|
|
61
61
|
allowedHosts: ["api.example.com", "status.example.com"]
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
-
|
|
64
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
65
65
|
});
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -92,11 +92,11 @@ whenever you can name the hosts — it gives the run a stable, auditable, least-
|
|
|
92
92
|
privilege egress surface (it is the tighter posture, not the default).
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
|
-
await aex.
|
|
95
|
+
await aex.run({
|
|
96
96
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
97
|
-
|
|
97
|
+
message: "Research the topic across the open web.",
|
|
98
98
|
environment: { networking: { mode: "open" } },
|
|
99
|
-
|
|
99
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
100
100
|
});
|
|
101
101
|
```
|
|
102
102
|
|