@aexhq/sdk 0.34.0 → 0.36.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 +16 -15
- package/dist/_contracts/index.d.ts +3 -4
- package/dist/_contracts/index.js +1 -4
- package/dist/_contracts/operations.d.ts +2 -1
- package/dist/_contracts/operations.js +10 -0
- package/dist/_contracts/run-config.d.ts +1 -3
- package/dist/_contracts/run-config.js +2 -7
- package/dist/_contracts/run-trace.d.ts +0 -86
- package/dist/_contracts/run-trace.js +1 -184
- package/dist/_contracts/run-unit.d.ts +2 -25
- package/dist/_contracts/run-unit.js +1 -2
- package/dist/_contracts/runtime-manifest.d.ts +1 -1
- package/dist/_contracts/runtime-security-profile.d.ts +0 -2
- package/dist/_contracts/runtime-security-profile.js +0 -9
- package/dist/_contracts/runtime-types.d.ts +25 -4
- package/dist/_contracts/stable.d.ts +1 -1
- package/dist/_contracts/stable.js +1 -1
- package/dist/_contracts/submission.d.ts +62 -95
- package/dist/_contracts/submission.js +59 -482
- package/dist/cli.mjs +99 -442
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +49 -25
- package/dist/client.js +341 -70
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +9 -15
- package/dist/index.js +11 -17
- package/dist/index.js.map +1 -1
- package/dist/retry.d.ts +162 -0
- package/dist/retry.js +320 -0
- package/dist/retry.js.map +1 -0
- package/dist/secret.d.ts +2 -2
- package/dist/secret.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/concepts/composition.md +8 -14
- package/docs/credentials.md +59 -101
- package/docs/defaults.md +0 -8
- package/docs/events.md +8 -9
- package/docs/limits-and-quotas.md +1 -4
- package/docs/limits.md +2 -6
- package/docs/mcp.md +4 -5
- package/docs/networking.md +6 -16
- package/docs/outputs.md +0 -4
- package/docs/public-surface.json +3 -3
- package/docs/quickstart.md +3 -7
- package/docs/retries.md +129 -0
- package/docs/run-config.md +6 -3
- package/docs/secrets.md +1 -1
- package/docs/skills.md +3 -3
- package/docs/vision-skills.md +52 -101
- package/examples/feature-tour.ts +284 -0
- package/package.json +1 -1
- package/dist/_contracts/proxy-protocol.d.ts +0 -305
- package/dist/_contracts/proxy-protocol.js +0 -297
- package/dist/_contracts/proxy-validation.d.ts +0 -19
- package/dist/_contracts/proxy-validation.js +0 -51
- package/dist/data-tools.d.ts +0 -82
- package/dist/data-tools.js +0 -251
- package/dist/data-tools.js.map +0 -1
- package/dist/proxy-endpoint.d.ts +0 -131
- package/dist/proxy-endpoint.js +0 -144
- package/dist/proxy-endpoint.js.map +0 -1
- package/examples/chat-corpus.ts +0 -84
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK feature tour: one managed session that uses typed model/runtime constants,
|
|
3
|
+
* inline AGENTS.md guidance, uploaded files, a custom tool bundle, selected
|
|
4
|
+
* built-in tools, runtime env vars/secrets, streamed events, output reads, and
|
|
5
|
+
* a follow-up session turn.
|
|
6
|
+
*
|
|
7
|
+
* Run from the repository root after building the workspace package:
|
|
8
|
+
*
|
|
9
|
+
* AEX_API_TOKEN=... DEEPSEEK_API_KEY=... bun packages/sdk/examples/feature-tour.ts
|
|
10
|
+
*
|
|
11
|
+
* Optional:
|
|
12
|
+
*
|
|
13
|
+
* AEX_API_URL=https://api.aex.dev
|
|
14
|
+
* AEX_FEATURE_TOUR_DOWNLOAD=./feature-tour-session.zip
|
|
15
|
+
* AEX_DEMO_RUNTIME_SECRET=... # mounted as a runtime secret; never printed
|
|
16
|
+
* AEX_DEMO_MCP_URL=https://... # declares an optional remote MCP server
|
|
17
|
+
* AEX_DEMO_MCP_TOKEN=... # optional bearer auth for that MCP server
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
AgentsMd,
|
|
21
|
+
Aex,
|
|
22
|
+
BuiltinTools,
|
|
23
|
+
File,
|
|
24
|
+
isRateLimited,
|
|
25
|
+
isTextMessage,
|
|
26
|
+
McpServer,
|
|
27
|
+
Models,
|
|
28
|
+
Providers,
|
|
29
|
+
Secret,
|
|
30
|
+
Sizes,
|
|
31
|
+
Tool
|
|
32
|
+
} from "@aexhq/sdk";
|
|
33
|
+
|
|
34
|
+
process.on("uncaughtException", handleFatal);
|
|
35
|
+
process.on("unhandledRejection", handleFatal);
|
|
36
|
+
|
|
37
|
+
const apiToken = required("AEX_API_TOKEN");
|
|
38
|
+
const deepseekKey = required("DEEPSEEK_API_KEY");
|
|
39
|
+
const apiUrl = process.env.AEX_API_URL;
|
|
40
|
+
const demoMcpUrl = process.env.AEX_DEMO_MCP_URL;
|
|
41
|
+
const demoMcpToken = process.env.AEX_DEMO_MCP_TOKEN;
|
|
42
|
+
const demoRuntimeSecret = process.env.AEX_DEMO_RUNTIME_SECRET;
|
|
43
|
+
const downloadPath = process.env.AEX_FEATURE_TOUR_DOWNLOAD;
|
|
44
|
+
const textEncoder = new TextEncoder();
|
|
45
|
+
|
|
46
|
+
const aex = new Aex({
|
|
47
|
+
apiToken,
|
|
48
|
+
...(apiUrl ? { baseUrl: apiUrl } : {}),
|
|
49
|
+
retry: {
|
|
50
|
+
maxAttempts: 4,
|
|
51
|
+
initialDelayMs: 500,
|
|
52
|
+
maxDelayMs: 10_000,
|
|
53
|
+
maxElapsedMs: 90_000
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const metricLookup = await Tool.fromFiles({
|
|
58
|
+
name: "metric_lookup",
|
|
59
|
+
description: "Looks up normalized demo metrics for one product line.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
properties: {
|
|
64
|
+
product: {
|
|
65
|
+
type: "string",
|
|
66
|
+
enum: ["atlas", "beacon", "cinder"],
|
|
67
|
+
description: "Product line to inspect."
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
required: ["product"]
|
|
71
|
+
},
|
|
72
|
+
entry: "index.js",
|
|
73
|
+
files: {
|
|
74
|
+
"index.js": `
|
|
75
|
+
const DATA = {
|
|
76
|
+
atlas: { customerCount: 118, activationHealthPct: 72.4, supportTickets: 11 },
|
|
77
|
+
beacon: { customerCount: 74, activationHealthPct: 65.1, supportTickets: 19 },
|
|
78
|
+
cinder: { customerCount: 43, activationHealthPct: 58.8, supportTickets: 7 }
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default async function ({ input }) {
|
|
82
|
+
const key = String(input.product ?? "").toLowerCase();
|
|
83
|
+
const row = DATA[key];
|
|
84
|
+
if (!row) {
|
|
85
|
+
return { content: [{ type: "text", text: \`unknown product: \${key}\` }], is_error: true };
|
|
86
|
+
}
|
|
87
|
+
return { content: [{ type: "text", text: JSON.stringify({ product: key, ...row }) }] };
|
|
88
|
+
}
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const demoCsv = [
|
|
94
|
+
"product,region,q1_revenue_usd,q2_revenue_usd,activation_rate",
|
|
95
|
+
"atlas,na,120000,139500,0.84",
|
|
96
|
+
"beacon,emea,98000,104300,0.79",
|
|
97
|
+
"cinder,apac,67000,81000,0.91"
|
|
98
|
+
].join("\n");
|
|
99
|
+
|
|
100
|
+
const attachedFile = await File.fromBytes({
|
|
101
|
+
name: "quarterly-metrics.csv",
|
|
102
|
+
bytes: textEncoder.encode(`${demoCsv}\n`),
|
|
103
|
+
mountPath: "/workspace/input"
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const runRules = await AgentsMd.fromContent(
|
|
107
|
+
[
|
|
108
|
+
"# Feature tour rules",
|
|
109
|
+
"- Use `/workspace/input/quarterly-metrics.csv` as the source table.",
|
|
110
|
+
"- Call `metric_lookup` for atlas, beacon, and cinder before writing conclusions.",
|
|
111
|
+
"- Write final artifacts under `/workspace/outputs`.",
|
|
112
|
+
"- Never print runtime secret values or provider keys."
|
|
113
|
+
].join("\n"),
|
|
114
|
+
{ name: "feature-tour-rules" }
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const mcpServers = demoMcpUrl
|
|
118
|
+
? [
|
|
119
|
+
McpServer.remote({
|
|
120
|
+
name: "demo-mcp",
|
|
121
|
+
url: demoMcpUrl,
|
|
122
|
+
...(demoMcpToken
|
|
123
|
+
? { headers: { Authorization: `Bearer ${demoMcpToken}` } }
|
|
124
|
+
: {})
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
: [];
|
|
128
|
+
|
|
129
|
+
const environmentSecrets = demoRuntimeSecret
|
|
130
|
+
? { DEMO_RUNTIME_SECRET: Secret.value(demoRuntimeSecret) }
|
|
131
|
+
: undefined;
|
|
132
|
+
|
|
133
|
+
console.log("creating feature-tour session...");
|
|
134
|
+
console.log(`optional mcp: ${mcpServers.length > 0 ? "enabled" : "disabled"}`);
|
|
135
|
+
console.log(`optional runtime secret: ${environmentSecrets ? "enabled" : "disabled"}`);
|
|
136
|
+
|
|
137
|
+
const session = await aex.openSession({
|
|
138
|
+
provider: Providers.DEEPSEEK,
|
|
139
|
+
model: Models.DEEPSEEK_V4_FLASH,
|
|
140
|
+
system: [
|
|
141
|
+
"You are a concise analytics agent.",
|
|
142
|
+
"Prefer exact calculations and write durable files for the caller."
|
|
143
|
+
].join(" "),
|
|
144
|
+
agentsMd: [runRules],
|
|
145
|
+
files: [attachedFile],
|
|
146
|
+
includeBuiltinTools: false,
|
|
147
|
+
tools: [
|
|
148
|
+
BuiltinTools.read_file,
|
|
149
|
+
BuiltinTools.write_file,
|
|
150
|
+
BuiltinTools.bash,
|
|
151
|
+
BuiltinTools.grep,
|
|
152
|
+
BuiltinTools.code_execution,
|
|
153
|
+
metricLookup
|
|
154
|
+
],
|
|
155
|
+
mcpServers,
|
|
156
|
+
environment: {
|
|
157
|
+
networking: { mode: "open" },
|
|
158
|
+
variables: {
|
|
159
|
+
FEATURE_TOUR: "true",
|
|
160
|
+
REPORT_DIR: "/workspace/outputs"
|
|
161
|
+
},
|
|
162
|
+
...(environmentSecrets ? { secrets: environmentSecrets } : {})
|
|
163
|
+
},
|
|
164
|
+
outputs: {
|
|
165
|
+
allowedDirs: ["/workspace/outputs"],
|
|
166
|
+
deniedDirs: ["*.tmp"],
|
|
167
|
+
maxFiles: 10,
|
|
168
|
+
maxFileBytes: 1_000_000
|
|
169
|
+
},
|
|
170
|
+
outputMode: "stream",
|
|
171
|
+
runtime: Sizes.SHARED_0_25X_1GB,
|
|
172
|
+
metadata: {
|
|
173
|
+
example: "sdk-feature-tour",
|
|
174
|
+
sdkSurface: "public"
|
|
175
|
+
},
|
|
176
|
+
overrides: {
|
|
177
|
+
idleTtl: "5m",
|
|
178
|
+
timeout: "10m",
|
|
179
|
+
maxSpendUsd: 2
|
|
180
|
+
},
|
|
181
|
+
apiKeys: { deepseek: deepseekKey }
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log(`session: ${session.id}`);
|
|
185
|
+
|
|
186
|
+
const prompt = [
|
|
187
|
+
"Analyze the attached quarterly metrics.",
|
|
188
|
+
"Call metric_lookup for atlas, beacon, and cinder.",
|
|
189
|
+
"Create /workspace/outputs/feature-tour-report.md with a short table, a ranking by q2_revenue_usd, and two risks.",
|
|
190
|
+
"Create /workspace/outputs/summary.json with keys topProduct, totalQ2RevenueUsd, highestActivationProduct, and riskCount."
|
|
191
|
+
].join(" ");
|
|
192
|
+
|
|
193
|
+
const firstTurn = session.send(prompt);
|
|
194
|
+
const firstTurnIterator = firstTurn[Symbol.asyncIterator]();
|
|
195
|
+
let result: Awaited<ReturnType<typeof firstTurn.done>> | undefined;
|
|
196
|
+
for (;;) {
|
|
197
|
+
const next = await firstTurnIterator.next();
|
|
198
|
+
if (next.done) {
|
|
199
|
+
result = next.value as Awaited<ReturnType<typeof firstTurn.done>>;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const event = next.value;
|
|
203
|
+
if (isTextMessage(event)) {
|
|
204
|
+
process.stdout.write(event.data.text);
|
|
205
|
+
} else if (event.type === "TOOL_CALL_START") {
|
|
206
|
+
const name = typeof event.data.name === "string" ? event.data.name : "tool";
|
|
207
|
+
process.stdout.write(`\n[tool:start] ${name}\n`);
|
|
208
|
+
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
209
|
+
process.stdout.write("[tool:result]\n");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!result) {
|
|
214
|
+
throw new Error("first turn stream ended without a result");
|
|
215
|
+
}
|
|
216
|
+
console.log(`\nfirst turn parked with status: ${result.status}`);
|
|
217
|
+
|
|
218
|
+
const followUp = await session
|
|
219
|
+
.send("Read summary.json back and answer with one sentence confirming the top product and total Q2 revenue.")
|
|
220
|
+
.done();
|
|
221
|
+
console.log(`follow-up status: ${followUp.status}`);
|
|
222
|
+
if (followUp.text) {
|
|
223
|
+
console.log(`follow-up text: ${followUp.text.trim()}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const parked = await session.wait({ timeoutMs: 60_000, intervalMs: 2_000 });
|
|
227
|
+
console.log(`settled session status: ${parked.status}`);
|
|
228
|
+
|
|
229
|
+
const messages = await session.messages().list();
|
|
230
|
+
console.log(`assistant messages: ${messages.length}`);
|
|
231
|
+
|
|
232
|
+
const events = await session.events().list();
|
|
233
|
+
console.log(`captured events: ${events.length}`);
|
|
234
|
+
|
|
235
|
+
const outputs = await session.outputs().list();
|
|
236
|
+
console.log("outputs:");
|
|
237
|
+
for (const output of outputs) {
|
|
238
|
+
console.log(`- ${output.filename ?? output.id} (${output.contentType ?? "unknown"})`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const summary = await session.outputs().read(
|
|
242
|
+
{ path: "summary.json", match: "suffix" },
|
|
243
|
+
{ maxBytes: 20_000 }
|
|
244
|
+
);
|
|
245
|
+
console.log("summary.json:");
|
|
246
|
+
console.log(summary.text);
|
|
247
|
+
|
|
248
|
+
const report = await session.outputs().findOne({
|
|
249
|
+
filename: "feature-tour-report.md"
|
|
250
|
+
});
|
|
251
|
+
if (report) {
|
|
252
|
+
const reportPreview = await session.outputs().read(report, {
|
|
253
|
+
maxBytes: 4_000,
|
|
254
|
+
grep: "risk"
|
|
255
|
+
});
|
|
256
|
+
console.log("report risk lines:");
|
|
257
|
+
console.log(reportPreview.text || "(no risk lines found)");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const reopenedOutputs = await aex.sessions.outputs(session.id).list();
|
|
261
|
+
console.log(`outputs via aex.sessions.outputs(...): ${reopenedOutputs.length}`);
|
|
262
|
+
|
|
263
|
+
if (downloadPath) {
|
|
264
|
+
const bytes = await session.download({ to: downloadPath });
|
|
265
|
+
console.log(`downloaded session archive: ${downloadPath} (${bytes.byteLength} bytes)`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function required(name: string): string {
|
|
269
|
+
const value = process.env[name];
|
|
270
|
+
if (!value) {
|
|
271
|
+
console.error(`Missing env var ${name}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function handleFatal(err: unknown): void {
|
|
278
|
+
if (isRateLimited(err)) {
|
|
279
|
+
console.error(`rate limited after ${err.attempts} attempts; retry after ${err.retryAfterMs ?? "unknown"}ms`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
console.error(err instanceof Error ? err.stack ?? err.message : String(err));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexhq/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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": {
|
|
@@ -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>;
|