@aexhq/sdk 0.13.6
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/LICENSE +201 -0
- package/README.md +160 -0
- package/dist/_contracts/connection-ticket.d.ts +21 -0
- package/dist/_contracts/connection-ticket.js +49 -0
- package/dist/_contracts/event-envelope.d.ts +276 -0
- package/dist/_contracts/event-envelope.js +324 -0
- package/dist/_contracts/event-stream-client.d.ts +47 -0
- package/dist/_contracts/event-stream-client.js +141 -0
- package/dist/_contracts/http.d.ts +35 -0
- package/dist/_contracts/http.js +114 -0
- package/dist/_contracts/index.d.ts +28 -0
- package/dist/_contracts/index.js +29 -0
- package/dist/_contracts/managed-key.d.ts +74 -0
- package/dist/_contracts/managed-key.js +110 -0
- package/dist/_contracts/operations.d.ts +237 -0
- package/dist/_contracts/operations.js +632 -0
- package/dist/_contracts/provider-support.d.ts +220 -0
- package/dist/_contracts/provider-support.js +90 -0
- package/dist/_contracts/proxy-protocol.d.ts +257 -0
- package/dist/_contracts/proxy-protocol.js +234 -0
- package/dist/_contracts/proxy-validation.d.ts +19 -0
- package/dist/_contracts/proxy-validation.js +51 -0
- package/dist/_contracts/run-artifacts.d.ts +47 -0
- package/dist/_contracts/run-artifacts.js +101 -0
- package/dist/_contracts/run-config.d.ts +304 -0
- package/dist/_contracts/run-config.js +659 -0
- package/dist/_contracts/run-cost.d.ts +125 -0
- package/dist/_contracts/run-cost.js +616 -0
- package/dist/_contracts/run-custody.d.ts +226 -0
- package/dist/_contracts/run-custody.js +465 -0
- package/dist/_contracts/run-record.d.ts +127 -0
- package/dist/_contracts/run-record.js +177 -0
- package/dist/_contracts/run-retention.d.ts +213 -0
- package/dist/_contracts/run-retention.js +484 -0
- package/dist/_contracts/run-unit.d.ts +194 -0
- package/dist/_contracts/run-unit.js +215 -0
- package/dist/_contracts/runner-event.d.ts +114 -0
- package/dist/_contracts/runner-event.js +187 -0
- package/dist/_contracts/runtime-manifest.d.ts +106 -0
- package/dist/_contracts/runtime-manifest.js +98 -0
- package/dist/_contracts/runtime-security-profile.d.ts +27 -0
- package/dist/_contracts/runtime-security-profile.js +82 -0
- package/dist/_contracts/runtime-sizes.d.ts +144 -0
- package/dist/_contracts/runtime-sizes.js +136 -0
- package/dist/_contracts/runtime-types.d.ts +212 -0
- package/dist/_contracts/runtime-types.js +2 -0
- package/dist/_contracts/sdk-errors.d.ts +34 -0
- package/dist/_contracts/sdk-errors.js +52 -0
- package/dist/_contracts/sdk-secrets.d.ts +31 -0
- package/dist/_contracts/sdk-secrets.js +220 -0
- package/dist/_contracts/side-effect-audit.d.ts +129 -0
- package/dist/_contracts/side-effect-audit.js +494 -0
- package/dist/_contracts/sse.d.ts +74 -0
- package/dist/_contracts/sse.js +0 -0
- package/dist/_contracts/stable.d.ts +26 -0
- package/dist/_contracts/stable.js +44 -0
- package/dist/_contracts/status.d.ts +19 -0
- package/dist/_contracts/status.js +61 -0
- package/dist/_contracts/submission.d.ts +383 -0
- package/dist/_contracts/submission.js +1380 -0
- package/dist/agents-md.d.ts +46 -0
- package/dist/agents-md.js +83 -0
- package/dist/agents-md.js.map +1 -0
- package/dist/asset-upload.d.ts +66 -0
- package/dist/asset-upload.js +168 -0
- package/dist/asset-upload.js.map +1 -0
- package/dist/bundle.d.ts +33 -0
- package/dist/bundle.js +89 -0
- package/dist/bundle.js.map +1 -0
- package/dist/cli.mjs +4140 -0
- package/dist/cli.mjs.sha256 +1 -0
- package/dist/client.d.ts +460 -0
- package/dist/client.js +857 -0
- package/dist/client.js.map +1 -0
- package/dist/fetch-archive.d.ts +16 -0
- package/dist/fetch-archive.js +170 -0
- package/dist/fetch-archive.js.map +1 -0
- package/dist/file.d.ts +57 -0
- package/dist/file.js +153 -0
- package/dist/file.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +84 -0
- package/dist/mcp-server.js +114 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/node-fs.d.ts +12 -0
- package/dist/node-fs.js +44 -0
- package/dist/node-fs.js.map +1 -0
- package/dist/proxy-endpoint.d.ts +131 -0
- package/dist/proxy-endpoint.js +147 -0
- package/dist/proxy-endpoint.js.map +1 -0
- package/dist/skill.d.ts +117 -0
- package/dist/skill.js +169 -0
- package/dist/skill.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/docs/cleanup.md +38 -0
- package/docs/credentials.md +153 -0
- package/docs/events.md +76 -0
- package/docs/mcp.md +47 -0
- package/docs/outputs.md +157 -0
- package/docs/product-boundaries.md +57 -0
- package/docs/provider-runtime-capabilities.md +103 -0
- package/docs/quickstart.md +110 -0
- package/docs/release.md +99 -0
- package/docs/run-config.md +53 -0
- package/docs/run-record.md +39 -0
- package/docs/skills.md +139 -0
- package/docs/testing.md +29 -0
- package/package.json +47 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunUnit — the self-contained read shape of a run.
|
|
3
|
+
*
|
|
4
|
+
* One canonical struct that captures every non-secret artifact persisted
|
|
5
|
+
* for a single run: parsed submission inputs, status/lifecycle, attempts,
|
|
6
|
+
* indexed events, raw-event Storage manifest, outputs (+ capture
|
|
7
|
+
* failures), proxy-call audit log, pinned workspace skills, provider
|
|
8
|
+
* built-in skills, and transient (Anthropic Files) skill records.
|
|
9
|
+
*
|
|
10
|
+
* Wire contract for `GET /api/runs/:runId`, the per-run archive's
|
|
11
|
+
* `run.json`/`submission.json`/`caps.json`, and the SDK/CLI
|
|
12
|
+
* `client.runs.get(runId)` return type.
|
|
13
|
+
*
|
|
14
|
+
* Immutability: every field here is read-only. Edit endpoints do not
|
|
15
|
+
* exist by design.
|
|
16
|
+
*
|
|
17
|
+
* Raw event payloads are not embedded in this struct. They live in
|
|
18
|
+
* private object storage as gzipped JSONL pages and are listed via
|
|
19
|
+
* `rawEventPages` (manifest only; bytes downloaded out-of-band so the
|
|
20
|
+
* detail response stays bounded). The archive zip carries the bytes.
|
|
21
|
+
*/
|
|
22
|
+
import { parseMcpServerRef, parseSkillRef } from "./run-config.js";
|
|
23
|
+
import { PLATFORM_PACKAGE_ECOSYSTEMS } from "./submission.js";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Submission parser
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Parse a legacy run snapshot jsonb payload into the typed flat
|
|
29
|
+
* submission. Never throws on minor unknown keys so we can
|
|
30
|
+
* forward-compat with worker-side enrichment.
|
|
31
|
+
*
|
|
32
|
+
* Returns a typed shape even for malformed snapshots — the worst case
|
|
33
|
+
* is `{kind: "submission", submission: {model: "", ...}}` with empty
|
|
34
|
+
* defaults — because the dashboard must still render *something* for a
|
|
35
|
+
* buggy historical row rather than 500ing the whole detail page.
|
|
36
|
+
*/
|
|
37
|
+
export function parseRunUnitSubmission(input) {
|
|
38
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
39
|
+
return fallbackFlat();
|
|
40
|
+
}
|
|
41
|
+
const value = input;
|
|
42
|
+
if (value.kind === "submission") {
|
|
43
|
+
return parseFlatProjection(value);
|
|
44
|
+
}
|
|
45
|
+
// Snapshot exists but does not match the flat shape — surface as an
|
|
46
|
+
// empty flat submission so consumers can still render lifecycle bits.
|
|
47
|
+
return fallbackFlat();
|
|
48
|
+
}
|
|
49
|
+
function parseFlatProjection(value) {
|
|
50
|
+
const submissionRaw = isRecord(value.submission) ? value.submission : {};
|
|
51
|
+
const outputsRaw = isRecord(submissionRaw.outputs) ? submissionRaw.outputs : {};
|
|
52
|
+
const allowedDirs = toOptionalStringArray(outputsRaw.allowedDirs);
|
|
53
|
+
const deniedDirs = toOptionalStringArray(outputsRaw.deniedDirs);
|
|
54
|
+
const submission = {
|
|
55
|
+
model: typeof submissionRaw.model === "string" ? submissionRaw.model : "",
|
|
56
|
+
...(typeof submissionRaw.system === "string" ? { system: submissionRaw.system } : {}),
|
|
57
|
+
prompt: toStringArray(submissionRaw.prompt),
|
|
58
|
+
skills: toSkillRefArray(submissionRaw.skills),
|
|
59
|
+
agentsMd: [],
|
|
60
|
+
files: [],
|
|
61
|
+
mcpServers: toMcpServerRefArray(submissionRaw.mcpServers),
|
|
62
|
+
...(parseEnvironment(submissionRaw.environment)
|
|
63
|
+
? { environment: parseEnvironment(submissionRaw.environment) }
|
|
64
|
+
: {}),
|
|
65
|
+
...(parseSecurityProfile(submissionRaw.securityProfile)
|
|
66
|
+
? { securityProfile: parseSecurityProfile(submissionRaw.securityProfile) }
|
|
67
|
+
: {}),
|
|
68
|
+
...(isJsonRecord(submissionRaw.metadata) ? { metadata: submissionRaw.metadata } : {}),
|
|
69
|
+
...(allowedDirs || deniedDirs
|
|
70
|
+
? {
|
|
71
|
+
outputs: {
|
|
72
|
+
...(allowedDirs ? { allowedDirs } : {}),
|
|
73
|
+
...(deniedDirs ? { deniedDirs } : {})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
: {})
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
kind: "submission",
|
|
80
|
+
submission
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseSecurityProfile(value) {
|
|
84
|
+
return value === "strict" || value === "standard" || value === "developer" ? value : undefined;
|
|
85
|
+
}
|
|
86
|
+
function fallbackFlat() {
|
|
87
|
+
return {
|
|
88
|
+
kind: "submission",
|
|
89
|
+
submission: {
|
|
90
|
+
model: "",
|
|
91
|
+
prompt: [],
|
|
92
|
+
skills: [],
|
|
93
|
+
agentsMd: [],
|
|
94
|
+
files: [],
|
|
95
|
+
mcpServers: []
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Coercion helpers — deliberately lenient. We never throw on a single
|
|
101
|
+
// malformed sub-field; we collapse it to a safe default and keep going
|
|
102
|
+
// so a dashboard read of a malformed snapshot still surfaces the rest.
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
function isRecord(value) {
|
|
105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
107
|
+
function isJsonRecord(value) {
|
|
108
|
+
return isRecord(value);
|
|
109
|
+
}
|
|
110
|
+
function toStringArray(value) {
|
|
111
|
+
if (!Array.isArray(value)) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return value.filter((item) => typeof item === "string");
|
|
115
|
+
}
|
|
116
|
+
function toOptionalStringArray(value) {
|
|
117
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const filtered = value.filter((item) => typeof item === "string");
|
|
121
|
+
return filtered.length === 0 ? undefined : filtered;
|
|
122
|
+
}
|
|
123
|
+
function toSkillRefArray(value) {
|
|
124
|
+
if (!Array.isArray(value)) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const out = [];
|
|
128
|
+
for (let i = 0; i < value.length; i++) {
|
|
129
|
+
try {
|
|
130
|
+
out.push(parseSkillRef(value[i], `submission.skills[${i}]`));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Skip malformed entries rather than failing the whole detail
|
|
134
|
+
// read. Worker-side enrichment may add fields we don't recognise.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function toMcpServerRefArray(value) {
|
|
140
|
+
if (!Array.isArray(value)) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const out = [];
|
|
144
|
+
for (let i = 0; i < value.length; i++) {
|
|
145
|
+
try {
|
|
146
|
+
out.push(parseMcpServerRef(value[i], `submission.mcpServers[${i}]`));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// ignore malformed
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function parseEnvironment(value) {
|
|
155
|
+
if (!isRecord(value)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const env = {};
|
|
159
|
+
if (isRecord(value.networking)) {
|
|
160
|
+
const mode = value.networking.mode;
|
|
161
|
+
const allowedHosts = value.networking.allowedHosts;
|
|
162
|
+
if (mode === "limited" || mode === "open") {
|
|
163
|
+
env.networking = {
|
|
164
|
+
mode,
|
|
165
|
+
...(Array.isArray(allowedHosts)
|
|
166
|
+
? { allowedHosts: toStringArray(allowedHosts) }
|
|
167
|
+
: {})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(value.packages)) {
|
|
172
|
+
const pkgs = value.packages
|
|
173
|
+
.filter(isRecord)
|
|
174
|
+
.map((p) => {
|
|
175
|
+
const r = p;
|
|
176
|
+
if (typeof r.name !== "string")
|
|
177
|
+
return null;
|
|
178
|
+
// Snapshots persisted after the ecosystem split carry it verbatim;
|
|
179
|
+
// older snapshots (pre-split) default to `apt` to match the strict
|
|
180
|
+
// parser's unprefixed default.
|
|
181
|
+
const ecosystem = typeof r.ecosystem === "string" &&
|
|
182
|
+
PLATFORM_PACKAGE_ECOSYSTEMS.includes(r.ecosystem)
|
|
183
|
+
? r.ecosystem
|
|
184
|
+
: "apt";
|
|
185
|
+
return {
|
|
186
|
+
name: r.name,
|
|
187
|
+
...(typeof r.version === "string" ? { version: r.version } : {}),
|
|
188
|
+
ecosystem
|
|
189
|
+
};
|
|
190
|
+
})
|
|
191
|
+
.filter((p) => p !== null);
|
|
192
|
+
if (pkgs.length > 0) {
|
|
193
|
+
env.packages = pkgs;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Lenient pass-through for envVars stored in already-validated
|
|
197
|
+
// snapshots. The strict parser in submission.ts enforces shape /
|
|
198
|
+
// size / reserved-prefix rules at submission time; here we just
|
|
199
|
+
// accept whatever shape was persisted.
|
|
200
|
+
if (isRecord(value.envVars)) {
|
|
201
|
+
const out = {};
|
|
202
|
+
for (const [k, v] of Object.entries(value.envVars)) {
|
|
203
|
+
if (typeof v === "string") {
|
|
204
|
+
out[k] = v;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (Object.keys(out).length > 0) {
|
|
208
|
+
env.envVars = out;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return env.networking || env.packages || env.envVars
|
|
212
|
+
? env
|
|
213
|
+
: undefined;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=run-unit.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified runner event schema. The managed runtime feeds one shape into
|
|
3
|
+
* the hosted aex event pipeline:
|
|
4
|
+
*
|
|
5
|
+
* - **Goose Managed** — the per-run managed runtime POSTs batches of
|
|
6
|
+
* NDJSON events to `/runs/{id}/runner/events`; the Goose adapter
|
|
7
|
+
* translates each event into one or more `RunnerEvent`s.
|
|
8
|
+
*
|
|
9
|
+
* The downstream subscribers (dashboard, SDK `streamEvents`, observable
|
|
10
|
+
* spans) never see runtime-specific wire shapes — they only see
|
|
11
|
+
* `RunnerEvent`s.
|
|
12
|
+
*
|
|
13
|
+
* This is the public event contract consumed by SDK and CLI clients.
|
|
14
|
+
*/
|
|
15
|
+
import type { JsonValue } from "./submission.js";
|
|
16
|
+
/**
|
|
17
|
+
* Schema version. Bump when the shape of `RunnerEvent`, its kind set,
|
|
18
|
+
* or the batch envelope changes. Subscribers gate on this so a future
|
|
19
|
+
* v2 wire shape can land behind a feature flag.
|
|
20
|
+
*/
|
|
21
|
+
export declare const RUNNER_EVENT_VERSION: 1;
|
|
22
|
+
/**
|
|
23
|
+
* The set of event kinds emitted into the unified stream. Adapters
|
|
24
|
+
* fold runtime-specific events into one of these — anything that
|
|
25
|
+
* doesn't fit is mapped to `notification` so the data is captured
|
|
26
|
+
* even when no UI handler exists yet.
|
|
27
|
+
*
|
|
28
|
+
* - `runtime_started` — either runtime announced "ready" (Fly
|
|
29
|
+
* machine running goose; Anthropic session
|
|
30
|
+
* accepted the first turn).
|
|
31
|
+
* - `assistant_text` — model text delta.
|
|
32
|
+
* - `tool_request` — model emitted a tool_use / function call.
|
|
33
|
+
* - `tool_response` — tool result delivered back to the model.
|
|
34
|
+
* - `skill_loaded` — a skill was loaded (Anthropic Skills API
|
|
35
|
+
* ref OR a workspace folder mount).
|
|
36
|
+
* - `file_uploaded` — a file became available to the agent
|
|
37
|
+
* (Files API id OR workspace path).
|
|
38
|
+
* - `notification` — runtime/extension notification; catch-all
|
|
39
|
+
* for diagnostic data.
|
|
40
|
+
* - `stream_error` — stream-level error (non-fatal). Subscribers
|
|
41
|
+
* may surface this as a UI warning; the run
|
|
42
|
+
* continues unless `runtime_terminal` follows.
|
|
43
|
+
* - `runtime_terminal` — the run reached a terminal state. The
|
|
44
|
+
* adapter MUST emit exactly one of these per
|
|
45
|
+
* run; subscribers gate on it for end-of-stream.
|
|
46
|
+
*/
|
|
47
|
+
export declare const RUNNER_EVENT_KINDS: readonly ["runtime_started", "assistant_text", "tool_request", "tool_response", "skill_loaded", "file_uploaded", "notification", "stream_error", "runtime_terminal"];
|
|
48
|
+
export type RunnerEventKind = (typeof RUNNER_EVENT_KINDS)[number];
|
|
49
|
+
/**
|
|
50
|
+
* One event in the unified stream. `seq` is monotonically increasing
|
|
51
|
+
* within a single run (the adapter is responsible for assigning seqs
|
|
52
|
+
* — no two events with the same `seq` for the same `runId`); `tMs` is
|
|
53
|
+
* a millisecond-resolution timestamp that is also monotonically
|
|
54
|
+
* non-decreasing within a single run (an event's `tMs` is never less
|
|
55
|
+
* than the previous event's `tMs`). `data` carries the runtime- and
|
|
56
|
+
* kind-specific payload, JSON-typed so the batch round-trips through
|
|
57
|
+
* the database column without ad-hoc serialization.
|
|
58
|
+
*/
|
|
59
|
+
export interface RunnerEvent {
|
|
60
|
+
readonly seq: number;
|
|
61
|
+
readonly tMs: number;
|
|
62
|
+
readonly kind: RunnerEventKind;
|
|
63
|
+
readonly data: Readonly<Record<string, JsonValue>>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Batch envelope. The runner ships one or more events per POST so the
|
|
67
|
+
* inbox writes them atomically. `events` MUST be sorted by `seq`
|
|
68
|
+
* ascending; api.aex.dev rejects malformed batches before touching
|
|
69
|
+
* Postgres.
|
|
70
|
+
*/
|
|
71
|
+
export interface RunnerEventBatch {
|
|
72
|
+
readonly v: typeof RUNNER_EVENT_VERSION;
|
|
73
|
+
readonly runId: string;
|
|
74
|
+
readonly events: readonly RunnerEvent[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Maximum number of events per batch. Bounds the size of the body
|
|
78
|
+
* api.aex.dev accepts and the size of the downstream Postgres / KV
|
|
79
|
+
* write. Larger streams are split into multiple batches; the runner is
|
|
80
|
+
* responsible for chunking.
|
|
81
|
+
*/
|
|
82
|
+
export declare const RUNNER_EVENT_BATCH_MAX_EVENTS: 256;
|
|
83
|
+
/**
|
|
84
|
+
* Validation outcome for an inbound batch. The `ok` branch returns
|
|
85
|
+
* the parsed batch with frozen events; the `error` branch returns the
|
|
86
|
+
* code + a short human message suitable for an HTTP 400 body. Codes
|
|
87
|
+
* are stable strings — the dashboard / SDK may branch on them.
|
|
88
|
+
*/
|
|
89
|
+
export type RunnerEventBatchValidation = {
|
|
90
|
+
readonly ok: true;
|
|
91
|
+
readonly batch: RunnerEventBatch;
|
|
92
|
+
} | {
|
|
93
|
+
readonly ok: false;
|
|
94
|
+
readonly code: RunnerEventBatchValidationCode;
|
|
95
|
+
readonly message: string;
|
|
96
|
+
};
|
|
97
|
+
export declare const RUNNER_EVENT_BATCH_VALIDATION_CODES: readonly ["invalid_envelope", "version_mismatch", "missing_run_id", "empty_batch", "batch_too_large", "invalid_event", "seq_not_monotonic", "t_ms_not_monotonic"];
|
|
98
|
+
export type RunnerEventBatchValidationCode = (typeof RUNNER_EVENT_BATCH_VALIDATION_CODES)[number];
|
|
99
|
+
/**
|
|
100
|
+
* Parse + validate an inbound runner event batch (untrusted input).
|
|
101
|
+
* Used at the api.aex.dev ingress so adapters never have to
|
|
102
|
+
* re-check the wire shape, and used by tests to assert the contract.
|
|
103
|
+
*
|
|
104
|
+
* Successful validation guarantees:
|
|
105
|
+
* - top-level envelope matches {@link RunnerEventBatch}
|
|
106
|
+
* - `v === RUNNER_EVENT_VERSION`
|
|
107
|
+
* - `runId` is a non-empty string
|
|
108
|
+
* - `events` is a non-empty array of at most
|
|
109
|
+
* {@link RUNNER_EVENT_BATCH_MAX_EVENTS} entries
|
|
110
|
+
* - each event is a {@link RunnerEvent} with a known `kind`
|
|
111
|
+
* - `seq` is strictly increasing across the batch
|
|
112
|
+
* - `tMs` is non-decreasing across the batch
|
|
113
|
+
*/
|
|
114
|
+
export declare function validateRunnerEventBatch(input: unknown): RunnerEventBatchValidation;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified runner event schema. The managed runtime feeds one shape into
|
|
3
|
+
* the hosted aex event pipeline:
|
|
4
|
+
*
|
|
5
|
+
* - **Goose Managed** — the per-run managed runtime POSTs batches of
|
|
6
|
+
* NDJSON events to `/runs/{id}/runner/events`; the Goose adapter
|
|
7
|
+
* translates each event into one or more `RunnerEvent`s.
|
|
8
|
+
*
|
|
9
|
+
* The downstream subscribers (dashboard, SDK `streamEvents`, observable
|
|
10
|
+
* spans) never see runtime-specific wire shapes — they only see
|
|
11
|
+
* `RunnerEvent`s.
|
|
12
|
+
*
|
|
13
|
+
* This is the public event contract consumed by SDK and CLI clients.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Schema version. Bump when the shape of `RunnerEvent`, its kind set,
|
|
17
|
+
* or the batch envelope changes. Subscribers gate on this so a future
|
|
18
|
+
* v2 wire shape can land behind a feature flag.
|
|
19
|
+
*/
|
|
20
|
+
export const RUNNER_EVENT_VERSION = 1;
|
|
21
|
+
/**
|
|
22
|
+
* The set of event kinds emitted into the unified stream. Adapters
|
|
23
|
+
* fold runtime-specific events into one of these — anything that
|
|
24
|
+
* doesn't fit is mapped to `notification` so the data is captured
|
|
25
|
+
* even when no UI handler exists yet.
|
|
26
|
+
*
|
|
27
|
+
* - `runtime_started` — either runtime announced "ready" (Fly
|
|
28
|
+
* machine running goose; Anthropic session
|
|
29
|
+
* accepted the first turn).
|
|
30
|
+
* - `assistant_text` — model text delta.
|
|
31
|
+
* - `tool_request` — model emitted a tool_use / function call.
|
|
32
|
+
* - `tool_response` — tool result delivered back to the model.
|
|
33
|
+
* - `skill_loaded` — a skill was loaded (Anthropic Skills API
|
|
34
|
+
* ref OR a workspace folder mount).
|
|
35
|
+
* - `file_uploaded` — a file became available to the agent
|
|
36
|
+
* (Files API id OR workspace path).
|
|
37
|
+
* - `notification` — runtime/extension notification; catch-all
|
|
38
|
+
* for diagnostic data.
|
|
39
|
+
* - `stream_error` — stream-level error (non-fatal). Subscribers
|
|
40
|
+
* may surface this as a UI warning; the run
|
|
41
|
+
* continues unless `runtime_terminal` follows.
|
|
42
|
+
* - `runtime_terminal` — the run reached a terminal state. The
|
|
43
|
+
* adapter MUST emit exactly one of these per
|
|
44
|
+
* run; subscribers gate on it for end-of-stream.
|
|
45
|
+
*/
|
|
46
|
+
export const RUNNER_EVENT_KINDS = [
|
|
47
|
+
"runtime_started",
|
|
48
|
+
"assistant_text",
|
|
49
|
+
"tool_request",
|
|
50
|
+
"tool_response",
|
|
51
|
+
"skill_loaded",
|
|
52
|
+
"file_uploaded",
|
|
53
|
+
"notification",
|
|
54
|
+
"stream_error",
|
|
55
|
+
"runtime_terminal"
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Maximum number of events per batch. Bounds the size of the body
|
|
59
|
+
* api.aex.dev accepts and the size of the downstream Postgres / KV
|
|
60
|
+
* write. Larger streams are split into multiple batches; the runner is
|
|
61
|
+
* responsible for chunking.
|
|
62
|
+
*/
|
|
63
|
+
export const RUNNER_EVENT_BATCH_MAX_EVENTS = 256;
|
|
64
|
+
export const RUNNER_EVENT_BATCH_VALIDATION_CODES = [
|
|
65
|
+
"invalid_envelope",
|
|
66
|
+
"version_mismatch",
|
|
67
|
+
"missing_run_id",
|
|
68
|
+
"empty_batch",
|
|
69
|
+
"batch_too_large",
|
|
70
|
+
"invalid_event",
|
|
71
|
+
"seq_not_monotonic",
|
|
72
|
+
"t_ms_not_monotonic"
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* Parse + validate an inbound runner event batch (untrusted input).
|
|
76
|
+
* Used at the api.aex.dev ingress so adapters never have to
|
|
77
|
+
* re-check the wire shape, and used by tests to assert the contract.
|
|
78
|
+
*
|
|
79
|
+
* Successful validation guarantees:
|
|
80
|
+
* - top-level envelope matches {@link RunnerEventBatch}
|
|
81
|
+
* - `v === RUNNER_EVENT_VERSION`
|
|
82
|
+
* - `runId` is a non-empty string
|
|
83
|
+
* - `events` is a non-empty array of at most
|
|
84
|
+
* {@link RUNNER_EVENT_BATCH_MAX_EVENTS} entries
|
|
85
|
+
* - each event is a {@link RunnerEvent} with a known `kind`
|
|
86
|
+
* - `seq` is strictly increasing across the batch
|
|
87
|
+
* - `tMs` is non-decreasing across the batch
|
|
88
|
+
*/
|
|
89
|
+
export function validateRunnerEventBatch(input) {
|
|
90
|
+
if (!isRecord(input)) {
|
|
91
|
+
return invalid("invalid_envelope", "batch must be a JSON object");
|
|
92
|
+
}
|
|
93
|
+
if (input.v !== RUNNER_EVENT_VERSION) {
|
|
94
|
+
return invalid("version_mismatch", `batch.v must equal ${RUNNER_EVENT_VERSION} (got ${JSON.stringify(input.v)})`);
|
|
95
|
+
}
|
|
96
|
+
if (typeof input.runId !== "string" || input.runId.length === 0) {
|
|
97
|
+
return invalid("missing_run_id", "batch.runId must be a non-empty string");
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(input.events) || input.events.length === 0) {
|
|
100
|
+
return invalid("empty_batch", "batch.events must be a non-empty array");
|
|
101
|
+
}
|
|
102
|
+
if (input.events.length > RUNNER_EVENT_BATCH_MAX_EVENTS) {
|
|
103
|
+
return invalid("batch_too_large", `batch.events has ${input.events.length} entries; max is ${RUNNER_EVENT_BATCH_MAX_EVENTS}`);
|
|
104
|
+
}
|
|
105
|
+
let lastSeq = Number.NEGATIVE_INFINITY;
|
|
106
|
+
let lastTMs = Number.NEGATIVE_INFINITY;
|
|
107
|
+
const events = [];
|
|
108
|
+
for (let i = 0; i < input.events.length; i++) {
|
|
109
|
+
const evt = input.events[i];
|
|
110
|
+
if (!isRecord(evt)) {
|
|
111
|
+
return invalid("invalid_event", `events[${i}] must be a JSON object`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof evt.seq !== "number" ||
|
|
114
|
+
!Number.isFinite(evt.seq) ||
|
|
115
|
+
!Number.isInteger(evt.seq) ||
|
|
116
|
+
evt.seq < 0) {
|
|
117
|
+
return invalid("invalid_event", `events[${i}].seq must be a non-negative integer`);
|
|
118
|
+
}
|
|
119
|
+
if (typeof evt.tMs !== "number" ||
|
|
120
|
+
!Number.isFinite(evt.tMs) ||
|
|
121
|
+
!Number.isInteger(evt.tMs) ||
|
|
122
|
+
evt.tMs < 0) {
|
|
123
|
+
return invalid("invalid_event", `events[${i}].tMs must be a non-negative integer`);
|
|
124
|
+
}
|
|
125
|
+
if (typeof evt.kind !== "string" ||
|
|
126
|
+
!RUNNER_EVENT_KINDS.includes(evt.kind)) {
|
|
127
|
+
return invalid("invalid_event", `events[${i}].kind must be one of: ${RUNNER_EVENT_KINDS.join(", ")} (got ${JSON.stringify(evt.kind)})`);
|
|
128
|
+
}
|
|
129
|
+
if (!isRecord(evt.data)) {
|
|
130
|
+
return invalid("invalid_event", `events[${i}].data must be a JSON object`);
|
|
131
|
+
}
|
|
132
|
+
if (!isJsonRecord(evt.data)) {
|
|
133
|
+
return invalid("invalid_event", `events[${i}].data must be JSON-serializable`);
|
|
134
|
+
}
|
|
135
|
+
if (evt.seq <= lastSeq) {
|
|
136
|
+
return invalid("seq_not_monotonic", `events[${i}].seq=${evt.seq} must be strictly greater than the previous seq=${lastSeq}`);
|
|
137
|
+
}
|
|
138
|
+
if (evt.tMs < lastTMs) {
|
|
139
|
+
return invalid("t_ms_not_monotonic", `events[${i}].tMs=${evt.tMs} must be >= the previous tMs=${lastTMs}`);
|
|
140
|
+
}
|
|
141
|
+
lastSeq = evt.seq;
|
|
142
|
+
lastTMs = evt.tMs;
|
|
143
|
+
events.push({
|
|
144
|
+
seq: evt.seq,
|
|
145
|
+
tMs: evt.tMs,
|
|
146
|
+
kind: evt.kind,
|
|
147
|
+
data: Object.freeze({ ...evt.data })
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
batch: { v: RUNNER_EVENT_VERSION, runId: input.runId, events: Object.freeze(events) }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function invalid(code, message) {
|
|
156
|
+
return { ok: false, code, message };
|
|
157
|
+
}
|
|
158
|
+
function isRecord(input) {
|
|
159
|
+
return typeof input === "object" && input !== null && !Array.isArray(input);
|
|
160
|
+
}
|
|
161
|
+
function isJsonRecord(input) {
|
|
162
|
+
for (const value of Object.values(input)) {
|
|
163
|
+
if (!isJsonValue(value))
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
function isJsonValue(input) {
|
|
169
|
+
if (input === null)
|
|
170
|
+
return true;
|
|
171
|
+
const t = typeof input;
|
|
172
|
+
if (t === "string" || t === "boolean")
|
|
173
|
+
return true;
|
|
174
|
+
if (t === "number")
|
|
175
|
+
return Number.isFinite(input);
|
|
176
|
+
if (Array.isArray(input))
|
|
177
|
+
return input.every(isJsonValue);
|
|
178
|
+
if (isRecord(input)) {
|
|
179
|
+
for (const v of Object.values(input)) {
|
|
180
|
+
if (!isJsonValue(v))
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=runner-event.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime manifest: the per-run, per-provider description of where
|
|
3
|
+
* aex places things inside the agent container, plus the merged
|
|
4
|
+
* env-var bag delivered via `RUNTIME.env` / `RUNTIME.json`.
|
|
5
|
+
*
|
|
6
|
+
* The hosted API computes a manifest at submitRun-response time
|
|
7
|
+
* (from the validated submission + the chosen provider) via
|
|
8
|
+
* {@link buildRuntimeManifest} and echoes it on the wire as
|
|
9
|
+
* `Run.runtimeManifest`, so caller code (anyone rendering catalog markdown
|
|
10
|
+
* pre-submission, or resolving aex's in-container path strings) doesn't
|
|
11
|
+
* have to guess.
|
|
12
|
+
* The managed runtime materialises the actual `RUNTIME.env` / `RUNTIME.json`
|
|
13
|
+
* files in-container from the same provider + envVars inputs, so the
|
|
14
|
+
* SDK-side view and the in-container view describe the same layout.
|
|
15
|
+
*
|
|
16
|
+
* Manifest values are derived, never persisted separately — the source
|
|
17
|
+
* of truth for the customer half remains `submission.environment.envVars`
|
|
18
|
+
* on the run row; the aex half is constant for a given
|
|
19
|
+
* provider+SDK-version pair.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Set of providers whose runtime contract aex models. Today only
|
|
23
|
+
* `"anthropic"` ships; the field is on the manifest so forward-compat
|
|
24
|
+
* consumers can branch on it without us having to silently change
|
|
25
|
+
* what `runtimeManifest` means when we add a second provider.
|
|
26
|
+
*/
|
|
27
|
+
export type RuntimeProvider = "anthropic";
|
|
28
|
+
/**
|
|
29
|
+
* The in-container paths the agent and skill code reference at
|
|
30
|
+
* runtime. All fields are absolute, all reflect Anthropic Managed
|
|
31
|
+
* Agents' session-mount rebase rule (every `mount_path` lands under
|
|
32
|
+
* `/mnt/session/uploads/` regardless of the leading slash).
|
|
33
|
+
*
|
|
34
|
+
* `skillsRoot` is the location of Skills API-registered bundles, which
|
|
35
|
+
* the runtime auto-discovers under `/workspace/skills/<name>/` —
|
|
36
|
+
* empirically a separate root from the session-resource mounts, NOT
|
|
37
|
+
* under `/mnt/session/uploads/`.
|
|
38
|
+
*/
|
|
39
|
+
export interface RuntimeManifest {
|
|
40
|
+
readonly provider: RuntimeProvider;
|
|
41
|
+
/** Where Skills-API-registered bundles auto-discover (Anthropic). */
|
|
42
|
+
readonly skillsRoot: string;
|
|
43
|
+
/** Parent dir of File mounts: `<filesRoot>/<f_id>/<rel-path>`. */
|
|
44
|
+
readonly filesRoot: string;
|
|
45
|
+
/** Parent dir of non-SKILL.md asset mounts: `<assetsRoot>/<skl_id>/<rel-path>`. */
|
|
46
|
+
readonly assetsRoot: string;
|
|
47
|
+
/** Absolute path of the in-container aex runtime bridge (invoke via `node`). */
|
|
48
|
+
readonly aexCli: string;
|
|
49
|
+
/** Absolute path of the per-run proxy-endpoints manifest. */
|
|
50
|
+
readonly indexJson: string;
|
|
51
|
+
/** Absolute path of the always-mounted aex runtime contract README. */
|
|
52
|
+
readonly readme: string;
|
|
53
|
+
/** Absolute path of the machine-readable manifest mirror. */
|
|
54
|
+
readonly runtimeJson: string;
|
|
55
|
+
/** Absolute path of the POSIX-shell-sourceable runtime env file. */
|
|
56
|
+
readonly runtimeEnv: string;
|
|
57
|
+
/**
|
|
58
|
+
* Merged env-var bag: aex-set runtime keys (with reserved
|
|
59
|
+
* `AEX_` prefix) plus customer-supplied `environment.envVars`.
|
|
60
|
+
* Both `RUNTIME.env` and `RUNTIME.json` are rendered from this
|
|
61
|
+
* exact map; `__KEY__` substitution in agent-facing markdown
|
|
62
|
+
* resolves against this exact map.
|
|
63
|
+
*/
|
|
64
|
+
readonly envVars: Readonly<Record<string, string>>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Managed-runner container paths. Kept here so the BFF, worker, and
|
|
68
|
+
* in-container bridge render identical values; runtime bootstrap constants are
|
|
69
|
+
* validated against these by a regression test
|
|
70
|
+
* (`packages/contracts/test/runtime-manifest.test.ts`).
|
|
71
|
+
*/
|
|
72
|
+
declare const ANTHROPIC_PATHS: Readonly<{
|
|
73
|
+
readonly skillsRoot: "/workspace/skills";
|
|
74
|
+
readonly filesRoot: "/mnt/session/uploads/aex/files";
|
|
75
|
+
readonly assetsRoot: "/mnt/session/uploads/aex/assets";
|
|
76
|
+
readonly aexCli: "/mnt/session/uploads/aex/aex";
|
|
77
|
+
readonly indexJson: "/mnt/session/uploads/aex/index.json";
|
|
78
|
+
readonly readme: "/mnt/session/uploads/aex/SKILLS.md";
|
|
79
|
+
readonly runtimeJson: "/mnt/session/uploads/aex/RUNTIME.json";
|
|
80
|
+
readonly runtimeEnv: "/mnt/session/uploads/aex/RUNTIME.env";
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Container paths exposed for a given provider. Today only
|
|
84
|
+
* `"anthropic"` is recognised; calling with anything else throws so
|
|
85
|
+
* forward-compat surfaces the missing provider entry instead of
|
|
86
|
+
* silently emitting Anthropic paths.
|
|
87
|
+
*/
|
|
88
|
+
export declare function runtimePathsFor(provider: RuntimeProvider): typeof ANTHROPIC_PATHS;
|
|
89
|
+
export interface BuildRuntimeManifestInput {
|
|
90
|
+
readonly provider: RuntimeProvider;
|
|
91
|
+
/**
|
|
92
|
+
* Customer-supplied `environment.envVars` from the validated
|
|
93
|
+
* submission. Keys with the reserved `AEX_` prefix are
|
|
94
|
+
* filtered out defensively — the strict submission parser already
|
|
95
|
+
* rejects them, but defence-in-depth means a malformed snapshot
|
|
96
|
+
* (or a future bypass) can't poison the manifest.
|
|
97
|
+
*/
|
|
98
|
+
readonly customerEnvVars?: Readonly<Record<string, string>> | undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Build the runtime manifest for a single submission. Pure function:
|
|
102
|
+
* same input → same output → safe to call from the BFF response path
|
|
103
|
+
* and from the worker bootstrap path with identical results.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildRuntimeManifest(input: BuildRuntimeManifestInput): RuntimeManifest;
|
|
106
|
+
export {};
|