@aexhq/sdk 0.16.0 → 0.17.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 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,113 @@
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-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner", "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
+ /**
13
+ * Symbol-style accessors for the closed model set. Prefer these over raw
14
+ * strings so an invalid token is a compile error, not a runtime 400 — e.g.
15
+ * `Models.CLAUDE_HAIKU_4_5`. The upstream provider is a pure function of the
16
+ * model ({@link providerForModel}), so picking a model fully determines
17
+ * routing; callers never pass `provider` separately.
18
+ */
19
+ export declare const Models: {
20
+ /** Claude Haiku 4.5 — Anthropic. */
21
+ readonly CLAUDE_HAIKU_4_5: "claude-haiku-4-5";
22
+ /** Claude 3.5 Haiku (latest) — Anthropic. */
23
+ readonly CLAUDE_3_5_HAIKU_LATEST: "claude-3-5-haiku-latest";
24
+ /** Claude 3.5 Sonnet (latest) — Anthropic. */
25
+ readonly CLAUDE_3_5_SONNET_LATEST: "claude-3-5-sonnet-latest";
26
+ /** DeepSeek V4 Flash — DeepSeek (non-thinking, fast). */
27
+ readonly DEEPSEEK_V4_FLASH: "deepseek-v4-flash";
28
+ /** DeepSeek V4 Pro — DeepSeek (reasoning-heavy). */
29
+ readonly DEEPSEEK_V4_PRO: "deepseek-v4-pro";
30
+ /**
31
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (non-thinking).
32
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
33
+ * {@link Models.DEEPSEEK_V4_FLASH}.
34
+ */
35
+ readonly DEEPSEEK_CHAT: "deepseek-chat";
36
+ /**
37
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (thinking).
38
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
39
+ * {@link Models.DEEPSEEK_V4_FLASH} (or {@link Models.DEEPSEEK_V4_PRO} for
40
+ * heavier reasoning).
41
+ */
42
+ readonly DEEPSEEK_REASONER: "deepseek-reasoner";
43
+ /** GPT-4.1 — OpenAI. */
44
+ readonly GPT_4_1: "gpt-4.1";
45
+ /** GPT-4o mini — OpenAI. */
46
+ readonly GPT_4O_MINI: "gpt-4o-mini";
47
+ /** Gemini 2.0 Flash — Gemini. */
48
+ readonly GEMINI_2_0_FLASH: "gemini-2.0-flash";
49
+ /** Gemini 2.5 Flash — Gemini. */
50
+ readonly GEMINI_2_5_FLASH: "gemini-2.5-flash";
51
+ /** Mistral Large (latest) — Mistral. */
52
+ readonly MISTRAL_LARGE_LATEST: "mistral-large-latest";
53
+ /** Mistral Small (latest) — Mistral. */
54
+ readonly MISTRAL_SMALL_LATEST: "mistral-small-latest";
55
+ };
56
+ /**
57
+ * Back-compat alias for {@link Models}. Existing imports of `RunModels`
58
+ * keep working; new code should prefer `Models`.
59
+ */
60
+ export declare const RunModels: {
61
+ /** Claude Haiku 4.5 — Anthropic. */
62
+ readonly CLAUDE_HAIKU_4_5: "claude-haiku-4-5";
63
+ /** Claude 3.5 Haiku (latest) — Anthropic. */
64
+ readonly CLAUDE_3_5_HAIKU_LATEST: "claude-3-5-haiku-latest";
65
+ /** Claude 3.5 Sonnet (latest) — Anthropic. */
66
+ readonly CLAUDE_3_5_SONNET_LATEST: "claude-3-5-sonnet-latest";
67
+ /** DeepSeek V4 Flash — DeepSeek (non-thinking, fast). */
68
+ readonly DEEPSEEK_V4_FLASH: "deepseek-v4-flash";
69
+ /** DeepSeek V4 Pro — DeepSeek (reasoning-heavy). */
70
+ readonly DEEPSEEK_V4_PRO: "deepseek-v4-pro";
71
+ /**
72
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (non-thinking).
73
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
74
+ * {@link Models.DEEPSEEK_V4_FLASH}.
75
+ */
76
+ readonly DEEPSEEK_CHAT: "deepseek-chat";
77
+ /**
78
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (thinking).
79
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
80
+ * {@link Models.DEEPSEEK_V4_FLASH} (or {@link Models.DEEPSEEK_V4_PRO} for
81
+ * heavier reasoning).
82
+ */
83
+ readonly DEEPSEEK_REASONER: "deepseek-reasoner";
84
+ /** GPT-4.1 — OpenAI. */
85
+ readonly GPT_4_1: "gpt-4.1";
86
+ /** GPT-4o mini — OpenAI. */
87
+ readonly GPT_4O_MINI: "gpt-4o-mini";
88
+ /** Gemini 2.0 Flash — Gemini. */
89
+ readonly GEMINI_2_0_FLASH: "gemini-2.0-flash";
90
+ /** Gemini 2.5 Flash — Gemini. */
91
+ readonly GEMINI_2_5_FLASH: "gemini-2.5-flash";
92
+ /** Mistral Large (latest) — Mistral. */
93
+ readonly MISTRAL_LARGE_LATEST: "mistral-large-latest";
94
+ /** Mistral Small (latest) — Mistral. */
95
+ readonly MISTRAL_SMALL_LATEST: "mistral-small-latest";
96
+ };
97
+ export declare const RUN_MODELS_BY_PROVIDER: {
98
+ readonly anthropic: readonly ["claude-haiku-4-5", "claude-3-5-haiku-latest", "claude-3-5-sonnet-latest"];
99
+ readonly deepseek: readonly ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"];
100
+ readonly openai: readonly ["gpt-4.1", "gpt-4o-mini"];
101
+ readonly gemini: readonly ["gemini-2.0-flash", "gemini-2.5-flash"];
102
+ readonly mistral: readonly ["mistral-large-latest", "mistral-small-latest"];
103
+ };
104
+ /**
105
+ * Resolve the upstream provider for a model id. Returns `undefined` when the
106
+ * input is not a known {@link RunModel} (so the SDK can fall back to the
107
+ * default and let the server reject the model). Total over the closed model
108
+ * set, so any `RunModel` resolves.
109
+ */
110
+ export declare function providerForModel(model: string): RunProvider | undefined;
111
+ export declare function isRunModel(input: unknown): input is RunModel;
112
+ export declare function parseRunModel(input: unknown, field?: string): RunModel;
113
+ export declare function assertRunModelMatchesProvider(provider: RunProvider, model: RunModel, field?: string): void;
@@ -0,0 +1,127 @@
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-v4-flash",
14
+ "deepseek-v4-pro",
15
+ "deepseek-chat",
16
+ "deepseek-reasoner",
17
+ "gpt-4.1",
18
+ "gpt-4o-mini",
19
+ "gemini-2.0-flash",
20
+ "gemini-2.5-flash",
21
+ "mistral-large-latest",
22
+ "mistral-small-latest"
23
+ ];
24
+ /**
25
+ * Symbol-style accessors for the closed model set. Prefer these over raw
26
+ * strings so an invalid token is a compile error, not a runtime 400 — e.g.
27
+ * `Models.CLAUDE_HAIKU_4_5`. The upstream provider is a pure function of the
28
+ * model ({@link providerForModel}), so picking a model fully determines
29
+ * routing; callers never pass `provider` separately.
30
+ */
31
+ export const Models = {
32
+ /** Claude Haiku 4.5 — Anthropic. */
33
+ CLAUDE_HAIKU_4_5: "claude-haiku-4-5",
34
+ /** Claude 3.5 Haiku (latest) — Anthropic. */
35
+ CLAUDE_3_5_HAIKU_LATEST: "claude-3-5-haiku-latest",
36
+ /** Claude 3.5 Sonnet (latest) — Anthropic. */
37
+ CLAUDE_3_5_SONNET_LATEST: "claude-3-5-sonnet-latest",
38
+ /** DeepSeek V4 Flash — DeepSeek (non-thinking, fast). */
39
+ DEEPSEEK_V4_FLASH: "deepseek-v4-flash",
40
+ /** DeepSeek V4 Pro — DeepSeek (reasoning-heavy). */
41
+ DEEPSEEK_V4_PRO: "deepseek-v4-pro",
42
+ /**
43
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (non-thinking).
44
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
45
+ * {@link Models.DEEPSEEK_V4_FLASH}.
46
+ */
47
+ DEEPSEEK_CHAT: "deepseek-chat",
48
+ /**
49
+ * @deprecated Legacy alias DeepSeek routes to `deepseek-v4-flash` (thinking).
50
+ * DeepSeek removes this name on 2026-07-24 15:59 UTC — migrate to
51
+ * {@link Models.DEEPSEEK_V4_FLASH} (or {@link Models.DEEPSEEK_V4_PRO} for
52
+ * heavier reasoning).
53
+ */
54
+ DEEPSEEK_REASONER: "deepseek-reasoner",
55
+ /** GPT-4.1 — OpenAI. */
56
+ GPT_4_1: "gpt-4.1",
57
+ /** GPT-4o mini — OpenAI. */
58
+ GPT_4O_MINI: "gpt-4o-mini",
59
+ /** Gemini 2.0 Flash — Gemini. */
60
+ GEMINI_2_0_FLASH: "gemini-2.0-flash",
61
+ /** Gemini 2.5 Flash — Gemini. */
62
+ GEMINI_2_5_FLASH: "gemini-2.5-flash",
63
+ /** Mistral Large (latest) — Mistral. */
64
+ MISTRAL_LARGE_LATEST: "mistral-large-latest",
65
+ /** Mistral Small (latest) — Mistral. */
66
+ MISTRAL_SMALL_LATEST: "mistral-small-latest"
67
+ };
68
+ /**
69
+ * Back-compat alias for {@link Models}. Existing imports of `RunModels`
70
+ * keep working; new code should prefer `Models`.
71
+ */
72
+ export const RunModels = Models;
73
+ export const RUN_MODELS_BY_PROVIDER = {
74
+ anthropic: [
75
+ Models.CLAUDE_HAIKU_4_5,
76
+ Models.CLAUDE_3_5_HAIKU_LATEST,
77
+ Models.CLAUDE_3_5_SONNET_LATEST
78
+ ],
79
+ deepseek: [
80
+ Models.DEEPSEEK_V4_FLASH,
81
+ Models.DEEPSEEK_V4_PRO,
82
+ Models.DEEPSEEK_CHAT,
83
+ Models.DEEPSEEK_REASONER
84
+ ],
85
+ openai: [Models.GPT_4_1, Models.GPT_4O_MINI],
86
+ gemini: [Models.GEMINI_2_0_FLASH, Models.GEMINI_2_5_FLASH],
87
+ mistral: [Models.MISTRAL_LARGE_LATEST, Models.MISTRAL_SMALL_LATEST]
88
+ };
89
+ /**
90
+ * Reverse index: every model id → its single upstream provider. Derived from
91
+ * {@link RUN_MODELS_BY_PROVIDER} so the two never drift; each model appears
92
+ * under exactly one provider.
93
+ */
94
+ const PROVIDER_BY_MODEL = (() => {
95
+ const map = {};
96
+ for (const [provider, models] of Object.entries(RUN_MODELS_BY_PROVIDER)) {
97
+ for (const model of models) {
98
+ map[model] = provider;
99
+ }
100
+ }
101
+ return map;
102
+ })();
103
+ /**
104
+ * Resolve the upstream provider for a model id. Returns `undefined` when the
105
+ * input is not a known {@link RunModel} (so the SDK can fall back to the
106
+ * default and let the server reject the model). Total over the closed model
107
+ * set, so any `RunModel` resolves.
108
+ */
109
+ export function providerForModel(model) {
110
+ return PROVIDER_BY_MODEL[model];
111
+ }
112
+ export function isRunModel(input) {
113
+ return typeof input === "string" && RUN_MODELS.includes(input);
114
+ }
115
+ export function parseRunModel(input, field = "submission.model") {
116
+ if (!isRunModel(input)) {
117
+ throw new Error(`${field} must be one of: ${RUN_MODELS.join(", ")}`);
118
+ }
119
+ return input;
120
+ }
121
+ export function assertRunModelMatchesProvider(provider, model, field = "submission.model") {
122
+ if (!RUN_MODELS_BY_PROVIDER[provider].includes(model)) {
123
+ throw new Error(`${field} ${JSON.stringify(model)} is not supported for provider ${provider}; ` +
124
+ `expected one of: ${RUN_MODELS_BY_PROVIDER[provider].join(", ")}`);
125
+ }
126
+ }
127
+ //# 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
@@ -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;