@aexhq/sdk 0.36.0 → 0.37.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 +1 -1
- package/dist/_contracts/event-envelope.d.ts +22 -1
- package/dist/_contracts/event-envelope.js +26 -2
- package/dist/_contracts/event-stream-client.js +7 -1
- package/dist/_contracts/operations.d.ts +30 -1
- package/dist/_contracts/operations.js +54 -1
- package/dist/_contracts/run-config.d.ts +1 -1
- package/dist/_contracts/run-unit.d.ts +12 -0
- package/dist/_contracts/run-unit.js +55 -0
- package/dist/_contracts/runtime-sizes.d.ts +2 -2
- package/dist/_contracts/runtime-sizes.js +5 -5
- package/dist/_contracts/runtime-types.d.ts +98 -0
- package/dist/_contracts/submission.d.ts +4 -4
- package/dist/cli.mjs +554 -69
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +40 -1
- package/dist/client.js +90 -5
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/authentication.md +92 -0
- package/docs/billing.md +112 -0
- package/docs/concepts/agent-tools.md +4 -4
- package/docs/concepts/providers-and-runtimes.md +4 -1
- package/docs/concepts/runs.md +2 -1
- package/docs/concepts/subagents.md +85 -0
- package/docs/credentials.md +27 -3
- package/docs/defaults.md +9 -7
- package/docs/errors.md +132 -0
- package/docs/events.md +36 -23
- package/docs/limits-and-quotas.md +29 -13
- package/docs/limits.md +2 -2
- package/docs/mcp.md +1 -1
- package/docs/networking.md +68 -42
- package/docs/outputs.md +4 -3
- package/docs/public-surface.json +1 -1
- package/docs/quickstart.md +9 -6
- package/docs/run-config.md +1 -1
- package/docs/secrets.md +5 -0
- package/docs/webhooks.md +132 -0
- package/package.json +1 -1
package/docs/mcp.md
CHANGED
|
@@ -20,7 +20,7 @@ Use allowlists for sensitive servers whenever possible.
|
|
|
20
20
|
## Large-payload responses
|
|
21
21
|
|
|
22
22
|
aex is a session dispatcher, not an MCP runtime. We intentionally do
|
|
23
|
-
**not** interpose on the transport between
|
|
23
|
+
**not** interpose on the transport between the model and an upstream MCP
|
|
24
24
|
server, so we cannot elide MCP responses or write them to the session
|
|
25
25
|
filesystem on the user's behalf. Anything an MCP tool returns lands
|
|
26
26
|
directly in the model's context.
|
package/docs/networking.md
CHANGED
|
@@ -4,44 +4,64 @@ title: Networking
|
|
|
4
4
|
|
|
5
5
|
# Networking
|
|
6
6
|
|
|
7
|
-
A run executes your agent's code in a sandbox
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
A run executes your agent's code in a sandbox with **no direct route to the
|
|
8
|
+
internet**. Outbound traffic is governed by **two layers**:
|
|
9
|
+
|
|
10
|
+
1. **The per-run policy (`environment.networking`)** — enforced by the agent
|
|
11
|
+
runtime *inside the run*. It applies to the standard proxy path every normal
|
|
12
|
+
HTTP client uses (see below) and can only *narrow* what the run may reach.
|
|
13
|
+
2. **The platform ceiling** — a fixed, platform-managed egress boundary every
|
|
14
|
+
connection ultimately traverses. It allows the hosts aex itself manages
|
|
15
|
+
(model providers, built-in tool endpoints, package registries, and related
|
|
16
|
+
well-known development hosts such as `github.com`) and enforces a fixed SSRF
|
|
17
|
+
deny-list: loopback, link-local, cloud-metadata, and other private ranges are
|
|
18
|
+
always blocked, including hostnames that resolve to those ranges.
|
|
19
|
+
|
|
20
|
+
Honest boundary statement: the per-run `allowedHosts` policy is enforced by the
|
|
21
|
+
run's own runtime on the standard proxy path — it is **not** yet enforced at
|
|
22
|
+
the platform proxy layer. A subprocess that deliberately bypasses the standard
|
|
23
|
+
proxy environment (a raw socket / raw CONNECT) is bounded by the **platform
|
|
24
|
+
ceiling** rather than by the per-run list. Per-run enforcement at the platform
|
|
25
|
+
proxy layer is planned; until it ships, treat `allowedHosts` as a strong
|
|
26
|
+
default-path control and an auditable statement of intent, not a hard isolation
|
|
27
|
+
boundary against adversarial code inside the run.
|
|
28
|
+
|
|
29
|
+
**Default posture.** A run that does not set `environment.networking` runs in
|
|
30
|
+
`open` mode: its own code may reach anything within the platform ceiling with
|
|
31
|
+
no allowlist required. Use `environment.networking` to *narrow* that surface
|
|
32
|
+
when you want a tighter, auditable egress posture. Code cannot widen the
|
|
33
|
+
ceiling from inside the container.
|
|
20
34
|
|
|
21
35
|
## Paths that always work
|
|
22
36
|
|
|
23
|
-
These reach the network over managed paths and are **not** subject to
|
|
37
|
+
These reach the network over managed platform paths and are **not** subject to
|
|
24
38
|
`environment.networking`, so you never list their hosts:
|
|
25
39
|
|
|
26
40
|
- The model / provider call for the run (and its subagents).
|
|
27
|
-
- The built-in `web_search` and `web_fetch` tools
|
|
28
|
-
-
|
|
41
|
+
- The built-in `web_search` and `web_fetch` tools. They run over a managed,
|
|
42
|
+
SSRF-guarded server-side path, which is why they can reach arbitrary public
|
|
43
|
+
URLs even though your own code is bounded by the ceiling.
|
|
44
|
+
- Remote MCP servers you declare in `mcpServers` — MCP traffic rides a managed
|
|
45
|
+
path; see [MCP](mcp.md).
|
|
29
46
|
- The package registries for any `environment.packages` you declare (pip → PyPI,
|
|
30
47
|
apt → the distribution mirrors). Declaring a package implicitly allows the
|
|
31
48
|
registry it installs from.
|
|
32
49
|
|
|
33
|
-
`environment.networking` governs the **other** case:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`code_execution`, or a third-party SDK.
|
|
50
|
+
`environment.networking` governs the **other** case: outbound that your own
|
|
51
|
+
code makes — a `curl` in the `bash` tool, a `requests`/`urllib` call in Python,
|
|
52
|
+
a `fetch` in `code_execution`, or a third-party SDK.
|
|
37
53
|
|
|
38
54
|
## Restrict a run to an allowlist
|
|
39
55
|
|
|
40
56
|
Set `mode: "limited"` and list exactly the hosts your code is allowed to reach.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
The run's runtime enforces the list on the standard proxy path: a connection to
|
|
58
|
+
a host that is neither on the list nor one of the always-allowed paths above is
|
|
59
|
+
refused before it leaves the run. Package-registry hosts implied by
|
|
60
|
+
`environment.packages` are appended automatically so installs keep working.
|
|
61
|
+
|
|
62
|
+
Note that `allowedHosts` narrows *within* the platform ceiling — listing a host
|
|
63
|
+
does not by itself make it reachable if the platform ceiling does not carry it.
|
|
64
|
+
If your run needs a host the ceiling blocks, contact support.
|
|
45
65
|
|
|
46
66
|
### TypeScript
|
|
47
67
|
|
|
@@ -75,11 +95,11 @@ visible at the same call site as the code that needs it.
|
|
|
75
95
|
## Open mode
|
|
76
96
|
|
|
77
97
|
`open` is the default: a run that omits `environment.networking` already runs in
|
|
78
|
-
open mode. Set `mode: "open"` explicitly when you want to be unambiguous
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
whenever you can name the hosts — it gives the run a stable, auditable,
|
|
82
|
-
privilege egress surface (it is the tighter posture, not the default).
|
|
98
|
+
open mode. Set `mode: "open"` explicitly when you want to be unambiguous. Open
|
|
99
|
+
mode applies no per-run allowlist — the run's own code may reach anything the
|
|
100
|
+
platform ceiling allows, still subject to the SSRF deny-list. Prefer `limited`
|
|
101
|
+
whenever you can name the hosts — it gives the run a stable, auditable,
|
|
102
|
+
least-privilege egress surface (it is the tighter posture, not the default).
|
|
83
103
|
|
|
84
104
|
```ts
|
|
85
105
|
await aex.run({
|
|
@@ -90,6 +110,9 @@ await aex.run({
|
|
|
90
110
|
});
|
|
91
111
|
```
|
|
92
112
|
|
|
113
|
+
(Web research like the example above flows through the managed `web_search` /
|
|
114
|
+
`web_fetch` path, which is not ceiling-bounded.)
|
|
115
|
+
|
|
93
116
|
## Transparent for normal HTTP clients
|
|
94
117
|
|
|
95
118
|
You write ordinary code — there is no per-request proxy configuration and no
|
|
@@ -101,7 +124,7 @@ that honors it — `curl`, Python `requests` / `urllib`, `pip`, `npm`, Node
|
|
|
101
124
|
```bash
|
|
102
125
|
# In the agent's shell, against a limited run that allows api.example.com:
|
|
103
126
|
curl -sS https://api.example.com/v1/status # works
|
|
104
|
-
curl -sS https://other-host.example #
|
|
127
|
+
curl -sS https://other-host.example # refused (not in allowlist)
|
|
105
128
|
```
|
|
106
129
|
|
|
107
130
|
You also do **not** need to install any certificate. The platform manages the
|
|
@@ -110,20 +133,23 @@ your client succeeds without extra setup.
|
|
|
110
133
|
|
|
111
134
|
## Limitations and gotchas
|
|
112
135
|
|
|
113
|
-
- **
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
- **The per-run policy is enforced by the run's runtime, not at the platform
|
|
137
|
+
proxy.** Clients that honor the standard proxy environment (almost all HTTP
|
|
138
|
+
tooling) are held to the `allowedHosts` list. A subprocess that deliberately
|
|
139
|
+
ignores the proxy environment and opens a raw connection is **not** held to
|
|
140
|
+
the per-run list — it is bounded by the platform ceiling (the aex-managed
|
|
141
|
+
provider/tool/registry host set) and the SSRF deny-list instead. Per-run
|
|
142
|
+
enforcement at the platform proxy layer is planned.
|
|
117
143
|
- **A client that hard-bypasses the standard environment may fail to connect.**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
environment the runtime provides (most libraries do by default), rather than
|
|
124
|
-
overriding it.
|
|
144
|
+
A client that ignores the proxy environment, pins or replaces its certificate
|
|
145
|
+
trust store, or speaks a non-HTTP protocol over a raw socket can hit a wall
|
|
146
|
+
even for a host you allowed. The fix is to let the client use the standard
|
|
147
|
+
proxy and certificate environment the runtime provides (most libraries do by
|
|
148
|
+
default), rather than overriding it.
|
|
125
149
|
- **`allowedHosts` only applies in `limited` mode.** It is ignored in `open`
|
|
126
|
-
mode, where the SSRF deny-list
|
|
150
|
+
mode, where the platform ceiling and the SSRF deny-list are the gates.
|
|
151
|
+
- **`allowedHosts` cannot exceed the platform ceiling.** It narrows; it never
|
|
152
|
+
widens. A listed host outside the ceiling still fails.
|
|
127
153
|
|
|
128
154
|
For credentialed HTTP calls, pass the credential as an `environment.secrets`
|
|
129
155
|
entry and let your code use its normal HTTP client. For remote tool servers, see
|
package/docs/outputs.md
CHANGED
|
@@ -163,9 +163,10 @@ const stream = response.body;
|
|
|
163
163
|
|
|
164
164
|
| Run state | Behaviour |
|
|
165
165
|
| --- | --- |
|
|
166
|
-
| `
|
|
167
|
-
| `provider_running`, mid-session / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after
|
|
168
|
-
| `
|
|
166
|
+
| `queued` / `claiming` / `provisioning` | `metadata/run.json` reflects the early state; `events/` and `outputs/` are typically empty. |
|
|
167
|
+
| `provider_running`, mid-session / `capturing_outputs` / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after the session parks for the complete set. |
|
|
168
|
+
| `idle` / `suspended` (parked between turns) | The complete archive for every turn sent so far; a later turn appends to it. |
|
|
169
|
+
| `succeeded` / `failed` / `timed_out` / `cancelled` | The complete typed event archive + all captured outputs. |
|
|
169
170
|
|
|
170
171
|
## `outputs.allowedDirs` — override capture roots
|
|
171
172
|
|
package/docs/public-surface.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"label": "Alpha testing",
|
|
8
8
|
"description": "Access is limited to invited testers while we harden the hosted runtime, dashboard, and SDK workflows."
|
|
9
9
|
},
|
|
10
|
-
"installCommand": "
|
|
10
|
+
"installCommand": "npm i @aexhq/sdk",
|
|
11
11
|
"examples": {
|
|
12
12
|
"typescriptLines": [
|
|
13
13
|
"import { Aex, Models, Sizes } from \"@aexhq/sdk\";",
|
package/docs/quickstart.md
CHANGED
|
@@ -7,16 +7,18 @@ title: Quickstart
|
|
|
7
7
|
## 1. Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
npm i @aexhq/sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
This installs the TypeScript SDK exports and the bundled `aex` CLI.
|
|
14
14
|
|
|
15
15
|
## 2. Set credentials
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
aex is currently in **invite-only beta**: workspaces and API tokens are issued
|
|
18
|
+
by the aex team — contact <support@aex.dev> for beta access. Once you have
|
|
19
|
+
access, create a quickstart SDK token with `runs:read`, `runs:write`, and
|
|
20
|
+
`outputs:read` in the dashboard at <https://aex.dev>. The examples also need
|
|
21
|
+
your BYOK provider key for the model you choose. For the Claude examples below:
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
export AEX_API_TOKEN="<your-aex-token>"
|
|
@@ -28,7 +30,7 @@ export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
|
|
|
28
30
|
```ts
|
|
29
31
|
import { Aex, Models, Sizes } from "@aexhq/sdk";
|
|
30
32
|
|
|
31
|
-
const aex = new Aex(
|
|
33
|
+
const aex = new Aex(process.env.AEX_API_TOKEN!);
|
|
32
34
|
|
|
33
35
|
const session = await aex.openSession({
|
|
34
36
|
model: Models.CLAUDE_HAIKU_4_5,
|
|
@@ -108,6 +110,7 @@ aex run \
|
|
|
108
110
|
## Add capabilities
|
|
109
111
|
|
|
110
112
|
- Add files, skills, AGENTS.md, MCP servers, packages, and networking controls with [Composition](concepts/composition.md).
|
|
111
|
-
-
|
|
113
|
+
- Delegate bounded sub-tasks to child runs with [Subagents](concepts/subagents.md).
|
|
114
|
+
- Get notified when a run finishes with [Webhooks](webhooks.md).
|
|
112
115
|
- Narrow output capture or download individual files with [Outputs](outputs.md).
|
|
113
116
|
- Check supported providers and models in the [provider/runtime capability matrix](provider-runtime-capabilities.md).
|
package/docs/run-config.md
CHANGED
|
@@ -16,7 +16,7 @@ Allowed fields:
|
|
|
16
16
|
- `metadata` - non-secret structured metadata.
|
|
17
17
|
- `overrides` - `{ idleTtl?, timeout?, maxSpendUsd? }`. `timeout` is an optional session deadline (e.g. `"30m"`, `"2h"`); `maxSpendUsd` stops the session once its spend would exceed the cap (see [Limits & quotas](limits-and-quotas.md)).
|
|
18
18
|
|
|
19
|
-
`message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, so they too are SDK-code options rather than config fields. Subagents
|
|
19
|
+
`message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, so they too are SDK-code options rather than config fields. Subagents are session-internal (the in-run `subagent` tool — see [Subagents](concepts/subagents.md)); there is no `parentRunId` option. The wire contract carries a per-run `limits` object (the exported `RunLimits` type: `maxConcurrentChildRuns`, `maxSubagentDepth`, `maxSpendUsd`), but the session surface exposes only its spend dial — set it with `overrides.maxSpendUsd`; the subagent depth/breadth dials are not settable per-session today and take the platform defaults.
|
|
20
20
|
|
|
21
21
|
Secrets never live in run config. Pass provider keys through the top-level
|
|
22
22
|
`apiKeys` map and runtime secrets through `environment.secrets` in the SDK, or
|
package/docs/secrets.md
CHANGED
|
@@ -88,6 +88,11 @@ const metadata = await aex.secrets.get("serper-api-key");
|
|
|
88
88
|
|
|
89
89
|
## Inject A Workspace Secret Into A Run
|
|
90
90
|
|
|
91
|
+
> **Availability note:** workspace-secret `Secret.ref(...)` injection requires
|
|
92
|
+
> the next platform deploy — on the current hosted plane the referenced
|
|
93
|
+
> variable can resolve empty inside the run. Per-run `Secret.value(...)`
|
|
94
|
+
> secrets are unaffected. This note will be removed once the deploy lands.
|
|
95
|
+
|
|
91
96
|
Reference workspace secrets with `Secret.ref(name)`. The value resolves
|
|
92
97
|
server-side and is injected as the named environment variable.
|
|
93
98
|
|
package/docs/webhooks.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Webhooks
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Webhooks
|
|
6
|
+
|
|
7
|
+
aex can notify your endpoint when a run finishes. Webhooks are **per-run**: you
|
|
8
|
+
pass a callback URL with the submission, and the platform delivers exactly one
|
|
9
|
+
`run.finished` event to it when the run reaches its terminal state.
|
|
10
|
+
|
|
11
|
+
> **Availability note:** terminal webhook delivery for session-based runs
|
|
12
|
+
> requires the next platform deploy — on the current hosted plane a session
|
|
13
|
+
> run's webhook may not fire. This note will be removed once the deploy lands.
|
|
14
|
+
|
|
15
|
+
## Register a callback
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Aex, Models } from "@aexhq/sdk";
|
|
19
|
+
|
|
20
|
+
const aex = new Aex(process.env.AEX_API_TOKEN!);
|
|
21
|
+
|
|
22
|
+
const session = await aex.openSession({
|
|
23
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
24
|
+
webhook: { url: "https://hooks.example.com/aex" },
|
|
25
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
aex run \
|
|
31
|
+
--api-token "$AEX_API_TOKEN" \
|
|
32
|
+
--anthropic-api-key "$ANTHROPIC_API_KEY" \
|
|
33
|
+
--model claude-haiku-4-5 \
|
|
34
|
+
--prompt "Write the report." \
|
|
35
|
+
--webhook https://hooks.example.com/aex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The URL must be `https`. The callback URL is an operational concern, not part
|
|
39
|
+
of the submission fingerprint: retrying the same submission with the same
|
|
40
|
+
idempotency key but a different callback URL never conflicts.
|
|
41
|
+
|
|
42
|
+
## What gets delivered
|
|
43
|
+
|
|
44
|
+
One POST carrying the terminal `run.finished` event, sent at the
|
|
45
|
+
settle-consistent barrier — when you receive it, the run record is already
|
|
46
|
+
terminal and its outputs are complete and readable. The body is built once at
|
|
47
|
+
settle, frozen, and re-sent byte-identical on every retry or manual redelivery:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"specversion": "1.0",
|
|
52
|
+
"id": "<delivery id — matches the webhook-id header>",
|
|
53
|
+
"source": "aex",
|
|
54
|
+
"type": "run.finished",
|
|
55
|
+
"subject": "<the run id>",
|
|
56
|
+
"time": "2026-07-02T12:34:56.000Z",
|
|
57
|
+
"data": {
|
|
58
|
+
"runId": "<the run id>",
|
|
59
|
+
"status": "succeeded",
|
|
60
|
+
"terminalAt": "2026-07-02T12:34:56.000Z",
|
|
61
|
+
"reason": null,
|
|
62
|
+
"failureClass": null,
|
|
63
|
+
"costTelemetry": { "billedCostUsd": 0.42 }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`reason` / `failureClass` carry the failure detail on non-success terminals;
|
|
69
|
+
`costTelemetry` is present when the billed cost is known.
|
|
70
|
+
|
|
71
|
+
## Verify deliveries
|
|
72
|
+
|
|
73
|
+
Deliveries are signed [Standard Webhooks](https://www.standardwebhooks.com/)
|
|
74
|
+
style: HMAC-SHA256 over `` `${webhook-id}.${webhook-timestamp}.${rawBody}` ``,
|
|
75
|
+
sent in three headers — `webhook-id` (stable across retries; your dedupe key),
|
|
76
|
+
`webhook-timestamp` (unix seconds), and `webhook-signature` (a space-delimited
|
|
77
|
+
list of `v1,<base64>` entries).
|
|
78
|
+
|
|
79
|
+
The signing key is a per-workspace secret. Reveal it (it is created on first
|
|
80
|
+
use) with either surface:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
aex webhooks secret --api-token "$AEX_API_TOKEN" # prints whsec_...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { whsec } = await aex.webhookSigningSecret();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Verify inbound requests with the exported `verifyAexWebhook` — pure Web Crypto,
|
|
91
|
+
identical under Bun and Node, and interoperable with the `standardwebhooks`
|
|
92
|
+
reference library:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { verifyAexWebhook } from "@aexhq/sdk";
|
|
96
|
+
|
|
97
|
+
// In your HTTP handler — use the EXACT raw body bytes, never re-serialize.
|
|
98
|
+
const ok = await verifyAexWebhook({
|
|
99
|
+
rawBody,
|
|
100
|
+
headers: request.headers,
|
|
101
|
+
secret: process.env.AEX_WEBHOOK_SECRET! // the whsec_... value
|
|
102
|
+
});
|
|
103
|
+
if (!ok) return new Response("bad signature", { status: 401 });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`verifyAexWebhook` rejects stale timestamps (default tolerance 300 seconds) and
|
|
107
|
+
compares signatures constant-time. It accepts every `v1` entry in the signature
|
|
108
|
+
list, so it is already rotation-ready — but **server-side rotation of the
|
|
109
|
+
signing secret is not available yet**: `aex webhooks secret` reveals or creates
|
|
110
|
+
the one workspace secret and there is no rotate endpoint today. If your secret
|
|
111
|
+
is compromised, contact <support@aex.dev>.
|
|
112
|
+
|
|
113
|
+
## Delivery ledger and redelivery
|
|
114
|
+
|
|
115
|
+
Each run keeps a delivery ledger — attempts, last status code, and last error:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const deliveries = await session.webhooks().list();
|
|
119
|
+
await session.webhooks().redeliver(deliveries[0]!.id);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
aex deliveries <session-id> --api-token "$AEX_API_TOKEN"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Redelivery re-sends the frozen payload with the **same** `webhook-id`, so a
|
|
127
|
+
consumer that dedupes on `webhook-id` handles retries, redeliveries, and
|
|
128
|
+
at-least-once delivery uniformly. An empty ledger means the run carried no
|
|
129
|
+
`webhook` or has not reached a terminal state yet.
|
|
130
|
+
|
|
131
|
+
For the terminal-event mechanics behind delivery timing, see
|
|
132
|
+
[Events](events.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexhq/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "TypeScript SDK for running autonomous agent sessions across providers (Anthropic, OpenAI, DeepSeek, Gemini, Mistral) behind one interface.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|