@aexhq/sdk 0.15.2 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +11 -8
  2. package/dist/_contracts/index.d.ts +2 -0
  3. package/dist/_contracts/index.js +2 -0
  4. package/dist/_contracts/internal.d.ts +2 -0
  5. package/dist/_contracts/internal.js +2 -0
  6. package/dist/_contracts/managed-key.d.ts +3 -2
  7. package/dist/_contracts/models.d.ts +33 -0
  8. package/dist/_contracts/models.js +59 -0
  9. package/dist/_contracts/post-hook.d.ts +28 -0
  10. package/dist/_contracts/post-hook.js +68 -0
  11. package/dist/_contracts/proxy-protocol.d.ts +14 -18
  12. package/dist/_contracts/proxy-protocol.js +16 -15
  13. package/dist/_contracts/run-config.d.ts +15 -17
  14. package/dist/_contracts/run-config.js +17 -19
  15. package/dist/_contracts/run-unit.d.ts +4 -4
  16. package/dist/_contracts/run-unit.js +17 -6
  17. package/dist/_contracts/submission.d.ts +12 -4
  18. package/dist/_contracts/submission.js +9 -9
  19. package/dist/cli.mjs +164 -46
  20. package/dist/cli.mjs.sha256 +1 -1
  21. package/dist/client.d.ts +12 -2
  22. package/dist/client.js +8 -0
  23. package/dist/client.js.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/proxy-endpoint.d.ts +1 -3
  28. package/dist/proxy-endpoint.js +2 -6
  29. package/dist/proxy-endpoint.js.map +1 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/docs/cleanup.md +3 -1
  33. package/docs/credentials.md +3 -3
  34. package/docs/outputs.md +3 -1
  35. package/docs/provider-runtime-capabilities.md +1 -1
  36. package/docs/quickstart.md +17 -7
  37. package/docs/run-config.md +12 -7
  38. package/docs/skills.md +102 -117
  39. package/docs/testing.md +9 -4
  40. package/package.json +2 -2
package/README.md CHANGED
@@ -9,7 +9,8 @@ aex is a TypeScript-first SDK + CLI for running autonomous agent sessions across
9
9
  ```ts
10
10
  import {
11
11
  AgentExecutor, // the only client class — submits durable runs to aex
12
- Skill, // workspace / provider / inline skill bundles
12
+ RunModels, // closed public model id constants
13
+ Skill, // local, URL, and catalog skill bundles normalized to assets
13
14
  McpServer, // MCP server declarations (headers split into secrets server-side)
14
15
  ProxyEndpoint, // per-run managed HTTP proxy endpoint
15
16
  AgentsMd, // AGENTS.md / CLAUDE.md uploads
@@ -55,15 +56,15 @@ The aex URL defaults to `https://api.aex.dev`. Set `--aex-url` on the CLI or `ba
55
56
  ## Quickstart (SDK)
56
57
 
57
58
  ```ts
58
- import { AgentExecutor } from "@aexhq/sdk";
59
+ import { AgentExecutor, RunModels } from "@aexhq/sdk";
59
60
 
60
61
  const aex = new AgentExecutor({
61
- apiToken: process.env.AEX_API_TOKEN!
62
+ apiToken: process.env.AEX_API_TOKEN!,
62
63
  // baseUrl defaults to https://api.aex.dev - set it for local or staging planes.
63
64
  });
64
65
 
65
66
  const runId = await aex.submitRun({
66
- model: "claude-haiku-4-5",
67
+ model: RunModels.CLAUDE_HAIKU_4_5,
67
68
  system: "You are a concise automation agent.",
68
69
  prompt: "Write a short answer about agent-first SDK design.",
69
70
  secrets: { apiKey: process.env.ANTHROPIC_API_KEY! }
@@ -87,9 +88,9 @@ Reusable, credential-free configs can be ordinary functions:
87
88
  ```ts
88
89
  function summarise(topic: string) {
89
90
  return {
90
- model: "claude-haiku-4-5",
91
- system: "You are a concise automation agent.",
92
- prompt: `Write a short answer about ${topic}.`
91
+ model: RunModels.CLAUDE_HAIKU_4_5,
92
+ system: "You are a concise automation agent.",
93
+ prompt: `Write a short answer about ${topic}.`
93
94
  };
94
95
  }
95
96
 
@@ -127,7 +128,9 @@ aex run \
127
128
  --follow
128
129
  ```
129
130
 
130
- `--config` accepts a plain run-config JSON file for a single run request: `{ model, system?, prompt, skills?, mcpServers?, environment?, proxyEndpoints?, metadata? }`. There is no saved-definition product or interpolation DSL — build the JSON at the call site.
131
+ `--config` accepts a plain run-config JSON file for a single run request: `{ model, system?, prompt, skills?, mcpServers?, environment?, runtimeSize?, timeout?, postHook?, proxyEndpoints?, metadata? }`. There is no saved-definition product or interpolation DSL — build the JSON at the call site. SDK code should use `RunModels`; config JSON is validated against the same `RUN_MODELS` allowlist.
132
+
133
+ Runtime controls: omit `builtins` to use the default `["developer"]` toolkit, pass `builtins: []` to disable builtins for a pure-MCP run, use `outputMode: "stream"` for per-token assistant text, prefer `RuntimeSizes` for `runtimeSize`, and use `postHook` to run a verifier command after the agent exits successfully.
131
134
 
132
135
  ## Test commands
133
136
 
@@ -1,8 +1,10 @@
1
1
  export * from "./proxy-protocol.js";
2
2
  export * from "./provider-support.js";
3
+ export * from "./models.js";
3
4
  export * from "./status.js";
4
5
  export * from "./submission.js";
5
6
  export * from "./runtime-sizes.js";
7
+ export * from "./post-hook.js";
6
8
  export * from "./runner-event.js";
7
9
  export * from "./event-envelope.js";
8
10
  export * from "./connection-ticket.js";
@@ -1,8 +1,10 @@
1
1
  export * from "./proxy-protocol.js";
2
2
  export * from "./provider-support.js";
3
+ export * from "./models.js";
3
4
  export * from "./status.js";
4
5
  export * from "./submission.js";
5
6
  export * from "./runtime-sizes.js";
7
+ export * from "./post-hook.js";
6
8
  export * from "./runner-event.js";
7
9
  export * from "./event-envelope.js";
8
10
  export * from "./connection-ticket.js";
@@ -1 +1,3 @@
1
+ export * from "./models.js";
2
+ export * from "./post-hook.js";
1
3
  export * from "./submission.js";
@@ -4,5 +4,7 @@
4
4
  // of validator code. NOT part of the public `@aexhq/contracts` surface — do
5
5
  // NOT add this to the package `index`; consumers reach it via the explicit
6
6
  // `@aexhq/contracts/internal` subpath.
7
+ export * from "./models.js";
8
+ export * from "./post-hook.js";
7
9
  export * from "./submission.js";
8
10
  //# sourceMappingURL=internal.js.map
@@ -1,4 +1,5 @@
1
1
  import type { RunProvider, RuntimeKind } from "./submission.js";
2
+ import type { RunModel } from "./models.js";
2
3
  export declare const CREDENTIAL_MODES: readonly ["byok", "managed"];
3
4
  export type CredentialMode = (typeof CREDENTIAL_MODES)[number];
4
5
  export declare const DEFAULT_CREDENTIAL_MODE: CredentialMode;
@@ -31,7 +32,7 @@ export interface ManagedKeyPolicyV1 {
31
32
  readonly billingRequired: true;
32
33
  readonly providers: readonly RunProvider[];
33
34
  readonly runtimes: readonly RuntimeKind[];
34
- readonly models?: readonly string[];
35
+ readonly models?: readonly RunModel[];
35
36
  readonly features: ManagedKeyFeaturePolicyV1;
36
37
  }
37
38
  /**
@@ -75,7 +76,7 @@ export interface ManagedCredentialResolutionInput {
75
76
  readonly runId: string;
76
77
  readonly provider: RunProvider;
77
78
  readonly runtime: RuntimeKind;
78
- readonly model: string;
79
+ readonly model: RunModel;
79
80
  readonly policy: ManagedKeyPolicyV1;
80
81
  }
81
82
  export interface ManagedCredentialLease {
@@ -0,0 +1,33 @@
1
+ import type { RunProvider } from "./submission.js";
2
+ /**
3
+ * Closed set of model ids accepted by the public run-submission schema.
4
+ *
5
+ * The provider-proxy still sends the model id through to the selected upstream
6
+ * provider, but callers cannot submit arbitrary strings. Additions belong here
7
+ * first so SDK types, CLI validation, docs examples, and platform parsing move
8
+ * together.
9
+ */
10
+ export declare const RUN_MODELS: readonly ["claude-haiku-4-5", "claude-3-5-haiku-latest", "claude-3-5-sonnet-latest", "deepseek-chat", "gpt-4.1", "gpt-4o-mini", "gemini-2.0-flash", "gemini-2.5-flash", "mistral-large-latest", "mistral-small-latest"];
11
+ export type RunModel = (typeof RUN_MODELS)[number];
12
+ export declare const RunModels: {
13
+ readonly CLAUDE_HAIKU_4_5: "claude-haiku-4-5";
14
+ readonly CLAUDE_3_5_HAIKU_LATEST: "claude-3-5-haiku-latest";
15
+ readonly CLAUDE_3_5_SONNET_LATEST: "claude-3-5-sonnet-latest";
16
+ readonly DEEPSEEK_CHAT: "deepseek-chat";
17
+ readonly GPT_4_1: "gpt-4.1";
18
+ readonly GPT_4O_MINI: "gpt-4o-mini";
19
+ readonly GEMINI_2_0_FLASH: "gemini-2.0-flash";
20
+ readonly GEMINI_2_5_FLASH: "gemini-2.5-flash";
21
+ readonly MISTRAL_LARGE_LATEST: "mistral-large-latest";
22
+ readonly MISTRAL_SMALL_LATEST: "mistral-small-latest";
23
+ };
24
+ export declare const RUN_MODELS_BY_PROVIDER: {
25
+ readonly anthropic: readonly ["claude-haiku-4-5", "claude-3-5-haiku-latest", "claude-3-5-sonnet-latest"];
26
+ readonly deepseek: readonly ["deepseek-chat"];
27
+ readonly openai: readonly ["gpt-4.1", "gpt-4o-mini"];
28
+ readonly gemini: readonly ["gemini-2.0-flash", "gemini-2.5-flash"];
29
+ readonly mistral: readonly ["mistral-large-latest", "mistral-small-latest"];
30
+ };
31
+ export declare function isRunModel(input: unknown): input is RunModel;
32
+ export declare function parseRunModel(input: unknown, field?: string): RunModel;
33
+ export declare function assertRunModelMatchesProvider(provider: RunProvider, model: RunModel, field?: string): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Closed set of model ids accepted by the public run-submission schema.
3
+ *
4
+ * The provider-proxy still sends the model id through to the selected upstream
5
+ * provider, but callers cannot submit arbitrary strings. Additions belong here
6
+ * first so SDK types, CLI validation, docs examples, and platform parsing move
7
+ * together.
8
+ */
9
+ export const RUN_MODELS = [
10
+ "claude-haiku-4-5",
11
+ "claude-3-5-haiku-latest",
12
+ "claude-3-5-sonnet-latest",
13
+ "deepseek-chat",
14
+ "gpt-4.1",
15
+ "gpt-4o-mini",
16
+ "gemini-2.0-flash",
17
+ "gemini-2.5-flash",
18
+ "mistral-large-latest",
19
+ "mistral-small-latest"
20
+ ];
21
+ export const RunModels = {
22
+ CLAUDE_HAIKU_4_5: "claude-haiku-4-5",
23
+ CLAUDE_3_5_HAIKU_LATEST: "claude-3-5-haiku-latest",
24
+ CLAUDE_3_5_SONNET_LATEST: "claude-3-5-sonnet-latest",
25
+ DEEPSEEK_CHAT: "deepseek-chat",
26
+ GPT_4_1: "gpt-4.1",
27
+ GPT_4O_MINI: "gpt-4o-mini",
28
+ GEMINI_2_0_FLASH: "gemini-2.0-flash",
29
+ GEMINI_2_5_FLASH: "gemini-2.5-flash",
30
+ MISTRAL_LARGE_LATEST: "mistral-large-latest",
31
+ MISTRAL_SMALL_LATEST: "mistral-small-latest"
32
+ };
33
+ export const RUN_MODELS_BY_PROVIDER = {
34
+ anthropic: [
35
+ RunModels.CLAUDE_HAIKU_4_5,
36
+ RunModels.CLAUDE_3_5_HAIKU_LATEST,
37
+ RunModels.CLAUDE_3_5_SONNET_LATEST
38
+ ],
39
+ deepseek: [RunModels.DEEPSEEK_CHAT],
40
+ openai: [RunModels.GPT_4_1, RunModels.GPT_4O_MINI],
41
+ gemini: [RunModels.GEMINI_2_0_FLASH, RunModels.GEMINI_2_5_FLASH],
42
+ mistral: [RunModels.MISTRAL_LARGE_LATEST, RunModels.MISTRAL_SMALL_LATEST]
43
+ };
44
+ export function isRunModel(input) {
45
+ return typeof input === "string" && RUN_MODELS.includes(input);
46
+ }
47
+ export function parseRunModel(input, field = "submission.model") {
48
+ if (!isRunModel(input)) {
49
+ throw new Error(`${field} must be one of: ${RUN_MODELS.join(", ")}`);
50
+ }
51
+ return input;
52
+ }
53
+ export function assertRunModelMatchesProvider(provider, model, field = "submission.model") {
54
+ if (!RUN_MODELS_BY_PROVIDER[provider].includes(model)) {
55
+ throw new Error(`${field} ${JSON.stringify(model)} is not supported for provider ${provider}; ` +
56
+ `expected one of: ${RUN_MODELS_BY_PROVIDER[provider].join(", ")}`);
57
+ }
58
+ }
59
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1,28 @@
1
+ /** Default post-agent-run hook timeout (5 minutes). */
2
+ export declare const DEFAULT_POST_HOOK_TIMEOUT_MS: number;
3
+ /** Default number of repair turns after a failing hook. */
4
+ export declare const DEFAULT_POST_HOOK_MAX_TURNS = 10;
5
+ /**
6
+ * Customer-facing post-agent-run verifier shape. The SDK and run-config JSON
7
+ * send this form on the wire; the server parser normalizes it to
8
+ * {@link PlatformPostHook}.
9
+ */
10
+ export interface PlatformPostHookInput {
11
+ readonly command: string;
12
+ readonly timeout?: string;
13
+ readonly maxTurns?: number;
14
+ readonly maxChars?: number | null;
15
+ }
16
+ /** Parsed post-agent-run verifier carried through dispatch to the runner. */
17
+ export interface PlatformPostHook {
18
+ readonly command: string;
19
+ readonly timeoutMs: number;
20
+ readonly maxTurns: number;
21
+ readonly maxChars: number | null;
22
+ }
23
+ /**
24
+ * Parse the public `postHook` option. An omitted hook, null hook, or hook with
25
+ * an empty/whitespace-only command is treated as omitted so callers can pre-fill
26
+ * configs without enabling the verifier accidentally.
27
+ */
28
+ export declare function parsePostHook(input: unknown, path?: string): PlatformPostHook | undefined;
@@ -0,0 +1,68 @@
1
+ import { parseDurationToMs } from "./runtime-sizes.js";
2
+ /** Default post-agent-run hook timeout (5 minutes). */
3
+ export const DEFAULT_POST_HOOK_TIMEOUT_MS = 5 * 60 * 1000;
4
+ /** Default number of repair turns after a failing hook. */
5
+ export const DEFAULT_POST_HOOK_MAX_TURNS = 10;
6
+ /**
7
+ * Parse the public `postHook` option. An omitted hook, null hook, or hook with
8
+ * an empty/whitespace-only command is treated as omitted so callers can pre-fill
9
+ * configs without enabling the verifier accidentally.
10
+ */
11
+ export function parsePostHook(input, path = "postHook") {
12
+ if (input === undefined || input === null) {
13
+ return undefined;
14
+ }
15
+ const value = requirePostHookRecord(input, path);
16
+ const allowed = new Set(["command", "timeout", "maxTurns", "maxChars"]);
17
+ for (const key of Object.keys(value)) {
18
+ if (!allowed.has(key)) {
19
+ throw new Error(`${path}.${key} is not an allowed field; permitted: command, timeout, maxTurns, maxChars`);
20
+ }
21
+ }
22
+ if (typeof value.command !== "string") {
23
+ throw new Error(`${path}.command must be a string`);
24
+ }
25
+ if (value.command.trim().length === 0) {
26
+ return undefined;
27
+ }
28
+ const timeoutMs = parsePostHookTimeout(value.timeout, `${path}.timeout`);
29
+ const maxTurns = parseNonNegativeInteger(value.maxTurns, `${path}.maxTurns`, DEFAULT_POST_HOOK_MAX_TURNS);
30
+ const maxChars = value.maxChars === null
31
+ ? null
32
+ : parseNonNegativeInteger(value.maxChars, `${path}.maxChars`, null);
33
+ return {
34
+ command: value.command,
35
+ timeoutMs,
36
+ maxTurns,
37
+ maxChars
38
+ };
39
+ }
40
+ function parsePostHookTimeout(input, path) {
41
+ if (input === undefined) {
42
+ return DEFAULT_POST_HOOK_TIMEOUT_MS;
43
+ }
44
+ if (typeof input !== "string") {
45
+ throw new Error(`${path} must be a duration string (e.g. "5m", "30s"); got ${JSON.stringify(input)}`);
46
+ }
47
+ const ms = parseDurationToMs(input);
48
+ if (ms <= 0) {
49
+ throw new Error(`${path} must be greater than 0ms; got ${ms}ms`);
50
+ }
51
+ return ms;
52
+ }
53
+ function parseNonNegativeInteger(input, path, defaultValue) {
54
+ if (input === undefined) {
55
+ return defaultValue;
56
+ }
57
+ if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0) {
58
+ throw new Error(`${path} must be a non-negative integer`);
59
+ }
60
+ return input;
61
+ }
62
+ function requirePostHookRecord(input, path) {
63
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
64
+ throw new Error(`${path} must be an object`);
65
+ }
66
+ return input;
67
+ }
68
+ //# sourceMappingURL=post-hook.js.map
@@ -39,8 +39,6 @@ export declare const PROXY_RESP_MODE_HEADER = "x-aex-proxy-effective-mode";
39
39
  * without buffering). See the streaming byte-cap note in proxy-routes.ts.
40
40
  */
41
41
  export declare const PROXY_RESP_TRUNCATED_HEADER = "x-aex-proxy-truncated";
42
- export declare const PROXY_RESP_REMAINING_CALLS_HEADER = "x-aex-proxy-remaining-calls";
43
- export declare const PROXY_RESP_REMAINING_BYTES_HEADER = "x-aex-proxy-remaining-bytes";
44
42
  /** JSON object of lowercase upstream header names → values (mode-filtered). */
45
43
  export declare const PROXY_RESP_UPSTREAM_HEADERS_HEADER = "x-aex-proxy-upstream-headers";
46
44
  /**
@@ -77,7 +75,7 @@ export declare function narrowResponseMode(policy: ProxyResponseMode, requested:
77
75
  * matches against them in scripts. Adding a new code is non-breaking;
78
76
  * removing or renaming an existing code requires a protocol bump.
79
77
  */
80
- export declare const PROXY_ERROR_CODES: readonly ["unsupported_protocol", "unauthorized", "endpoint_not_found", "policy_denied", "rate_limited", "budget_exceeded", "ssrf_denied", "upstream_timeout", "upstream_error", "exceeded_cap", "bad_request", "internal_error"];
78
+ 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"];
81
79
  export type ProxyErrorCode = (typeof PROXY_ERROR_CODES)[number];
82
80
  /**
83
81
  * Shape of the JSON written to the per-run manifest mounted inside
@@ -108,23 +106,26 @@ export interface ProxyIndexEntry {
108
106
  readonly maxRequestBytes: number;
109
107
  readonly maxResponseBytes: number;
110
108
  readonly timeoutMs: number;
111
- readonly perCallBudget: number;
112
- readonly responseByteBudget: number;
113
109
  }
114
110
  /**
115
- * Default caps for a proxy endpoint when the submission doesn't specify
116
- * one. Conservative on purpose. Lives in the protocol module (next to the
117
- * index-file shape) so {@link buildProxyIndexFile} can fill every optional
118
- * cap with a concrete value; the submission parser re-exports it.
111
+ * Default caps for a proxy endpoint when the submission doesn't specify one.
112
+ * Lives in the protocol module (next to the index-file shape) so
113
+ * {@link buildProxyIndexFile} can fill every optional cap with a concrete
114
+ * value; the submission parser re-exports it.
115
+ *
116
+ * `maxResponseBytes: 0` means UNLIMITED — the v2 path streams the upstream body
117
+ * unbuffered (O(1) memory regardless of size), so there is no cap to apply by
118
+ * default. A customer can opt into a per-response truncation cap by setting a
119
+ * positive value. There is no cumulative per-run call/byte budget: it needed a
120
+ * per-call counter on the hot path and only existed to bound memory, which
121
+ * streaming already does.
119
122
  */
120
123
  export declare const PROXY_ENDPOINT_DEFAULTS: {
121
124
  readonly allowHeaders: readonly string[];
122
125
  readonly responseMode: ProxyResponseMode;
123
126
  readonly maxRequestBytes: number;
124
- readonly maxResponseBytes: number;
127
+ readonly maxResponseBytes: 0;
125
128
  readonly timeoutMs: 10000;
126
- readonly perCallBudget: 60;
127
- readonly responseByteBudget: number;
128
129
  };
129
130
  /**
130
131
  * Non-secret endpoint policy the index builder consumes. Structurally a
@@ -142,8 +143,6 @@ export interface ProxyEndpointPolicy {
142
143
  readonly maxRequestBytes?: number;
143
144
  readonly maxResponseBytes?: number;
144
145
  readonly timeoutMs?: number;
145
- readonly perCallBudget?: number;
146
- readonly responseByteBudget?: number;
147
146
  }
148
147
  export interface BuildProxyIndexFileInput {
149
148
  readonly runId: string;
@@ -176,7 +175,7 @@ export declare function buildProxyIndexFile(input: BuildProxyIndexFileInput): Pr
176
175
  *
177
176
  * The `none` variant declares an upstream that takes no auth (public
178
177
  * APIs like Wikimedia Commons or NASA Images). It still routes through
179
- * the proxy for unified egress, audit, and budget enforcement, but
178
+ * the proxy for unified egress and audit, but
180
179
  * carries no `proxyEndpointAuth[]` entry and the BFF injects no
181
180
  * header or query value.
182
181
  */
@@ -261,9 +260,6 @@ export interface ProxyResponseEnvelope {
261
260
  */
262
261
  readonly effectiveResponseMode: ProxyResponseMode;
263
262
  readonly modeClamped: boolean;
264
- /** Remaining per-endpoint per-run budget after this call. */
265
- readonly remainingCalls: number;
266
- readonly remainingResponseBytes: number;
267
263
  }
268
264
  /**
269
265
  * JSON body returned on any error. The CLI emits this verbatim on
@@ -48,8 +48,6 @@ export const PROXY_RESP_MODE_HEADER = "x-aex-proxy-effective-mode";
48
48
  * without buffering). See the streaming byte-cap note in proxy-routes.ts.
49
49
  */
50
50
  export const PROXY_RESP_TRUNCATED_HEADER = "x-aex-proxy-truncated";
51
- export const PROXY_RESP_REMAINING_CALLS_HEADER = "x-aex-proxy-remaining-calls";
52
- export const PROXY_RESP_REMAINING_BYTES_HEADER = "x-aex-proxy-remaining-bytes";
53
51
  /** JSON object of lowercase upstream header names → values (mode-filtered). */
54
52
  export const PROXY_RESP_UPSTREAM_HEADERS_HEADER = "x-aex-proxy-upstream-headers";
55
53
  /**
@@ -103,7 +101,6 @@ export const PROXY_ERROR_CODES = [
103
101
  "endpoint_not_found",
104
102
  "policy_denied",
105
103
  "rate_limited",
106
- "budget_exceeded",
107
104
  "ssrf_denied",
108
105
  "upstream_timeout",
109
106
  "upstream_error",
@@ -112,19 +109,26 @@ export const PROXY_ERROR_CODES = [
112
109
  "internal_error"
113
110
  ];
114
111
  /**
115
- * Default caps for a proxy endpoint when the submission doesn't specify
116
- * one. Conservative on purpose. Lives in the protocol module (next to the
117
- * index-file shape) so {@link buildProxyIndexFile} can fill every optional
118
- * cap with a concrete value; the submission parser re-exports it.
112
+ * Default caps for a proxy endpoint when the submission doesn't specify one.
113
+ * Lives in the protocol module (next to the index-file shape) so
114
+ * {@link buildProxyIndexFile} can fill every optional cap with a concrete
115
+ * value; the submission parser re-exports it.
116
+ *
117
+ * `maxResponseBytes: 0` means UNLIMITED — the v2 path streams the upstream body
118
+ * unbuffered (O(1) memory regardless of size), so there is no cap to apply by
119
+ * default. A customer can opt into a per-response truncation cap by setting a
120
+ * positive value. There is no cumulative per-run call/byte budget: it needed a
121
+ * per-call counter on the hot path and only existed to bound memory, which
122
+ * streaming already does.
119
123
  */
120
124
  export const PROXY_ENDPOINT_DEFAULTS = {
121
125
  allowHeaders: [],
122
126
  responseMode: "headers_only",
123
127
  maxRequestBytes: 64 * 1024,
124
- maxResponseBytes: 1024 * 1024,
125
- timeoutMs: 10_000,
126
- perCallBudget: 60,
127
- responseByteBudget: 1024 * 1024
128
+ // Unlimited (0). The request body is buffered to enforce its cap, so that
129
+ // stays finite; the response is streamed, so it does not need one.
130
+ maxResponseBytes: 0,
131
+ timeoutMs: 10_000
128
132
  };
129
133
  /**
130
134
  * Build the per-run {@link ProxyIndexFile} mounted into the container at
@@ -158,9 +162,7 @@ export function buildProxyIndexFile(input) {
158
162
  responseMode: e.responseMode ?? PROXY_ENDPOINT_DEFAULTS.responseMode,
159
163
  maxRequestBytes: e.maxRequestBytes ?? PROXY_ENDPOINT_DEFAULTS.maxRequestBytes,
160
164
  maxResponseBytes: e.maxResponseBytes ?? PROXY_ENDPOINT_DEFAULTS.maxResponseBytes,
161
- timeoutMs: e.timeoutMs ?? PROXY_ENDPOINT_DEFAULTS.timeoutMs,
162
- perCallBudget: e.perCallBudget ?? PROXY_ENDPOINT_DEFAULTS.perCallBudget,
163
- responseByteBudget: e.responseByteBudget ?? PROXY_ENDPOINT_DEFAULTS.responseByteBudget
165
+ timeoutMs: e.timeoutMs ?? PROXY_ENDPOINT_DEFAULTS.timeoutMs
164
166
  }))
165
167
  };
166
168
  }
@@ -256,7 +258,6 @@ export const PROXY_ERROR_HTTP_STATUS = {
256
258
  endpoint_not_found: 404,
257
259
  policy_denied: 403,
258
260
  rate_limited: 429,
259
- budget_exceeded: 429,
260
261
  ssrf_denied: 403,
261
262
  upstream_timeout: 504,
262
263
  upstream_error: 502,
@@ -3,17 +3,11 @@
3
3
  *
4
4
  * Public composition concepts:
5
5
  *
6
- * - `SkillRef` is the wire-level reference to a skill either an
7
- * `skl_*` id pointing at a workspace-uploaded bundle, a
8
- * `{vendor, skillId, version}` reference to a provider built-in, or
9
- * a `{slot, name, contentHash}` reference to per-run bytes attached
10
- * as a multipart part on the submitRun call (and torn down at run
11
- * terminal). The three shapes are discriminated by `kind` so
12
- * consumers branch mechanically and providers can never accidentally
13
- * be looked up in `skill_bundles`. Transient refs do NOT round-trip
14
- * through JSON (bytes can't be serialised back); `parseRunRequestConfig`
15
- * therefore rejects them while the BFF multipart submission parser
16
- * accepts them.
6
+ * - `SkillRef` is the wire-level reference to a skill. Public run
7
+ * configs use storage-neutral `kind:"asset"` refs produced by the SDK
8
+ * upload path or by workspace catalog records. Provider skill refs remain
9
+ * in the parser for wire compatibility, but the managed runtime rejects
10
+ * them at submission time.
17
11
  *
18
12
  * - `McpServerRef` is the non-secret part of an MCP server declaration:
19
13
  * `name` and `url`. Bearer / cookie / per-request headers travel in
@@ -37,7 +31,9 @@
37
31
  * boundary.
38
32
  */
39
33
  import type { JsonValue, PlatformProxyEndpoint, PlatformEnvironment } from "./submission.js";
34
+ import { type RunModel } from "./models.js";
40
35
  import type { RuntimeSize } from "./runtime-sizes.js";
36
+ import { type PlatformPostHookInput } from "./post-hook.js";
41
37
  /**
42
38
  * Mirrors the server-side CHECK constraint
43
39
  * `skill_bundles_id_format_chk = check (id ~ '^skl_[A-Za-z0-9_-]{8,128}$')`
@@ -62,10 +58,9 @@ export declare const SKILL_BUNDLE_LIMITS: {
62
58
  /** Compressed (.zip) ceiling. */
63
59
  readonly maxCompressedBytes: number;
64
60
  /**
65
- * Hard ceiling for the direct-to-storage (presigned PUT) upload path, where
66
- * bytes never transit the hosted API so its memory/request-payload limits no
67
- * longer cap the bundle. Kept well under the object store's 5 GiB single-PUT
68
- * limit; objects above this would need S3 multipart, which is out of scope.
61
+ * Hard ceiling for the direct-to-storage upload path. Bytes never transit the
62
+ * hosted API, so its memory/request-payload limits do not cap accepted
63
+ * bundles; objects above this product cap are rejected before upload.
69
64
  */
70
65
  readonly maxBytes: number;
71
66
  /** Sum of uncompressed file sizes. */
@@ -259,7 +254,7 @@ export declare function rejectStdioMcpShape(record: Record<string, unknown>): vo
259
254
  * into the normal `submitRun` request.
260
255
  */
261
256
  export interface RunRequestConfig {
262
- readonly model: string;
257
+ readonly model: RunModel;
263
258
  readonly system?: string;
264
259
  readonly prompt: string | readonly string[];
265
260
  readonly skills?: readonly SkillRef[];
@@ -269,6 +264,8 @@ export interface RunRequestConfig {
269
264
  readonly runtimeSize?: RuntimeSize;
270
265
  /** Run deadline as a duration string (`"1h"`, `"30m"`); bounded [1m, 6h] server-side. */
271
266
  readonly timeout?: string;
267
+ /** Post-agent-run verifier command. Empty command is treated as omitted. */
268
+ readonly postHook?: PlatformPostHookInput;
272
269
  readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
273
270
  readonly metadata?: Readonly<Record<string, JsonValue>>;
274
271
  }
@@ -290,7 +287,7 @@ export declare function parseRunRequestConfig(input: unknown): RunRequestConfig;
290
287
  * the audit log don't have to re-handle two shapes.
291
288
  */
292
289
  export interface NormalisedRunRequestConfig {
293
- readonly model: string;
290
+ readonly model: RunModel;
294
291
  readonly system?: string;
295
292
  readonly prompt: readonly string[];
296
293
  readonly skills: readonly SkillRef[];
@@ -298,6 +295,7 @@ export interface NormalisedRunRequestConfig {
298
295
  readonly environment?: PlatformEnvironment;
299
296
  readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
300
297
  readonly metadata?: Readonly<Record<string, JsonValue>>;
298
+ readonly postHook?: PlatformPostHookInput;
301
299
  /**
302
300
  * MCP servers whose run-config entry carried `headers`. Keyed by the `name`
303
301
  * that appears in `mcpServers` so the BFF can pair them up.
@@ -3,17 +3,11 @@
3
3
  *
4
4
  * Public composition concepts:
5
5
  *
6
- * - `SkillRef` is the wire-level reference to a skill either an
7
- * `skl_*` id pointing at a workspace-uploaded bundle, a
8
- * `{vendor, skillId, version}` reference to a provider built-in, or
9
- * a `{slot, name, contentHash}` reference to per-run bytes attached
10
- * as a multipart part on the submitRun call (and torn down at run
11
- * terminal). The three shapes are discriminated by `kind` so
12
- * consumers branch mechanically and providers can never accidentally
13
- * be looked up in `skill_bundles`. Transient refs do NOT round-trip
14
- * through JSON (bytes can't be serialised back); `parseRunRequestConfig`
15
- * therefore rejects them while the BFF multipart submission parser
16
- * accepts them.
6
+ * - `SkillRef` is the wire-level reference to a skill. Public run
7
+ * configs use storage-neutral `kind:"asset"` refs produced by the SDK
8
+ * upload path or by workspace catalog records. Provider skill refs remain
9
+ * in the parser for wire compatibility, but the managed runtime rejects
10
+ * them at submission time.
17
11
  *
18
12
  * - `McpServerRef` is the non-secret part of an MCP server declaration:
19
13
  * `name` and `url`. Bearer / cookie / per-request headers travel in
@@ -36,6 +30,8 @@
36
30
  * Keep this as the public source of truth for the SDK/CLI composition
37
31
  * boundary.
38
32
  */
33
+ import { parseRunModel } from "./models.js";
34
+ import { parsePostHook } from "./post-hook.js";
39
35
  // ---------------------------------------------------------------------------
40
36
  // Skill ID + name format
41
37
  // ---------------------------------------------------------------------------
@@ -66,10 +62,9 @@ export const SKILL_BUNDLE_LIMITS = {
66
62
  /** Compressed (.zip) ceiling. */
67
63
  maxCompressedBytes: 10 * 1024 * 1024,
68
64
  /**
69
- * Hard ceiling for the direct-to-storage (presigned PUT) upload path, where
70
- * bytes never transit the hosted API so its memory/request-payload limits no
71
- * longer cap the bundle. Kept well under the object store's 5 GiB single-PUT
72
- * limit; objects above this would need S3 multipart, which is out of scope.
65
+ * Hard ceiling for the direct-to-storage upload path. Bytes never transit the
66
+ * hosted API, so its memory/request-payload limits do not cap accepted
67
+ * bundles; objects above this product cap are rejected before upload.
73
68
  */
74
69
  maxBytes: 2 * 1024 * 1024 * 1024,
75
70
  /** Sum of uncompressed file sizes. */
@@ -609,6 +604,7 @@ export function parseRunRequestConfig(input) {
609
604
  "environment",
610
605
  "runtimeSize",
611
606
  "timeout",
607
+ "postHook",
612
608
  "proxyEndpoints",
613
609
  "metadata"
614
610
  ]);
@@ -617,10 +613,7 @@ export function parseRunRequestConfig(input) {
617
613
  throw new Error(`run request config contains unexpected field: ${key}`);
618
614
  }
619
615
  }
620
- const model = record.model;
621
- if (typeof model !== "string" || model.length === 0) {
622
- throw new Error("run request config model must be a non-empty string");
623
- }
616
+ const model = parseRunModel(record.model, "run request config model");
624
617
  const system = record.system;
625
618
  if (system !== undefined && typeof system !== "string") {
626
619
  throw new Error("run request config system, when provided, must be a string");
@@ -628,6 +621,7 @@ export function parseRunRequestConfig(input) {
628
621
  const prompt = parseRunRequestConfigPrompt(record.prompt);
629
622
  const skills = parseRunRequestConfigSkills(record.skills);
630
623
  const mcpServers = parseRunRequestConfigMcpServers(record.mcpServers);
624
+ const postHook = parsePostHook(record.postHook, "run request config postHook");
631
625
  return {
632
626
  model,
633
627
  ...(system !== undefined ? { system } : {}),
@@ -647,6 +641,9 @@ export function parseRunRequestConfig(input) {
647
641
  ...(record.timeout !== undefined
648
642
  ? { timeout: record.timeout }
649
643
  : {}),
644
+ ...(postHook !== undefined
645
+ ? { postHook: record.postHook }
646
+ : {}),
650
647
  ...(record.proxyEndpoints !== undefined
651
648
  ? { proxyEndpoints: record.proxyEndpoints }
652
649
  : {}),
@@ -724,6 +721,7 @@ export function normaliseRunRequestConfig(config) {
724
721
  ...(config.environment !== undefined ? { environment: config.environment } : {}),
725
722
  ...(config.proxyEndpoints !== undefined ? { proxyEndpoints: config.proxyEndpoints } : {}),
726
723
  ...(config.metadata !== undefined ? { metadata: config.metadata } : {}),
724
+ ...(config.postHook !== undefined ? { postHook: config.postHook } : {}),
727
725
  mcpServerSecrets
728
726
  };
729
727
  }
@@ -185,9 +185,9 @@ export interface RunUnit {
185
185
  * submission. Never throws on minor unknown keys so we can
186
186
  * forward-compat with worker-side enrichment.
187
187
  *
188
- * Returns a typed shape even for malformed snapshots the worst case
189
- * is `{kind: "submission", submission: {model: "", ...}}` with empty
190
- * defaults — because the dashboard must still render *something* for a
191
- * buggy historical row rather than 500ing the whole detail page.
188
+ * Returns a typed shape even for malformed snapshots by falling back to the
189
+ * default public model and empty collection defaults, because the dashboard
190
+ * must still render *something* for a buggy historical row rather than 500ing
191
+ * the whole detail page.
192
192
  */
193
193
  export declare function parseRunUnitSubmission(input: unknown): RunUnitSubmission;