@aexhq/sdk 0.35.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.
Files changed (72) hide show
  1. package/README.md +17 -16
  2. package/dist/_contracts/event-envelope.d.ts +22 -1
  3. package/dist/_contracts/event-envelope.js +26 -2
  4. package/dist/_contracts/event-stream-client.js +7 -1
  5. package/dist/_contracts/index.d.ts +3 -4
  6. package/dist/_contracts/index.js +1 -4
  7. package/dist/_contracts/operations.d.ts +31 -1
  8. package/dist/_contracts/operations.js +64 -1
  9. package/dist/_contracts/run-config.d.ts +2 -4
  10. package/dist/_contracts/run-config.js +2 -7
  11. package/dist/_contracts/run-trace.d.ts +0 -86
  12. package/dist/_contracts/run-trace.js +1 -184
  13. package/dist/_contracts/run-unit.d.ts +14 -25
  14. package/dist/_contracts/run-unit.js +56 -2
  15. package/dist/_contracts/runtime-manifest.d.ts +1 -1
  16. package/dist/_contracts/runtime-security-profile.d.ts +0 -2
  17. package/dist/_contracts/runtime-security-profile.js +0 -9
  18. package/dist/_contracts/runtime-sizes.d.ts +2 -2
  19. package/dist/_contracts/runtime-sizes.js +5 -5
  20. package/dist/_contracts/runtime-types.d.ts +123 -4
  21. package/dist/_contracts/stable.d.ts +1 -1
  22. package/dist/_contracts/stable.js +1 -1
  23. package/dist/_contracts/submission.d.ts +8 -76
  24. package/dist/_contracts/submission.js +5 -472
  25. package/dist/cli.mjs +574 -511
  26. package/dist/cli.mjs.sha256 +1 -1
  27. package/dist/client.d.ts +69 -25
  28. package/dist/client.js +338 -68
  29. package/dist/client.js.map +1 -1
  30. package/dist/index.d.ts +8 -16
  31. package/dist/index.js +5 -17
  32. package/dist/index.js.map +1 -1
  33. package/dist/secret.d.ts +2 -2
  34. package/dist/secret.js +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/docs/authentication.md +92 -0
  38. package/docs/billing.md +112 -0
  39. package/docs/concepts/agent-tools.md +4 -4
  40. package/docs/concepts/composition.md +8 -14
  41. package/docs/concepts/providers-and-runtimes.md +4 -1
  42. package/docs/concepts/runs.md +2 -1
  43. package/docs/concepts/subagents.md +85 -0
  44. package/docs/credentials.md +78 -96
  45. package/docs/defaults.md +9 -15
  46. package/docs/errors.md +132 -0
  47. package/docs/events.md +44 -32
  48. package/docs/limits-and-quotas.md +30 -17
  49. package/docs/limits.md +4 -8
  50. package/docs/mcp.md +5 -6
  51. package/docs/networking.md +75 -59
  52. package/docs/outputs.md +4 -7
  53. package/docs/public-surface.json +4 -4
  54. package/docs/quickstart.md +12 -13
  55. package/docs/run-config.md +7 -4
  56. package/docs/secrets.md +6 -1
  57. package/docs/skills.md +3 -3
  58. package/docs/vision-skills.md +52 -101
  59. package/docs/webhooks.md +132 -0
  60. package/examples/feature-tour.ts +4 -21
  61. package/package.json +1 -1
  62. package/dist/_contracts/proxy-protocol.d.ts +0 -305
  63. package/dist/_contracts/proxy-protocol.js +0 -297
  64. package/dist/_contracts/proxy-validation.d.ts +0 -19
  65. package/dist/_contracts/proxy-validation.js +0 -51
  66. package/dist/data-tools.d.ts +0 -82
  67. package/dist/data-tools.js +0 -251
  68. package/dist/data-tools.js.map +0 -1
  69. package/dist/proxy-endpoint.d.ts +0 -131
  70. package/dist/proxy-endpoint.js +0 -144
  71. package/dist/proxy-endpoint.js.map +0 -1
  72. package/examples/chat-corpus.ts +0 -84
@@ -1,305 +0,0 @@
1
- /**
2
- * Wire-protocol version. Bumped on any breaking change to the request or
3
- * response shape. The CLI sends this in the `X-Aex-Proxy-Protocol`
4
- * header on every request; the BFF rejects mismatches with HTTP 426
5
- * `unsupported_protocol`.
6
- *
7
- * Bumps are coordinated: CLI and BFF release together, the hosted API
8
- * bundles the matching CLI artifact, and the e2e suite runs both with
9
- * the new version.
10
- */
11
- export declare const PROXY_PROTOCOL_VERSION: "1";
12
- /**
13
- * Streaming named-proxy protocol. A client that sends `"2"` in
14
- * {@link PROXY_PROTOCOL_HEADER} opts into the streamed response path: the
15
- * hosted API pipes the upstream body back unbuffered (no base64, no
16
- * full-body JSON envelope) and carries the envelope metadata
17
- * in the `x-aex-proxy-*` response headers below instead of a JSON envelope.
18
- *
19
- * Additive: `"1"` stays valid and keeps the buffered
20
- * {@link ProxyResponseEnvelope}. Old runners keep working; new runners
21
- * stream. The version is on the request header so the hosted API can serve
22
- * both shapes without a coordinated CLI/BFF release.
23
- */
24
- export declare const PROXY_PROTOCOL_VERSION_V2: "2";
25
- export declare const PROXY_PROTOCOL_HEADER = "x-aex-proxy-protocol";
26
- /**
27
- * Response headers for the streamed (v2) named-proxy path. The hosted API sets
28
- * these BEFORE it starts streaming the upstream body, so the client can
29
- * reconstruct the same fields the v1 {@link ProxyResponseEnvelope} carried
30
- * without buffering. All values are plain strings; numeric fields are
31
- * decimal, booleans are `"true"`/`"false"`.
32
- */
33
- export declare const PROXY_RESP_STATUS_HEADER = "x-aex-proxy-status";
34
- export declare const PROXY_RESP_MODE_HEADER = "x-aex-proxy-effective-mode";
35
- /**
36
- * `"true"` when the cap forced truncation, `"false"` when the full body
37
- * fit, `"unknown"` when the upstream omitted `content-length` and the body
38
- * happened to reach the cap (can't distinguish exact-fit from over-cap
39
- * without buffering). See the streaming byte-cap note in proxy-routes.ts.
40
- */
41
- export declare const PROXY_RESP_TRUNCATED_HEADER = "x-aex-proxy-truncated";
42
- /** JSON object of lowercase upstream header names → values (mode-filtered). */
43
- export declare const PROXY_RESP_UPSTREAM_HEADERS_HEADER = "x-aex-proxy-upstream-headers";
44
- /**
45
- * Default `User-Agent` the proxy attaches to every outbound request when
46
- * the caller did not supply one via `allowHeaders`. Some upstreams reject
47
- * requests that arrive without a meaningful UA — notably the Wikimedia
48
- * family (Wikidata, Wikipedia, Wikimedia Commons), whose policy requires
49
- * a contactable identifier and otherwise returns HTTP 403 with a
50
- * `Please identify your user agent` body.
51
- *
52
- * Callers can override per request by listing `user-agent` in their
53
- * endpoint's `allowHeaders` and setting it on the proxy call; the
54
- * default only fires when nothing was forwarded.
55
- *
56
- * See <https://meta.wikimedia.org/wiki/User-Agent_policy>.
57
- */
58
- export declare const PROXY_DEFAULT_USER_AGENT = "aex-proxy/1.0 (+https://aex.dev/contact)";
59
- export declare const PROXY_METHOD_HEADER = "x-aex-method";
60
- export declare const PROXY_PATH_HEADER = "x-aex-path";
61
- export declare const PROXY_QUERY_HEADER = "x-aex-query";
62
- export declare const PROXY_HEADERS_HEADER = "x-aex-headers";
63
- export declare const PROXY_RESPONSE_MODE_HEADER = "x-aex-response-mode";
64
- export declare const PROXY_ALLOWED_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
65
- export type ProxyMethod = (typeof PROXY_ALLOWED_METHODS)[number];
66
- export declare const PROXY_RESPONSE_MODES: readonly ["status_only", "headers_only", "full"];
67
- export type ProxyResponseMode = (typeof PROXY_RESPONSE_MODES)[number];
68
- export declare const PROXY_RETRY_JITTERS: readonly ["full", "none"];
69
- export type ProxyRetryJitter = (typeof PROXY_RETRY_JITTERS)[number];
70
- export interface ProxyRetryPolicy {
71
- /** Total attempts, including the initial request. */
72
- readonly maxAttempts?: number;
73
- readonly initialDelayMs?: number;
74
- readonly maxDelayMs?: number;
75
- readonly jitter?: ProxyRetryJitter;
76
- readonly retryOnStatuses?: readonly number[];
77
- readonly retryOnMethods?: readonly ProxyMethod[];
78
- readonly respectRetryAfter?: boolean;
79
- }
80
- export type ResolvedProxyRetryPolicy = Required<ProxyRetryPolicy>;
81
- export declare const PROXY_RETRY_POLICY_DEFAULTS: ResolvedProxyRetryPolicy;
82
- export declare function resolveProxyRetryPolicy(policy: ProxyRetryPolicy): ResolvedProxyRetryPolicy;
83
- /**
84
- * Returns the narrower of the two response modes (lower width wins).
85
- * Pure function so the CLI and BFF can both call it without import cycles.
86
- */
87
- export declare function narrowResponseMode(policy: ProxyResponseMode, requested: ProxyResponseMode): ProxyResponseMode;
88
- /**
89
- * Error codes returned by the proxy route. Stable strings — the CLI
90
- * matches against them in scripts. Adding a new code is non-breaking;
91
- * removing or renaming an existing code requires a protocol bump.
92
- */
93
- export declare const PROXY_ERROR_CODES: readonly ["unsupported_protocol", "unauthorized", "endpoint_not_found", "policy_denied", "rate_limited", "ssrf_denied", "upstream_timeout", "upstream_error", "exceeded_cap", "bad_request", "internal_error"];
94
- export type ProxyErrorCode = (typeof PROXY_ERROR_CODES)[number];
95
- /**
96
- * Shape of the JSON written to the per-run manifest mounted inside
97
- * the container (`/mnt/session/uploads/aex/index.json`).
98
- *
99
- * Always present (every run), regardless of whether any proxy endpoints
100
- * were declared. With zero endpoints, `endpoints` is `[]` and
101
- * `proxyBaseUrl` is `null` — this keeps `aex --help` working
102
- * uniformly and makes the always-on surface observable in tests.
103
- *
104
- * Auth values NEVER appear in this file. The file is mounted into the
105
- * container; treat it as world-readable from the agent's perspective.
106
- */
107
- export interface ProxyIndexFile {
108
- readonly protocolVersion: typeof PROXY_PROTOCOL_VERSION;
109
- readonly runId: string;
110
- readonly proxyBaseUrl: string | null;
111
- readonly endpoints: readonly ProxyIndexEntry[];
112
- }
113
- export interface ProxyIndexEntry {
114
- readonly name: string;
115
- readonly baseUrl: string;
116
- readonly authShape: ProxyAuthShape;
117
- readonly allowMethods: readonly ProxyMethod[];
118
- readonly allowPathPrefixes: readonly string[];
119
- readonly allowHeaders: readonly string[];
120
- readonly responseMode: ProxyResponseMode;
121
- readonly maxRequestBytes: number;
122
- readonly maxResponseBytes: number;
123
- readonly timeoutMs: number;
124
- readonly retry?: ResolvedProxyRetryPolicy;
125
- }
126
- /**
127
- * Default caps for a proxy endpoint when the submission doesn't specify one.
128
- * Lives in the protocol module (next to the index-file shape) so
129
- * {@link buildProxyIndexFile} can fill every optional cap with a concrete
130
- * value; the submission parser re-exports it.
131
- *
132
- * `maxResponseBytes: 0` means UNLIMITED — the v2 path streams the upstream body
133
- * unbuffered (O(1) memory regardless of size), so there is no cap to apply by
134
- * default. A customer can opt into a per-response truncation cap by setting a
135
- * positive value. There is no cumulative per-run call/byte budget: it needed a
136
- * per-call counter on the hot path and only existed to bound memory, which
137
- * streaming already does. The platform records named-proxy request bytes,
138
- * response bytes, attempts, and retries as run-log usage telemetry only; no
139
- * pricing/charging model is derived here.
140
- */
141
- export declare const PROXY_ENDPOINT_DEFAULTS: {
142
- readonly allowHeaders: readonly string[];
143
- readonly responseMode: ProxyResponseMode;
144
- readonly maxRequestBytes: number;
145
- readonly maxResponseBytes: 0;
146
- readonly timeoutMs: number;
147
- };
148
- /**
149
- * Non-secret endpoint policy the index builder consumes. Structurally a
150
- * subset of `PlatformProxyEndpoint` (submission.ts) — declared here so the
151
- * protocol module stays free of an import cycle with the submission parser.
152
- */
153
- export interface ProxyEndpointPolicy {
154
- readonly name: string;
155
- readonly baseUrl: string;
156
- readonly authShape: ProxyAuthShape;
157
- readonly allowMethods: readonly ProxyMethod[];
158
- readonly allowPathPrefixes: readonly string[];
159
- readonly allowHeaders?: readonly string[];
160
- readonly responseMode?: ProxyResponseMode;
161
- readonly maxRequestBytes?: number;
162
- readonly maxResponseBytes?: number;
163
- readonly timeoutMs?: number;
164
- readonly retry?: ProxyRetryPolicy;
165
- }
166
- export interface BuildProxyIndexFileInput {
167
- readonly runId: string;
168
- /**
169
- * Hosted API origin that serves `/api/runs/:runId/proxy/:endpointName`.
170
- * When unset
171
- * (or empty) the run has no reachable proxy plane and `proxyBaseUrl`
172
- * resolves to `null`.
173
- */
174
- readonly proxyPublicBaseUrl?: string;
175
- readonly endpoints?: readonly ProxyEndpointPolicy[];
176
- }
177
- /**
178
- * Build the per-run {@link ProxyIndexFile} mounted into the container at
179
- * `/mnt/session/uploads/aex/index.json`. Pure: applies
180
- * {@link PROXY_ENDPOINT_DEFAULTS} so every optional cap is concrete, and
181
- * carries ONLY the non-secret endpoint policy — auth values never appear.
182
- *
183
- * ALWAYS emits a file (the always-on surface). With zero endpoints OR no
184
- * `proxyPublicBaseUrl`, `proxyBaseUrl` is `null` and `endpoints` is `[]`.
185
- * Otherwise `proxyBaseUrl` is `<trimmed base>/api/runs/<runId>/proxy`, the
186
- * prefix the in-container runtime bridge appends `/<endpointName>` to (proxy.ts).
187
- */
188
- export declare function buildProxyIndexFile(input: BuildProxyIndexFileInput): ProxyIndexFile;
189
- /**
190
- * Structural description of how the upstream endpoint expects auth.
191
- * The actual auth value lives in the run's Vault bundle under
192
- * `secrets.proxyEndpointAuth[i].value` and is never reflected back
193
- * into the container or index file.
194
- *
195
- * The `none` variant declares an upstream that takes no auth (public
196
- * APIs like Wikimedia Commons or NASA Images). It still routes through
197
- * the proxy for unified egress and audit, but
198
- * carries no `proxyEndpointAuth[]` entry and the BFF injects no
199
- * header or query value.
200
- */
201
- export type ProxyAuthShape = {
202
- readonly type: "none";
203
- } | {
204
- readonly type: "bearer";
205
- } | {
206
- readonly type: "basic";
207
- } | {
208
- readonly type: "header";
209
- readonly name: string;
210
- } | {
211
- readonly type: "query";
212
- readonly name: string;
213
- };
214
- export type ProxyAuthType = ProxyAuthShape["type"];
215
- /**
216
- * Header name (lowercase) that an upstream auth shape uses as its
217
- * carrier. Returns `undefined` for query-based and keyless auth.
218
- *
219
- * Used by the submission parser to forbid `allowHeaders` from listing
220
- * the auth header (avoids leaks via caller-supplied headers), and by
221
- * the proxy route to strip any caller header that would collide with
222
- * the auth carrier at request time.
223
- */
224
- export declare function authShapeHeaderName(shape: ProxyAuthShape): string | undefined;
225
- /**
226
- * Query-string key that an upstream query-based auth shape uses as its
227
- * carrier. Returns `undefined` for non-query shapes (including "none").
228
- */
229
- export declare function authShapeQueryName(shape: ProxyAuthShape): string | undefined;
230
- /**
231
- * Inbound request headers every Aex proxy plane STRIPS before
232
- * forwarding a runtime/runner request upstream. Three categories:
233
- *
234
- * - Credential carriers (`authorization`, `x-api-key`, `cookie`,
235
- * `proxy-authorization`) — these belong to Aex's own auth gate
236
- * (the per-run bearer) or to the caller, never the upstream. The
237
- * legitimate upstream credential is injected server-side from the
238
- * run's Vault bundle / endpoint auth shape AFTER this strip, so it is
239
- * never sourced from an inbound header.
240
- * - Hop-by-hop fields (RFC 7230 §6.1: `connection`, `keep-alive`,
241
- * `transfer-encoding`, `te`, `trailer`, `upgrade`, `expect`,
242
- * `proxy-authenticate`, `proxy-connection`) — must not survive a
243
- * proxy hop.
244
- * - Routing primitives a compromised runner could spoof to bypass an
245
- * upstream's IP allowlist / rate-limit (`host`, `content-length`,
246
- * `x-forwarded-*`, `x-real-ip`, `forwarded`).
247
- *
248
- * The platform API provider-proxy and the dashboard MCP proxy strip exactly
249
- * this set (both inject upstream auth separately — the provider key, or the
250
- * Vault MCP-bundle headers, applied AFTER the strip). The dashboard
251
- * customer HTTP proxy hard-denies this set MINUS `x-api-key`, because a
252
- * customer endpoint may legitimately declare `x-api-key` as its auth
253
- * carrier; it derives from this constant so the hop-by-hop + routing
254
- * entries never drift. Keeping the membership here is the single source of
255
- * truth that stops those surfaces diverging.
256
- */
257
- export declare const PROXY_STRIPPED_INBOUND_HEADERS: ReadonlySet<string>;
258
- /**
259
- * JSON body returned on a successful proxy call. The actual HTTP
260
- * response from the BFF to the CLI is always 200 once the BFF accepts
261
- * the request; the upstream's status/headers/body are reflected inside
262
- * this envelope so the CLI can decide what to write to stdout/stderr.
263
- */
264
- export interface ProxyResponseEnvelope {
265
- readonly endpointName: string;
266
- readonly upstreamStatus: number;
267
- /** Lowercase header names → values. Allowlist-filtered by the BFF. */
268
- readonly upstreamHeaders: Readonly<Record<string, string>>;
269
- /**
270
- * Base64-encoded upstream body. Present only when the effective
271
- * response mode is `full`. Truncated to `maxResponseBytes`; if the
272
- * upstream exceeded the cap, `truncated` is `true`.
273
- */
274
- readonly upstreamBodyBase64?: string;
275
- readonly truncated?: boolean;
276
- /**
277
- * Echoed back so the CLI can warn the agent when its requested mode
278
- * was clamped against the policy ceiling.
279
- */
280
- readonly effectiveResponseMode: ProxyResponseMode;
281
- readonly modeClamped: boolean;
282
- }
283
- /**
284
- * JSON body returned on any error. The CLI emits this verbatim on
285
- * stderr and exits non-zero. Audit row carries the same `code`.
286
- */
287
- export interface ProxyErrorBody {
288
- readonly error: ProxyErrorCode;
289
- /** Human-readable message. Never includes auth values. */
290
- readonly message: string;
291
- /**
292
- * Optional diagnostic fields. Always safe to surface — auth values
293
- * and full URLs are stripped at the BFF.
294
- */
295
- readonly endpointName?: string;
296
- readonly upstreamStatus?: number;
297
- /** Server-supplied protocol version on `unsupported_protocol`. */
298
- readonly serverProtocolVersion?: string;
299
- }
300
- /**
301
- * Status code → error code mapping used by the BFF to ensure the audit
302
- * row's error code and the HTTP response line up. Kept here so callers
303
- * can do a sanity check in tests.
304
- */
305
- export declare const PROXY_ERROR_HTTP_STATUS: Record<ProxyErrorCode, number>;
@@ -1,297 +0,0 @@
1
- // Wire format between the in-container runtime bridge (mounted at
2
- // `/mnt/session/uploads/aex/aex`, invoked through `bun`) and
3
- // the hosted API's named proxy route
4
- // (`POST /api/runs/:runId/proxy/:endpointName`).
5
- //
6
- // This module is the single source of truth for the request shape, the
7
- // response shape, the error-code enum, and the protocol version header.
8
- // CLI and BFF both import from here; drift becomes a build-time type error.
9
- //
10
- /**
11
- * Wire-protocol version. Bumped on any breaking change to the request or
12
- * response shape. The CLI sends this in the `X-Aex-Proxy-Protocol`
13
- * header on every request; the BFF rejects mismatches with HTTP 426
14
- * `unsupported_protocol`.
15
- *
16
- * Bumps are coordinated: CLI and BFF release together, the hosted API
17
- * bundles the matching CLI artifact, and the e2e suite runs both with
18
- * the new version.
19
- */
20
- export const PROXY_PROTOCOL_VERSION = "1";
21
- /**
22
- * Streaming named-proxy protocol. A client that sends `"2"` in
23
- * {@link PROXY_PROTOCOL_HEADER} opts into the streamed response path: the
24
- * hosted API pipes the upstream body back unbuffered (no base64, no
25
- * full-body JSON envelope) and carries the envelope metadata
26
- * in the `x-aex-proxy-*` response headers below instead of a JSON envelope.
27
- *
28
- * Additive: `"1"` stays valid and keeps the buffered
29
- * {@link ProxyResponseEnvelope}. Old runners keep working; new runners
30
- * stream. The version is on the request header so the hosted API can serve
31
- * both shapes without a coordinated CLI/BFF release.
32
- */
33
- export const PROXY_PROTOCOL_VERSION_V2 = "2";
34
- export const PROXY_PROTOCOL_HEADER = "x-aex-proxy-protocol";
35
- /**
36
- * Response headers for the streamed (v2) named-proxy path. The hosted API sets
37
- * these BEFORE it starts streaming the upstream body, so the client can
38
- * reconstruct the same fields the v1 {@link ProxyResponseEnvelope} carried
39
- * without buffering. All values are plain strings; numeric fields are
40
- * decimal, booleans are `"true"`/`"false"`.
41
- */
42
- export const PROXY_RESP_STATUS_HEADER = "x-aex-proxy-status";
43
- export const PROXY_RESP_MODE_HEADER = "x-aex-proxy-effective-mode";
44
- /**
45
- * `"true"` when the cap forced truncation, `"false"` when the full body
46
- * fit, `"unknown"` when the upstream omitted `content-length` and the body
47
- * happened to reach the cap (can't distinguish exact-fit from over-cap
48
- * without buffering). See the streaming byte-cap note in proxy-routes.ts.
49
- */
50
- export const PROXY_RESP_TRUNCATED_HEADER = "x-aex-proxy-truncated";
51
- /** JSON object of lowercase upstream header names → values (mode-filtered). */
52
- export const PROXY_RESP_UPSTREAM_HEADERS_HEADER = "x-aex-proxy-upstream-headers";
53
- /**
54
- * Default `User-Agent` the proxy attaches to every outbound request when
55
- * the caller did not supply one via `allowHeaders`. Some upstreams reject
56
- * requests that arrive without a meaningful UA — notably the Wikimedia
57
- * family (Wikidata, Wikipedia, Wikimedia Commons), whose policy requires
58
- * a contactable identifier and otherwise returns HTTP 403 with a
59
- * `Please identify your user agent` body.
60
- *
61
- * Callers can override per request by listing `user-agent` in their
62
- * endpoint's `allowHeaders` and setting it on the proxy call; the
63
- * default only fires when nothing was forwarded.
64
- *
65
- * See <https://meta.wikimedia.org/wiki/User-Agent_policy>.
66
- */
67
- export const PROXY_DEFAULT_USER_AGENT = "aex-proxy/1.0 (+https://aex.dev/contact)";
68
- export const PROXY_METHOD_HEADER = "x-aex-method";
69
- export const PROXY_PATH_HEADER = "x-aex-path";
70
- export const PROXY_QUERY_HEADER = "x-aex-query";
71
- export const PROXY_HEADERS_HEADER = "x-aex-headers";
72
- export const PROXY_RESPONSE_MODE_HEADER = "x-aex-response-mode";
73
- export const PROXY_ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
74
- export const PROXY_RESPONSE_MODES = ["status_only", "headers_only", "full"];
75
- export const PROXY_RETRY_JITTERS = ["full", "none"];
76
- export const PROXY_RETRY_POLICY_DEFAULTS = {
77
- maxAttempts: 3,
78
- initialDelayMs: 250,
79
- maxDelayMs: 5000,
80
- jitter: "full",
81
- retryOnStatuses: [408, 425, 429, 500, 502, 503, 504],
82
- retryOnMethods: ["GET", "HEAD"],
83
- respectRetryAfter: true
84
- };
85
- export function resolveProxyRetryPolicy(policy) {
86
- return {
87
- maxAttempts: policy.maxAttempts ?? PROXY_RETRY_POLICY_DEFAULTS.maxAttempts,
88
- initialDelayMs: policy.initialDelayMs ?? PROXY_RETRY_POLICY_DEFAULTS.initialDelayMs,
89
- maxDelayMs: policy.maxDelayMs ?? PROXY_RETRY_POLICY_DEFAULTS.maxDelayMs,
90
- jitter: policy.jitter ?? PROXY_RETRY_POLICY_DEFAULTS.jitter,
91
- retryOnStatuses: policy.retryOnStatuses ?? PROXY_RETRY_POLICY_DEFAULTS.retryOnStatuses,
92
- retryOnMethods: policy.retryOnMethods ?? PROXY_RETRY_POLICY_DEFAULTS.retryOnMethods,
93
- respectRetryAfter: policy.respectRetryAfter ?? PROXY_RETRY_POLICY_DEFAULTS.respectRetryAfter
94
- };
95
- }
96
- /**
97
- * Narrowing order: a request may only narrow below the policy ceiling.
98
- * `status_only` is narrowest, `full` is widest. The BFF computes
99
- * `narrowest(policyMode, headerMode)` and ignores escalation attempts,
100
- * auditing them as `mode_clamped`.
101
- */
102
- const RESPONSE_MODE_WIDTH = {
103
- status_only: 0,
104
- headers_only: 1,
105
- full: 2
106
- };
107
- /**
108
- * Returns the narrower of the two response modes (lower width wins).
109
- * Pure function so the CLI and BFF can both call it without import cycles.
110
- */
111
- export function narrowResponseMode(policy, requested) {
112
- return RESPONSE_MODE_WIDTH[requested] < RESPONSE_MODE_WIDTH[policy] ? requested : policy;
113
- }
114
- /**
115
- * Error codes returned by the proxy route. Stable strings — the CLI
116
- * matches against them in scripts. Adding a new code is non-breaking;
117
- * removing or renaming an existing code requires a protocol bump.
118
- */
119
- export const PROXY_ERROR_CODES = [
120
- "unsupported_protocol",
121
- "unauthorized",
122
- "endpoint_not_found",
123
- "policy_denied",
124
- "rate_limited",
125
- "ssrf_denied",
126
- "upstream_timeout",
127
- "upstream_error",
128
- "exceeded_cap",
129
- "bad_request",
130
- "internal_error"
131
- ];
132
- /**
133
- * Default caps for a proxy endpoint when the submission doesn't specify one.
134
- * Lives in the protocol module (next to the index-file shape) so
135
- * {@link buildProxyIndexFile} can fill every optional cap with a concrete
136
- * value; the submission parser re-exports it.
137
- *
138
- * `maxResponseBytes: 0` means UNLIMITED — the v2 path streams the upstream body
139
- * unbuffered (O(1) memory regardless of size), so there is no cap to apply by
140
- * default. A customer can opt into a per-response truncation cap by setting a
141
- * positive value. There is no cumulative per-run call/byte budget: it needed a
142
- * per-call counter on the hot path and only existed to bound memory, which
143
- * streaming already does. The platform records named-proxy request bytes,
144
- * response bytes, attempts, and retries as run-log usage telemetry only; no
145
- * pricing/charging model is derived here.
146
- */
147
- export const PROXY_ENDPOINT_DEFAULTS = {
148
- allowHeaders: [],
149
- responseMode: "headers_only",
150
- // 10 MiB. The body is buffered in the hosted API to enforce this cap, while the
151
- // launch default fits practical multimodal/tool POSTs without every endpoint
152
- // needing an override.
153
- maxRequestBytes: 10 * 1024 * 1024,
154
- // Unlimited (0). The request body is buffered to enforce its cap, so that
155
- // stays finite; the response is streamed, so it does not need one.
156
- maxResponseBytes: 0,
157
- // 5 minutes. Long-running upstream tool/model calls should not fail under a
158
- // development-oriented 10s ceiling; endpoints can still set a smaller value.
159
- timeoutMs: 5 * 60 * 1000
160
- };
161
- /**
162
- * Build the per-run {@link ProxyIndexFile} mounted into the container at
163
- * `/mnt/session/uploads/aex/index.json`. Pure: applies
164
- * {@link PROXY_ENDPOINT_DEFAULTS} so every optional cap is concrete, and
165
- * carries ONLY the non-secret endpoint policy — auth values never appear.
166
- *
167
- * ALWAYS emits a file (the always-on surface). With zero endpoints OR no
168
- * `proxyPublicBaseUrl`, `proxyBaseUrl` is `null` and `endpoints` is `[]`.
169
- * Otherwise `proxyBaseUrl` is `<trimmed base>/api/runs/<runId>/proxy`, the
170
- * prefix the in-container runtime bridge appends `/<endpointName>` to (proxy.ts).
171
- */
172
- export function buildProxyIndexFile(input) {
173
- const endpoints = input.endpoints ?? [];
174
- const base = input.proxyPublicBaseUrl?.trim() ?? "";
175
- const haveBase = base.length > 0;
176
- const proxyBaseUrl = endpoints.length > 0 && haveBase
177
- ? `${base.replace(/\/+$/, "")}/api/runs/${input.runId}/proxy`
178
- : null;
179
- return {
180
- protocolVersion: PROXY_PROTOCOL_VERSION,
181
- runId: input.runId,
182
- proxyBaseUrl,
183
- endpoints: endpoints.map((e) => ({
184
- name: e.name,
185
- baseUrl: e.baseUrl,
186
- authShape: e.authShape,
187
- allowMethods: e.allowMethods,
188
- allowPathPrefixes: e.allowPathPrefixes,
189
- allowHeaders: e.allowHeaders ?? PROXY_ENDPOINT_DEFAULTS.allowHeaders,
190
- responseMode: e.responseMode ?? PROXY_ENDPOINT_DEFAULTS.responseMode,
191
- maxRequestBytes: e.maxRequestBytes ?? PROXY_ENDPOINT_DEFAULTS.maxRequestBytes,
192
- maxResponseBytes: e.maxResponseBytes ?? PROXY_ENDPOINT_DEFAULTS.maxResponseBytes,
193
- timeoutMs: e.timeoutMs ?? PROXY_ENDPOINT_DEFAULTS.timeoutMs,
194
- ...(e.retry !== undefined ? { retry: resolveProxyRetryPolicy(e.retry) } : {})
195
- }))
196
- };
197
- }
198
- /**
199
- * Header name (lowercase) that an upstream auth shape uses as its
200
- * carrier. Returns `undefined` for query-based and keyless auth.
201
- *
202
- * Used by the submission parser to forbid `allowHeaders` from listing
203
- * the auth header (avoids leaks via caller-supplied headers), and by
204
- * the proxy route to strip any caller header that would collide with
205
- * the auth carrier at request time.
206
- */
207
- export function authShapeHeaderName(shape) {
208
- switch (shape.type) {
209
- case "bearer":
210
- case "basic":
211
- return "authorization";
212
- case "header":
213
- return shape.name.toLowerCase();
214
- case "query":
215
- case "none":
216
- return undefined;
217
- }
218
- }
219
- /**
220
- * Query-string key that an upstream query-based auth shape uses as its
221
- * carrier. Returns `undefined` for non-query shapes (including "none").
222
- */
223
- export function authShapeQueryName(shape) {
224
- return shape.type === "query" ? shape.name : undefined;
225
- }
226
- /**
227
- * Inbound request headers every Aex proxy plane STRIPS before
228
- * forwarding a runtime/runner request upstream. Three categories:
229
- *
230
- * - Credential carriers (`authorization`, `x-api-key`, `cookie`,
231
- * `proxy-authorization`) — these belong to Aex's own auth gate
232
- * (the per-run bearer) or to the caller, never the upstream. The
233
- * legitimate upstream credential is injected server-side from the
234
- * run's Vault bundle / endpoint auth shape AFTER this strip, so it is
235
- * never sourced from an inbound header.
236
- * - Hop-by-hop fields (RFC 7230 §6.1: `connection`, `keep-alive`,
237
- * `transfer-encoding`, `te`, `trailer`, `upgrade`, `expect`,
238
- * `proxy-authenticate`, `proxy-connection`) — must not survive a
239
- * proxy hop.
240
- * - Routing primitives a compromised runner could spoof to bypass an
241
- * upstream's IP allowlist / rate-limit (`host`, `content-length`,
242
- * `x-forwarded-*`, `x-real-ip`, `forwarded`).
243
- *
244
- * The platform API provider-proxy and the dashboard MCP proxy strip exactly
245
- * this set (both inject upstream auth separately — the provider key, or the
246
- * Vault MCP-bundle headers, applied AFTER the strip). The dashboard
247
- * customer HTTP proxy hard-denies this set MINUS `x-api-key`, because a
248
- * customer endpoint may legitimately declare `x-api-key` as its auth
249
- * carrier; it derives from this constant so the hop-by-hop + routing
250
- * entries never drift. Keeping the membership here is the single source of
251
- * truth that stops those surfaces diverging.
252
- */
253
- export const PROXY_STRIPPED_INBOUND_HEADERS = new Set([
254
- // credential carriers
255
- "authorization",
256
- "x-api-key",
257
- "cookie",
258
- "proxy-authorization",
259
- // hop-by-hop (RFC 7230 §6.1)
260
- "connection",
261
- "keep-alive",
262
- "transfer-encoding",
263
- "te",
264
- "trailer",
265
- "upgrade",
266
- "expect",
267
- "proxy-authenticate",
268
- "proxy-connection",
269
- // routing primitives a runner could spoof
270
- "host",
271
- "content-length",
272
- "x-forwarded-for",
273
- "x-forwarded-host",
274
- "x-forwarded-proto",
275
- "x-forwarded-port",
276
- "x-real-ip",
277
- "forwarded"
278
- ]);
279
- /**
280
- * Status code → error code mapping used by the BFF to ensure the audit
281
- * row's error code and the HTTP response line up. Kept here so callers
282
- * can do a sanity check in tests.
283
- */
284
- export const PROXY_ERROR_HTTP_STATUS = {
285
- unsupported_protocol: 426,
286
- unauthorized: 401,
287
- endpoint_not_found: 404,
288
- policy_denied: 403,
289
- rate_limited: 429,
290
- ssrf_denied: 403,
291
- upstream_timeout: 504,
292
- upstream_error: 502,
293
- exceeded_cap: 502,
294
- bad_request: 400,
295
- internal_error: 500
296
- };
297
- //# sourceMappingURL=proxy-protocol.js.map
@@ -1,19 +0,0 @@
1
- import type { PlatformProxyEndpoint, PlatformProxyEndpointAuth } from "./submission.js";
2
- /**
3
- * Cross-validate a `proxyEndpoints` policy list against a
4
- * `secrets.proxyEndpointAuth` value list. Throws on the first mismatch
5
- * with an actionable, field-named error message.
6
- *
7
- * Mirrors the BFF's authoritative validator so misconfigured submissions
8
- * fail fast in the SDK/CLI before going over the wire.
9
- */
10
- export declare function validateProxyAuth(endpoints: readonly PlatformProxyEndpoint[], auth: readonly PlatformProxyEndpointAuth[] | undefined): void;
11
- /**
12
- * Build an `allowedHosts` list for `environment.network` that includes
13
- * the aex proxy host (and optionally Anthropic's MCP host) when the
14
- * caller is hand-rolling networking.
15
- */
16
- export declare function buildPlatformAllowedHosts(input: {
17
- readonly baseUrl: string;
18
- readonly extraHosts?: readonly string[];
19
- }): readonly string[];
@@ -1,51 +0,0 @@
1
- /**
2
- * Cross-validate a `proxyEndpoints` policy list against a
3
- * `secrets.proxyEndpointAuth` value list. Throws on the first mismatch
4
- * with an actionable, field-named error message.
5
- *
6
- * Mirrors the BFF's authoritative validator so misconfigured submissions
7
- * fail fast in the SDK/CLI before going over the wire.
8
- */
9
- export function validateProxyAuth(endpoints, auth) {
10
- const authList = auth ?? [];
11
- const endpointNames = new Set(endpoints.map((e) => e.name));
12
- const authNames = new Set();
13
- for (const entry of authList) {
14
- if (authNames.has(entry.name)) {
15
- throw new Error(`secrets.proxyEndpointAuth contains duplicate name '${entry.name}'`);
16
- }
17
- authNames.add(entry.name);
18
- if (!endpointNames.has(entry.name)) {
19
- throw new Error(`secrets.proxyEndpointAuth[].name='${entry.name}' has no matching proxyEndpoints[].name`);
20
- }
21
- }
22
- for (const endpoint of endpoints) {
23
- const match = authList.find((a) => a.name === endpoint.name);
24
- if (!match) {
25
- throw new Error(`proxyEndpoints[].name='${endpoint.name}' is missing a matching secrets.proxyEndpointAuth entry`);
26
- }
27
- if (match.value.type !== endpoint.authShape.type) {
28
- throw new Error(`secrets.proxyEndpointAuth[name='${endpoint.name}'].value.type='${match.value.type}' does not match proxyEndpoints[name='${endpoint.name}'].authShape.type='${endpoint.authShape.type}'`);
29
- }
30
- }
31
- }
32
- /**
33
- * Build an `allowedHosts` list for `environment.network` that includes
34
- * the aex proxy host (and optionally Anthropic's MCP host) when the
35
- * caller is hand-rolling networking.
36
- */
37
- export function buildPlatformAllowedHosts(input) {
38
- const result = [];
39
- try {
40
- result.push(new URL(input.baseUrl).host);
41
- }
42
- catch {
43
- throw new Error("buildPlatformAllowedHosts: baseUrl must be an absolute URL");
44
- }
45
- for (const host of input.extraHosts ?? []) {
46
- if (!result.includes(host))
47
- result.push(host);
48
- }
49
- return result;
50
- }
51
- //# sourceMappingURL=proxy-validation.js.map