@tai42/api-client 0.1.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/LICENSE +202 -0
- package/README.md +28 -0
- package/dist/agents.d.ts +265 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +200 -0
- package/dist/agents.js.map +1 -0
- package/dist/client.d.ts +1316 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +468 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/fleet-report.d.ts +61 -0
- package/dist/fleet-report.d.ts.map +1 -0
- package/dist/fleet-report.js +42 -0
- package/dist/fleet-report.js.map +1 -0
- package/dist/http.d.ts +69 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +152 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas.d.ts +5469 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +1103 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sse.d.ts +27 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +84 -0
- package/dist/sse.js.map +1 -0
- package/dist/tool-runs.d.ts +112 -0
- package/dist/tool-runs.d.ts.map +1 -0
- package/dist/tool-runs.js +70 -0
- package/dist/tool-runs.js.map +1 -0
- package/package.json +51 -0
- package/src/agents.ts +248 -0
- package/src/client.ts +837 -0
- package/src/errors.ts +67 -0
- package/src/fleet-report.ts +94 -0
- package/src/http.ts +198 -0
- package/src/index.ts +142 -0
- package/src/schemas.ts +1341 -0
- package/src/sse.ts +90 -0
- package/src/tool-runs.ts +103 -0
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One zod schema per consumed endpoint. These mirror the skeleton's router
|
|
3
|
+
* response shapes exactly; a drift throws (ApiSchemaError) and the UI shows the
|
|
4
|
+
* error state. JSON-schema-shaped fields are kept as permissive records — the
|
|
5
|
+
* auto-form renderer interprets them at runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
/** An arbitrary JSON value (tool results, rendered payloads). */
|
|
9
|
+
export const jsonValue = z.unknown();
|
|
10
|
+
/** A JSON Schema object (Pydantic-emitted). Permissive; parsed by the auto-form. */
|
|
11
|
+
export const jsonSchema = z.record(z.string(), z.unknown());
|
|
12
|
+
// -- tools -------------------------------------------------------------------
|
|
13
|
+
export const toolNames = z.array(z.string());
|
|
14
|
+
export const toolSchema = z.object({
|
|
15
|
+
input: jsonSchema,
|
|
16
|
+
output: jsonSchema.nullable(),
|
|
17
|
+
description: z.string().nullable(),
|
|
18
|
+
});
|
|
19
|
+
export const allToolSchemas = z.record(z.string(), toolSchema);
|
|
20
|
+
export const runToolResult = jsonValue;
|
|
21
|
+
/**
|
|
22
|
+
* The MEDIA variant a direct tool run (`/api/run-tool`) can return:
|
|
23
|
+
* fastmcp serializes a returned `Image`/`Audio` MediaBlock to the MCP content
|
|
24
|
+
* object `{ type: 'image' | 'audio', data: <base64>, mimeType: <mime> }`. The
|
|
25
|
+
* `ResultViewer` detects this shape and renders an `<img>`/`<audio>` from a
|
|
26
|
+
* `data:` URI. A tool result is still arbitrary JSON (`runToolResult`); this is
|
|
27
|
+
* one recognised shape it can take, kept as its own schema so the viewer's
|
|
28
|
+
* detection is a schema parse, not an ad-hoc shape check.
|
|
29
|
+
*
|
|
30
|
+
* SECURITY: the refinement pins `mimeType` to its media kind — an `image` block
|
|
31
|
+
* MUST carry an `image/*` type, an `audio` block an `audio/*` type. A drifting or
|
|
32
|
+
* arbitrary mimeType (e.g. a script type) fails the parse, so the viewer never
|
|
33
|
+
* turns an unexpected mime into a `data:` URI — it falls back to the JSON/text
|
|
34
|
+
* view instead. A `File` MediaBlock serializes to a distinct `EmbeddedResource`
|
|
35
|
+
* shape (no top-level `data`/`mimeType`), so it also fails here and is not
|
|
36
|
+
* media-rendered.
|
|
37
|
+
*/
|
|
38
|
+
export const toolMediaResult = z
|
|
39
|
+
.object({
|
|
40
|
+
type: z.enum(['image', 'audio']),
|
|
41
|
+
data: z.string(),
|
|
42
|
+
mimeType: z.string(),
|
|
43
|
+
})
|
|
44
|
+
.refine((media) => media.mimeType.startsWith(`${media.type}/`), {
|
|
45
|
+
message: 'media mimeType must match its media kind (image/* for image, audio/* for audio)',
|
|
46
|
+
path: ['mimeType'],
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* The ADDITIVE per-tool native-tags map (`GET /api/tools/tags`). Distinct from the
|
|
50
|
+
* flat `toolNames` contract (`GET /api/tools`, unchanged): this surfaces each
|
|
51
|
+
* tool's FastMCP `tags` — including the ones preset bodies project — so the shared
|
|
52
|
+
* `ToolPicker` can group/filter its options by tag.
|
|
53
|
+
*/
|
|
54
|
+
export const toolTagEntry = z.object({ name: z.string(), tags: z.array(z.string()) });
|
|
55
|
+
export const toolTags = z.array(toolTagEntry);
|
|
56
|
+
// -- extensions --------------------------------------------------------------
|
|
57
|
+
export const extension = z.object({ name: z.string(), kind: z.string() });
|
|
58
|
+
export const extensions = z.array(extension);
|
|
59
|
+
/**
|
|
60
|
+
* One element of an extension combo: a bare extension NAME, or a
|
|
61
|
+
* `{ name, config }` mapping binding author config (e.g. an `output_schema` JSON
|
|
62
|
+
* Schema, or an `ask_external` verifier). Both wire forms are admitted so a
|
|
63
|
+
* config-bearing combo parses without dropping server data. A drift throws
|
|
64
|
+
* `ApiSchemaError`.
|
|
65
|
+
*/
|
|
66
|
+
export const presetExtensionElement = z.union([
|
|
67
|
+
z.string(),
|
|
68
|
+
z.object({ name: z.string(), config: z.record(z.string(), z.unknown()) }),
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* `GET /api/tools/{name}/extensions` — a tool's FULL lossless list-of-combos plus
|
|
72
|
+
* the extension catalog. `combos` is a list of combos, each an ordered list of
|
|
73
|
+
* extension ELEMENTS (a bare name or a config-bearing `{ name, config }` mapping —
|
|
74
|
+
* `output_schema` carries its schema this way); `[]` when the tool has no map entry.
|
|
75
|
+
* It carries EVERY combo mapped to the base tool (the union across every `tools`/`mcp`
|
|
76
|
+
* config), never a single flattened one. `available` is the same flat catalog
|
|
77
|
+
* `GET /api/extensions` returns. A drift throws `ApiSchemaError`.
|
|
78
|
+
*/
|
|
79
|
+
export const toolExtensions = z.object({
|
|
80
|
+
combos: z.array(z.array(presetExtensionElement)),
|
|
81
|
+
available: extensions,
|
|
82
|
+
});
|
|
83
|
+
// -- presets -----------------------------------------------------------------
|
|
84
|
+
/** A list of extension COMBOS — each combo an ordered list of elements. */
|
|
85
|
+
export const presetExtensions = z.array(z.array(presetExtensionElement));
|
|
86
|
+
/**
|
|
87
|
+
* One preset as `GET /api/presets` returns it. Every preset is versioned, so
|
|
88
|
+
* `active_version` is always an int; `conflicted` marks a QUARANTINED row (its
|
|
89
|
+
* name collided with a foreign tool at boot — delete-only). `extensions` are the
|
|
90
|
+
* ACTIVE version's extension combos, `tags` the preset's categorization labels
|
|
91
|
+
* (distinct from a version's generic label list), and `output_schema` the
|
|
92
|
+
* optional author-set output JSON Schema. A drift throws `ApiSchemaError`;
|
|
93
|
+
* nothing is coerced.
|
|
94
|
+
*/
|
|
95
|
+
export const presetRecord = z.object({
|
|
96
|
+
name: z.string(),
|
|
97
|
+
base_tool: z.string(),
|
|
98
|
+
description: z.string(),
|
|
99
|
+
active_version: z.number(),
|
|
100
|
+
tags: z.array(z.string()),
|
|
101
|
+
extensions: presetExtensions,
|
|
102
|
+
output_schema: z.record(z.string(), z.unknown()).nullable(),
|
|
103
|
+
conflicted: z.boolean(),
|
|
104
|
+
// The human-readable cause a row is quarantined (its name collided with a
|
|
105
|
+
// foreign tool at boot), or `null` when the row is not conflicted. Rendered
|
|
106
|
+
// verbatim; nothing is coerced.
|
|
107
|
+
conflicted_reason: z.string().nullable(),
|
|
108
|
+
});
|
|
109
|
+
export const presetList = z.array(presetRecord);
|
|
110
|
+
/** `GET /api/presets/{name}` — the record plus the active version's baked kwargs. */
|
|
111
|
+
export const presetDetail = presetRecord.extend({
|
|
112
|
+
fixed_kwargs: z.record(z.string(), z.unknown()),
|
|
113
|
+
});
|
|
114
|
+
/** The full version body a preset stores under `kind="preset"`. */
|
|
115
|
+
export const presetBody = z.object({
|
|
116
|
+
base_tool: z.string(),
|
|
117
|
+
description: z.string(),
|
|
118
|
+
fixed_kwargs: z.record(z.string(), z.unknown()),
|
|
119
|
+
extensions: presetExtensions,
|
|
120
|
+
tags: z.array(z.string()),
|
|
121
|
+
output_schema: z.record(z.string(), z.unknown()).nullable(),
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* One immutable version row (`GET /api/presets/{name}/versions[/{version}]`).
|
|
125
|
+
* `is_current` flags the active version; `tags` here is the GENERIC per-version
|
|
126
|
+
* label list (kind-agnostic), distinct from the categorization `body.tags`.
|
|
127
|
+
*/
|
|
128
|
+
export const presetVersion = z.object({
|
|
129
|
+
version: z.number(),
|
|
130
|
+
body: presetBody,
|
|
131
|
+
tags: z.array(z.string()),
|
|
132
|
+
created_at: z.string(),
|
|
133
|
+
is_current: z.boolean(),
|
|
134
|
+
});
|
|
135
|
+
export const presetVersionList = z.array(presetVersion);
|
|
136
|
+
/** `POST /api/presets/{name}/rollback` — the re-pointed active version. */
|
|
137
|
+
export const presetRollback = z.object({ name: z.string(), active_version: z.number() });
|
|
138
|
+
/** `DELETE /api/presets/{name}` — the removed preset. */
|
|
139
|
+
export const presetDeleted = z.object({ name: z.string(), deleted: z.literal(true) });
|
|
140
|
+
/** `POST /api/presets/{name}/rename` — the re-keyed record identity. */
|
|
141
|
+
export const presetRenamed = z.object({
|
|
142
|
+
name: z.string(),
|
|
143
|
+
renamed_from: z.string(),
|
|
144
|
+
active_version: z.number(),
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* `GET /api/presets/{name}/referees` — every OTHER preset whose active authored
|
|
148
|
+
* composition names this one as a tool (the referees a rename would strand). An
|
|
149
|
+
* empty list is the clean case; a non-empty list means a rename is blocked until
|
|
150
|
+
* those presets are updated. A drift throws `ApiSchemaError`.
|
|
151
|
+
*/
|
|
152
|
+
export const presetReferees = z.object({
|
|
153
|
+
name: z.string(),
|
|
154
|
+
referees: z.array(z.string()),
|
|
155
|
+
});
|
|
156
|
+
/**
|
|
157
|
+
* `POST /api/presets/validate` — the dry-run verdict for a preset draft. `valid`
|
|
158
|
+
* is the absence of an `error`; an invalid draft is a SUCCESSFUL validation (HTTP
|
|
159
|
+
* 200), so `error` carries the server's verbatim message. A drift throws
|
|
160
|
+
* `ApiSchemaError`.
|
|
161
|
+
*/
|
|
162
|
+
export const presetValidation = z.object({
|
|
163
|
+
valid: z.boolean(),
|
|
164
|
+
error: z.string().nullable(),
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* `PUT /api/presets/{name}/versions/{version}/tags` — the re-annotated version.
|
|
168
|
+
* Version tags are labels on an immutable body, so this rebinds nothing; the
|
|
169
|
+
* response echoes the identity plus the new tag list. A drift throws
|
|
170
|
+
* `ApiSchemaError`.
|
|
171
|
+
*/
|
|
172
|
+
export const presetVersionTags = z.object({
|
|
173
|
+
name: z.string(),
|
|
174
|
+
version: z.number(),
|
|
175
|
+
tags: z.array(z.string()),
|
|
176
|
+
});
|
|
177
|
+
// -- templates ---------------------------------------------------------------
|
|
178
|
+
export const templateNames = z.array(z.string());
|
|
179
|
+
export const templateDetail = z.object({ template: z.string(), schema: jsonSchema });
|
|
180
|
+
export const templateUploaded = z.object({ path: z.string(), uploaded: z.literal(true) });
|
|
181
|
+
export const templateDeleted = z.object({ path: z.string(), deleted: z.literal(true) });
|
|
182
|
+
export const templateRendered = z.object({ rendered: z.string() });
|
|
183
|
+
export const templateCacheCleared = z.object({ cleared: z.literal(true) });
|
|
184
|
+
// -- storage -----------------------------------------------------------------
|
|
185
|
+
/** `GET /api/storage` — the storage-provider identity, or the absent sentinel
|
|
186
|
+
* (`present: false`, a 200) when no storage-provider plugin is installed. */
|
|
187
|
+
export const storageInfo = z.object({
|
|
188
|
+
present: z.boolean(),
|
|
189
|
+
provider: z.string().nullable(),
|
|
190
|
+
module: z.string().nullable(),
|
|
191
|
+
});
|
|
192
|
+
/** `GET /api/storage/resources` — the full sorted resource-id list. */
|
|
193
|
+
export const storageResourceList = z.object({ resources: z.array(z.string()) });
|
|
194
|
+
/** `GET /api/storage/resources/{id}/stat` — an object's inferred content type;
|
|
195
|
+
* `null` when the provider cannot infer one (e.g. an unknown suffix). */
|
|
196
|
+
export const storageResourceStat = z.object({
|
|
197
|
+
id: z.string(),
|
|
198
|
+
content_type: z.string().nullable(),
|
|
199
|
+
});
|
|
200
|
+
/** `POST /api/storage/resources` — the stored object's id. */
|
|
201
|
+
export const storageResourceStored = z.object({ id: z.string(), stored: z.literal(true) });
|
|
202
|
+
/** `DELETE /api/storage/resources/{id}` — the removed object's id. */
|
|
203
|
+
export const storageResourceDeleted = z.object({ id: z.string(), deleted: z.literal(true) });
|
|
204
|
+
/** `DELETE /api/storage/dirs/{path}` — the removed directory subtree. */
|
|
205
|
+
export const storageDirDeleted = z.object({ dir: z.string(), deleted: z.literal(true) });
|
|
206
|
+
// -- backend identity + fleet (worker bus) -----------------------------------
|
|
207
|
+
/** `GET /api/backend` — the execution-backend identity, or the absent sentinel
|
|
208
|
+
* (`present: false`, a 200) when no backend plugin is registered. Backend identity
|
|
209
|
+
* is kept distinct from the fleet: the fleet doors ride the app's worker bus and
|
|
210
|
+
* need no backend at all. */
|
|
211
|
+
export const backendInfo = z.object({
|
|
212
|
+
present: z.boolean(),
|
|
213
|
+
backend: z.string().nullable(),
|
|
214
|
+
module: z.string().nullable(),
|
|
215
|
+
});
|
|
216
|
+
/** The two worker kinds that join the bus: an ASGI `serve` worker or a
|
|
217
|
+
* `backend` runtime process. A drift throws `ApiSchemaError`. */
|
|
218
|
+
export const originKind = z.enum(['serve', 'backend']);
|
|
219
|
+
/**
|
|
220
|
+
* One live worker on the bus — a presence-census entry. `origin` is the
|
|
221
|
+
* `{kind}-{uuid}` identity used as a reload target; `kind` tells the two worker
|
|
222
|
+
* kinds apart; `pid` is the process id the presence key carries.
|
|
223
|
+
*/
|
|
224
|
+
export const fleetOrigin = z.object({
|
|
225
|
+
origin: z.string(),
|
|
226
|
+
kind: originKind,
|
|
227
|
+
pid: z.number(),
|
|
228
|
+
});
|
|
229
|
+
/**
|
|
230
|
+
* `GET /api/fleet/workers` — the live worker fleet from the bus presence census.
|
|
231
|
+
* The census lists ALL subscribed origins (ASGI `serve` workers and backend-runtime
|
|
232
|
+
* processes alike), not only backend workers. A census read that fails surfaces as a
|
|
233
|
+
* loud 500, never a fabricated empty fleet.
|
|
234
|
+
*/
|
|
235
|
+
export const fleetWorkers = z.object({ workers: z.array(fleetOrigin) });
|
|
236
|
+
/**
|
|
237
|
+
* One origin's outcome within a fleet broadcast. `applied`/`failed` are terminal
|
|
238
|
+
* wire replies from a subscriber; `missing`/`departed`/`timed_out` are
|
|
239
|
+
* publisher-computed from the presence census (a silent origin never sends them).
|
|
240
|
+
*/
|
|
241
|
+
export const fleetOutcome = z.enum(['applied', 'failed', 'missing', 'departed', 'timed_out']);
|
|
242
|
+
/**
|
|
243
|
+
* One origin's result within a {@link fleetResult}. `payload` carries query-op data
|
|
244
|
+
* (opaque to the UI); `error` carries a failed apply's message; `detail` carries the
|
|
245
|
+
* publisher's report text for a computed `missing`/`departed`/`timed_out`. A drift
|
|
246
|
+
* throws `ApiSchemaError`.
|
|
247
|
+
*/
|
|
248
|
+
export const fleetOriginResult = z.object({
|
|
249
|
+
origin: z.string(),
|
|
250
|
+
outcome: fleetOutcome,
|
|
251
|
+
payload: jsonValue,
|
|
252
|
+
error: z.string().nullable(),
|
|
253
|
+
detail: z.string().nullable(),
|
|
254
|
+
});
|
|
255
|
+
/**
|
|
256
|
+
* The awaited per-origin report of one fleet broadcast. Two honest shapes ride the
|
|
257
|
+
* same schema. Reachable (`reachable: true`): `results` holds one entry per expected
|
|
258
|
+
* origin (the serving worker's own entry included), so an unconfirmed sibling is a
|
|
259
|
+
* visible `failed`/`missing`/`departed`/`timed_out` entry, never a silent stale
|
|
260
|
+
* worker. Bus-unreachable (`reachable: false`): the transport failed before any
|
|
261
|
+
* origin could reply, so `results` is empty and only `error` is carried. This is the
|
|
262
|
+
* bare body of `POST /api/fleet/reload-config` and the single-MCP reload; it is also
|
|
263
|
+
* the body the mode-wrapped {@link fleetReportFanout} carries under its `fleet` /
|
|
264
|
+
* `unreachable` modes.
|
|
265
|
+
*/
|
|
266
|
+
export const fleetResult = z.object({
|
|
267
|
+
op: z.string(),
|
|
268
|
+
reachable: z.boolean(),
|
|
269
|
+
local_only: z.boolean(),
|
|
270
|
+
results: z.array(fleetOriginResult),
|
|
271
|
+
error: z.string().nullable(),
|
|
272
|
+
});
|
|
273
|
+
/**
|
|
274
|
+
* `POST /api/fleet/reload-config` — soft-restart the fleet and report per-origin. The
|
|
275
|
+
* serving worker applies its own reload first, then broadcasts; the returned report
|
|
276
|
+
* names every origin's outcome. A failed local apply does not abort the broadcast, so
|
|
277
|
+
* the report is embedded even on the convergence-failure path.
|
|
278
|
+
*/
|
|
279
|
+
export const fleetReloadResult = fleetResult;
|
|
280
|
+
/**
|
|
281
|
+
* The mode-wrapped fan-out summary a mutation embeds under its `fanout` field. Three
|
|
282
|
+
* honest modes, discriminated on `mode`:
|
|
283
|
+
* - `local-only` — the broadcast reached no sibling (a lone worker / no bus), a
|
|
284
|
+
* benign single-process note.
|
|
285
|
+
* - `fleet` — a reachable multi-worker broadcast; carries the full {@link
|
|
286
|
+
* fleetResult} so an unconfirmed sibling is visible.
|
|
287
|
+
* - `unreachable` — the bus itself could not be reached; carries the
|
|
288
|
+
* bus-unreachable {@link fleetResult} (no origin list, only `error`).
|
|
289
|
+
*
|
|
290
|
+
* Every mutation whose response embeds this parses it EXPLICITLY: zod's default
|
|
291
|
+
* unknown-key stripping would otherwise silently drop the field, hiding a failed
|
|
292
|
+
* propagation from the shared fleet-report handler. A drift throws `ApiSchemaError`.
|
|
293
|
+
*/
|
|
294
|
+
export const fleetReportFanout = z.discriminatedUnion('mode', [
|
|
295
|
+
z.object({ mode: z.literal('local-only'), note: z.string() }),
|
|
296
|
+
fleetResult.extend({ mode: z.literal('fleet') }),
|
|
297
|
+
fleetResult.extend({ mode: z.literal('unreachable') }),
|
|
298
|
+
]);
|
|
299
|
+
// -- manifest / mcp ----------------------------------------------------------
|
|
300
|
+
export const manifestView = z.object({
|
|
301
|
+
mcp: z.array(z.record(z.string(), z.unknown())),
|
|
302
|
+
user_tools: z.array(z.string()),
|
|
303
|
+
});
|
|
304
|
+
/**
|
|
305
|
+
* The apply result every config-writer mutation returns (`POST /api/mcp-config`,
|
|
306
|
+
* `POST /api/config/env`): the serving worker's local reload result (`status` +
|
|
307
|
+
* `env_keys`) plus the mode-wrapped fleet fan-out of the reload it broadcast. The
|
|
308
|
+
* `fanout` is parsed EXPLICITLY so the shared fleet-report handler can surface a
|
|
309
|
+
* failed propagation — zod's unknown-key stripping would otherwise hide it. A drift
|
|
310
|
+
* throws `ApiSchemaError`.
|
|
311
|
+
*/
|
|
312
|
+
export const reloadConfigResult = z.object({
|
|
313
|
+
status: z.string(),
|
|
314
|
+
env_keys: z.number(),
|
|
315
|
+
fanout: fleetReportFanout,
|
|
316
|
+
});
|
|
317
|
+
// -- system kinds ------------------------------------------------------------
|
|
318
|
+
// `GET /api/system/kinds` — one row per pluggable kind (identity, accounts,
|
|
319
|
+
// monitoring, storage, backend, channels, webhook_verifiers, config,
|
|
320
|
+
// studio_plugins). `state` is the kind's active/default/off status: `active` = a
|
|
321
|
+
// real plugin serves it, `default` = a built-in fallback serves it, `off` =
|
|
322
|
+
// nothing is registered (a legal state, reported not "fixed"). `plugin` is the
|
|
323
|
+
// provider/module name when known, else null. Plain (non-strict) `z.object` so a
|
|
324
|
+
// server that adds a field to the row still parses.
|
|
325
|
+
export const kindStatus = z.object({
|
|
326
|
+
kind: z.string(),
|
|
327
|
+
state: z.enum(['active', 'default', 'off']),
|
|
328
|
+
plugin: z.string().nullable(),
|
|
329
|
+
detail: z.string(),
|
|
330
|
+
});
|
|
331
|
+
export const kindStatusList = z.array(kindStatus);
|
|
332
|
+
/**
|
|
333
|
+
* `POST /api/tools/{name}/extensions` — the apply result. The write persists the
|
|
334
|
+
* manifest, reloads locally, then broadcasts the reload to the whole fleet. The
|
|
335
|
+
* response is the one apply-result shape every config-writer returns: the local
|
|
336
|
+
* reload result (`status` + `env_keys`) plus the mode-wrapped fleet `fanout`. The
|
|
337
|
+
* fan-out rides EVERY deployment (a lone worker reports `local-only`), so this flat
|
|
338
|
+
* shape parses every save and the shared fleet-report handler renders any failed
|
|
339
|
+
* propagation from `fanout`.
|
|
340
|
+
*/
|
|
341
|
+
export const toolExtensionsApplyResult = reloadConfigResult;
|
|
342
|
+
export const mcpStatus = z.object({
|
|
343
|
+
bound: z.record(z.string(), z.array(z.string())),
|
|
344
|
+
failed: z.array(z.object({ title: z.string(), status: z.string() })),
|
|
345
|
+
});
|
|
346
|
+
/**
|
|
347
|
+
* `POST /api/mcp-status/{title}/reload` — re-probe a single MCP server and broadcast
|
|
348
|
+
* the reload to the fleet. The response is the bare per-origin {@link fleetResult}
|
|
349
|
+
* (the serving worker's re-probe rides its own `results` entry as the payload); there
|
|
350
|
+
* is no local/workers split. A drift throws `ApiSchemaError`.
|
|
351
|
+
*/
|
|
352
|
+
export const mcpReloadResult = fleetResult;
|
|
353
|
+
// -- sub-mcp -----------------------------------------------------------------
|
|
354
|
+
export const subMcpList = z.record(z.string(), z.record(z.string(), z.unknown()));
|
|
355
|
+
export const subMcpCreated = z.object({ slug: z.string(), tools: z.array(z.string()) });
|
|
356
|
+
export const subMcpRemoved = z.object({ slug: z.string(), removed: z.literal(true) });
|
|
357
|
+
// -- config ------------------------------------------------------------------
|
|
358
|
+
export const envConfig = z.object({
|
|
359
|
+
env: z.record(z.string(), z.string()),
|
|
360
|
+
secret_keys: z.array(z.string()),
|
|
361
|
+
});
|
|
362
|
+
/**
|
|
363
|
+
* `GET /api/config/mode` — the active config backend mode (`file` / `k8s`). The
|
|
364
|
+
* skeleton emits ONLY `config_mode`; `read_only` is a UI-side gate that no config
|
|
365
|
+
* backend currently drives (both the `file` and `k8s` providers implement writes),
|
|
366
|
+
* so it is tolerated-absent and defaults to `false` (writable). A deployment whose
|
|
367
|
+
* config is genuinely read-only can still opt in by emitting `read_only: true`,
|
|
368
|
+
* which the settings surface honors — but its absence is the normal case, not a
|
|
369
|
+
* drift, so requiring it here would make every real `getConfigMode()` throw.
|
|
370
|
+
*/
|
|
371
|
+
export const configMode = z.object({
|
|
372
|
+
config_mode: z.string(),
|
|
373
|
+
read_only: z.boolean().default(false),
|
|
374
|
+
});
|
|
375
|
+
// -- connectors --------------------------------------------------------------
|
|
376
|
+
export const subServiceView = z.object({
|
|
377
|
+
id: z.string(),
|
|
378
|
+
display_name: z.string(),
|
|
379
|
+
description: z.string(),
|
|
380
|
+
scopes: z.array(z.string()),
|
|
381
|
+
});
|
|
382
|
+
export const configFieldView = z.object({
|
|
383
|
+
key: z.string(),
|
|
384
|
+
label: z.string(),
|
|
385
|
+
target: z.enum(['env', 'header']),
|
|
386
|
+
required: z.boolean(),
|
|
387
|
+
secret: z.boolean(),
|
|
388
|
+
});
|
|
389
|
+
export const providerView = z.object({
|
|
390
|
+
id: z.string(),
|
|
391
|
+
display_name: z.string(),
|
|
392
|
+
description: z.string(),
|
|
393
|
+
icon_url: z.string(),
|
|
394
|
+
kind: z.enum(['oauth', 'none']),
|
|
395
|
+
origin: z.enum(['system', 'community']),
|
|
396
|
+
category: z.string(),
|
|
397
|
+
sub_services: z.array(subServiceView),
|
|
398
|
+
config_fields: z.array(configFieldView),
|
|
399
|
+
});
|
|
400
|
+
export const providers = z.array(providerView);
|
|
401
|
+
export const connectionView = z.object({
|
|
402
|
+
connection_id: z.string(),
|
|
403
|
+
provider_id: z.string(),
|
|
404
|
+
alias: z.string(),
|
|
405
|
+
kind: z.enum(['oauth', 'none']),
|
|
406
|
+
account_identity: z.string().nullable(),
|
|
407
|
+
enabled_sub_services: z.array(z.string()),
|
|
408
|
+
granted_scopes: z.array(z.string()),
|
|
409
|
+
auth_health_state: z.enum(['healthy', 'reconnect_required', 'refresh_failing']),
|
|
410
|
+
created_at: z.string(),
|
|
411
|
+
});
|
|
412
|
+
export const connectionsList = z.object({ items: z.array(connectionView), total: z.number() });
|
|
413
|
+
// A connector mutation that writes the manifest embeds the mode-wrapped fleet
|
|
414
|
+
// `fanout` of the reload it broadcast; it is `null` when the response path mutated no
|
|
415
|
+
// manifest (an OAuth START opens a flow before any write). Parsed EXPLICITLY so the
|
|
416
|
+
// shared fleet-report handler surfaces a failed propagation on every connector flow —
|
|
417
|
+
// zod's unknown-key stripping would otherwise drop it silently.
|
|
418
|
+
export const startConnectResult = z.union([
|
|
419
|
+
z.object({ flow_id: z.string(), authorize_url: z.string() }),
|
|
420
|
+
z.object({
|
|
421
|
+
connection_id: z.string(),
|
|
422
|
+
added_manifest_entries: z.array(z.string()),
|
|
423
|
+
fanout: fleetReportFanout.nullable(),
|
|
424
|
+
}),
|
|
425
|
+
]);
|
|
426
|
+
// A reconnect START opens an OAuth flow and writes no manifest, so it carries no
|
|
427
|
+
// fan-out (the manifest write lands when the flow completes).
|
|
428
|
+
export const reconnectResult = z.object({ flow_id: z.string(), authorize_url: z.string() });
|
|
429
|
+
export const disconnectResult = z.object({
|
|
430
|
+
connection_id: z.string(),
|
|
431
|
+
upstream_revoke_outcome: z.enum(['success', 'failed', 'skipped']),
|
|
432
|
+
upstream_revoke_status: z.number().nullable(),
|
|
433
|
+
removed_manifest_entries: z.array(z.string()),
|
|
434
|
+
fanout: fleetReportFanout.nullable(),
|
|
435
|
+
});
|
|
436
|
+
export const patchSubServicesResult = z.object({
|
|
437
|
+
connection_id: z.string(),
|
|
438
|
+
enabled_sub_services: z.array(z.string()),
|
|
439
|
+
consent_required: z.boolean(),
|
|
440
|
+
flow_id: z.string().nullable(),
|
|
441
|
+
authorize_url: z.string().nullable(),
|
|
442
|
+
added_manifest_entries: z.array(z.string()),
|
|
443
|
+
removed_manifest_entries: z.array(z.string()),
|
|
444
|
+
// `null` on a consent-only toggle that forks an OAuth flow without a manifest write.
|
|
445
|
+
fanout: fleetReportFanout.nullable(),
|
|
446
|
+
});
|
|
447
|
+
export const oauthCompleteResult = z.discriminatedUnion('kind', [
|
|
448
|
+
z.object({
|
|
449
|
+
kind: z.literal('success'),
|
|
450
|
+
connection_id: z.string(),
|
|
451
|
+
return_url: z.string(),
|
|
452
|
+
// `null` when completing the flow mutated no manifest (a reconcile that changed
|
|
453
|
+
// nothing).
|
|
454
|
+
fanout: fleetReportFanout.nullable(),
|
|
455
|
+
}),
|
|
456
|
+
z.object({ kind: z.literal('failed'), reason: z.string() }),
|
|
457
|
+
z.object({ kind: z.literal('cancelled'), message: z.string() }),
|
|
458
|
+
]);
|
|
459
|
+
// -- studio plugin registry --------------------------------------------------
|
|
460
|
+
export const studioPluginManifest = z.object({
|
|
461
|
+
name: z.string(),
|
|
462
|
+
version: z.string(),
|
|
463
|
+
api_version: z.number(),
|
|
464
|
+
entry: z.string(),
|
|
465
|
+
integrity: z.record(z.string(), z.string()),
|
|
466
|
+
contributions: z.object({
|
|
467
|
+
tool_panels: z.record(z.string(), z.string()).default({}),
|
|
468
|
+
pages: z.array(z.string()).default([]),
|
|
469
|
+
settings_tabs: z.array(z.string()).default([]),
|
|
470
|
+
// Optional with a default so a manifest without the field still parses; the
|
|
471
|
+
// non-strict object would otherwise SILENTLY STRIP an undeclared field, so a
|
|
472
|
+
// server that serves `nav_entries` would never reach the UI.
|
|
473
|
+
nav_entries: z.array(z.string()).default([]),
|
|
474
|
+
}),
|
|
475
|
+
});
|
|
476
|
+
export const studioPluginRegistry = z.array(studioPluginManifest);
|
|
477
|
+
// -- interactions ------------------------------------------------------------
|
|
478
|
+
// Shapes match the skeleton's interactions SSE contract: the SSE
|
|
479
|
+
// `interaction.add` frame carries the question and its answer format; the answer
|
|
480
|
+
// door returns { interaction_id, status }. `answered` is not a wire field — it
|
|
481
|
+
// is a client-side flag the stream hook flips on an `interaction.answered` event.
|
|
482
|
+
export const answerFormat = z.enum(['text', 'confirm', 'select', 'form', 'external']);
|
|
483
|
+
/** The kind of a question's display-only media item: a remote/inline image, or a link. */
|
|
484
|
+
export const mediaKind = z.enum(['image', 'link']);
|
|
485
|
+
/**
|
|
486
|
+
* One item of a question's display-only media. Applied PER ITEM by the renderer
|
|
487
|
+
* (never as a strict array on the frame): the frame's `media` field is a loose
|
|
488
|
+
* `z.array(z.unknown())`, and the renderer `safeParse`s each item with this schema
|
|
489
|
+
* so one malformed/hostile item is a loud per-item notice, not a whole-frame parse
|
|
490
|
+
* failure that would silently vanish an answerable question.
|
|
491
|
+
*
|
|
492
|
+
* `caption` is `.nullish()` (absent OR `null` both parse to a caption-less item):
|
|
493
|
+
* a caption-less item may arrive with the key omitted OR with an explicit `null`, so
|
|
494
|
+
* `.optional()` here would false-fail the explicit `null` and render a good image as
|
|
495
|
+
* malformed. `url` and `kind` are the only load-bearing fields; the image
|
|
496
|
+
* src scheme gate and the link neutralization are enforced by the renderer.
|
|
497
|
+
*/
|
|
498
|
+
export const interactionMediaItem = z.object({
|
|
499
|
+
kind: mediaKind,
|
|
500
|
+
url: z.string(),
|
|
501
|
+
caption: z.string().nullish(),
|
|
502
|
+
});
|
|
503
|
+
export const interaction = z.object({
|
|
504
|
+
interaction_id: z.string(),
|
|
505
|
+
group_id: z.string(),
|
|
506
|
+
question: z.string().default(''),
|
|
507
|
+
answer_format: answerFormat,
|
|
508
|
+
// Format-specific payload: select options, form JSON schema, external url. The
|
|
509
|
+
// wire sends `null` for formats that carry none (text/confirm); it is
|
|
510
|
+
// normalized to `{}` so consumers always see an object.
|
|
511
|
+
format_payload: z
|
|
512
|
+
.record(z.string(), z.unknown())
|
|
513
|
+
.nullish()
|
|
514
|
+
.transform((value) => value ?? {}),
|
|
515
|
+
created_at: z.string(),
|
|
516
|
+
timeout_at: z.string(),
|
|
517
|
+
// Whether the answer body is sensitive and never persisted server-side. Rides
|
|
518
|
+
// every add frame; an absent value (older frame) normalizes to false.
|
|
519
|
+
sensitive: z.boolean().default(false),
|
|
520
|
+
// True only for an external question answered by a signed server-to-server
|
|
521
|
+
// callback (never by a human); the frame never carries the verifier object.
|
|
522
|
+
// Absent/undefined for every other question.
|
|
523
|
+
server_verified: z.boolean().optional(),
|
|
524
|
+
// Name of the channel plugin the question was also delivered through (e.g.
|
|
525
|
+
// "telegram"); absent when the question lives only in this inbox.
|
|
526
|
+
channel: z.string().optional(),
|
|
527
|
+
// Display-only media shown WITH the question (images and/or links). Absent when
|
|
528
|
+
// the question carries none. Deliberately LOOSE (`z.array(z.unknown())`): each
|
|
529
|
+
// item is `safeParse`d per item by the renderer against `interactionMediaItem`,
|
|
530
|
+
// so one malformed/hostile item can never fail the whole frame parse and hide an
|
|
531
|
+
// answerable question. A present non-array value is still malformed.
|
|
532
|
+
media: z.array(z.unknown()).optional(),
|
|
533
|
+
});
|
|
534
|
+
/** The answer door's response — the blocked caller was woken. */
|
|
535
|
+
export const interactionAnswered = z.object({
|
|
536
|
+
interaction_id: z.string(),
|
|
537
|
+
status: z.string(),
|
|
538
|
+
});
|
|
539
|
+
// -- channels ----------------------------------------------------------------
|
|
540
|
+
// The installed channel-plugin catalog: names registered via the manifest's
|
|
541
|
+
// channel modules. A deployment with none bound answers questions only in this
|
|
542
|
+
// inbox (the Studio default).
|
|
543
|
+
export const channels = z.object({
|
|
544
|
+
channels: z.array(z.string()),
|
|
545
|
+
});
|
|
546
|
+
// -- notifications -----------------------------------------------------------
|
|
547
|
+
// The internal notifications sink: the messages the `notify_user` operation records
|
|
548
|
+
// with no channel, so they land only in the Studio inbox. Each record carries the
|
|
549
|
+
// server-minted id + timestamp; `recipient` is null when the message names none.
|
|
550
|
+
export const notification = z.object({
|
|
551
|
+
id: z.string(),
|
|
552
|
+
message: z.string(),
|
|
553
|
+
recipient: z.string().nullable(),
|
|
554
|
+
created_at: z.string(),
|
|
555
|
+
});
|
|
556
|
+
export const notifications = z.object({
|
|
557
|
+
notifications: z.array(notification),
|
|
558
|
+
});
|
|
559
|
+
// -- hooks -------------------------------------------------------------------
|
|
560
|
+
// Shapes match tai_contract.hooks.HookParams: a hook fires a `tool` on a `topic`,
|
|
561
|
+
// optionally gated by a `condition` and shaped by an `expr` (each either an inline
|
|
562
|
+
// spec or a registered id, with its own kwargs).
|
|
563
|
+
export const hookParams = z.object({
|
|
564
|
+
name: z.string(),
|
|
565
|
+
topic: z.string(),
|
|
566
|
+
tool: z.string(),
|
|
567
|
+
tool_kwargs: z.record(z.string(), z.unknown()).default({}),
|
|
568
|
+
condition: z.string().nullable().default(null),
|
|
569
|
+
condition_id: z.string().nullable().default(null),
|
|
570
|
+
// The `*_kwargs` pair is non-optional in the contract (defaults to {}, never
|
|
571
|
+
// null) — unlike the sibling condition/expr string fields, which are nullable.
|
|
572
|
+
condition_kwargs: z.record(z.string(), z.unknown()).default({}),
|
|
573
|
+
expr: z.string().nullable().default(null),
|
|
574
|
+
expr_id: z.string().nullable().default(null),
|
|
575
|
+
expr_kwargs: z.record(z.string(), z.unknown()).default({}),
|
|
576
|
+
});
|
|
577
|
+
export const hookList = z.object({
|
|
578
|
+
items: z.array(hookParams),
|
|
579
|
+
total: z.number(),
|
|
580
|
+
// Per-topic verifier bindings: the topic name maps to the verifier bound to it
|
|
581
|
+
// and that verifier's config (which carries a `secret_env` name, never a secret
|
|
582
|
+
// value). Tolerant of an older/empty response, which omits the key entirely.
|
|
583
|
+
topic_verifiers: z
|
|
584
|
+
.record(z.string(), z.object({ verifier: z.string(), config: z.record(z.string(), z.unknown()) }))
|
|
585
|
+
.default({}),
|
|
586
|
+
});
|
|
587
|
+
export const hookRegistered = z.object({
|
|
588
|
+
registered: z.boolean(),
|
|
589
|
+
name: z.string(),
|
|
590
|
+
});
|
|
591
|
+
export const hookRemoved = z.object({
|
|
592
|
+
removed: z.boolean(),
|
|
593
|
+
name: z.string(),
|
|
594
|
+
});
|
|
595
|
+
// The registered webhook-verifier catalog (`GET /api/hooks/verifiers`) — the
|
|
596
|
+
// sorted verifier NAMES only, never any verifier's config. An empty registry is a
|
|
597
|
+
// valid empty array. This is the ONLY source the topic-verifier bind picker reads.
|
|
598
|
+
export const hookVerifiers = z.array(z.string());
|
|
599
|
+
/** `PUT /api/hooks/topics/{topic}/verifier` — the bound topic + verifier name. */
|
|
600
|
+
export const topicVerifierSet = z.object({ topic: z.string(), verifier: z.string() });
|
|
601
|
+
/** `DELETE /api/hooks/topics/{topic}/verifier` — the unbound topic. */
|
|
602
|
+
export const topicVerifierRemoved = z.object({ removed: z.boolean(), topic: z.string() });
|
|
603
|
+
// -- settings schema ---------------------------------------------------------
|
|
604
|
+
// The settings form is auto-rendered from a declarative group/field description
|
|
605
|
+
// the skeleton emits from its typed settings models.
|
|
606
|
+
export const settingsSchema = z.object({
|
|
607
|
+
groups: z.array(z.object({
|
|
608
|
+
name: z.string(),
|
|
609
|
+
module: z.string(),
|
|
610
|
+
qualname: z.string(),
|
|
611
|
+
fields: z.array(z.object({
|
|
612
|
+
name: z.string(),
|
|
613
|
+
env_var: z.string(),
|
|
614
|
+
type: z.string(),
|
|
615
|
+
default: jsonValue.nullable(),
|
|
616
|
+
required: z.boolean(),
|
|
617
|
+
secret: z.boolean(),
|
|
618
|
+
description: z.string().nullable(),
|
|
619
|
+
nested_group: z.string().nullable(),
|
|
620
|
+
value: jsonValue.nullable(),
|
|
621
|
+
})),
|
|
622
|
+
})),
|
|
623
|
+
});
|
|
624
|
+
// -- auth: scopes + api keys -------------------------------------------------
|
|
625
|
+
/** `{ url: scope_id }` — every URL currently mapped to a scope. */
|
|
626
|
+
export const authScopes = z.record(z.string(), z.string());
|
|
627
|
+
export const addUrlToScopeResult = z.object({ scope_id: z.string(), url: z.string() });
|
|
628
|
+
export const removeUrlFromScopeResult = z.object({ url: z.string() });
|
|
629
|
+
export const removeScopeResult = z.object({ scope_id: z.string(), deleted_keys: z.number() });
|
|
630
|
+
/**
|
|
631
|
+
* One row of the route catalog (`GET /api/auth/routes`) — a plain HTTP route with
|
|
632
|
+
* its current mapping. `mapped` is the scope id, the public marker, or `null` when
|
|
633
|
+
* the path is unmapped (the mapper's "unassigned" bucket source). `methods` is the
|
|
634
|
+
* route's method set with `HEAD` removed.
|
|
635
|
+
*/
|
|
636
|
+
export const authRoute = z.object({
|
|
637
|
+
path: z.string(),
|
|
638
|
+
methods: z.array(z.string()),
|
|
639
|
+
mapped: z.string().nullable(),
|
|
640
|
+
});
|
|
641
|
+
export const authRoutes = z.array(authRoute);
|
|
642
|
+
/** `GET /api/auth/public-routes` — the sorted urls pinned to the public marker. */
|
|
643
|
+
export const publicRoutes = z.array(z.string());
|
|
644
|
+
/** `POST /api/auth/public-routes` — the pinned url. */
|
|
645
|
+
export const pinPublicResult = z.object({ url: z.string() });
|
|
646
|
+
/** `DELETE /api/auth/public-routes` — the unpinned url (404 when not pinned). */
|
|
647
|
+
export const unpinPublicResult = z.object({ url: z.string() });
|
|
648
|
+
export const tokensPayload = z.array(z.object({
|
|
649
|
+
user_id: z.string(),
|
|
650
|
+
description: z.string(),
|
|
651
|
+
scopes: z.array(z.string()),
|
|
652
|
+
policy_data: jsonValue,
|
|
653
|
+
condition: z.string().nullable().optional(),
|
|
654
|
+
condition_id: z.string().nullable().optional(),
|
|
655
|
+
condition_kwargs: jsonValue.nullable().optional(),
|
|
656
|
+
}));
|
|
657
|
+
/** The raw `sk-…` key string, returned once at creation time and never again. */
|
|
658
|
+
export const createdApiKey = z.string();
|
|
659
|
+
export const editApiKeyResult = z.object({ user_id: z.string(), updated: z.boolean() });
|
|
660
|
+
export const revokeApiKeyResult = z.object({ user_id: z.string(), revoked: z.boolean() });
|
|
661
|
+
// -- login methods (the public sign-in aggregator) ---------------------------
|
|
662
|
+
/**
|
|
663
|
+
* One field of a `form` login method. `secret` fields render as password
|
|
664
|
+
* inputs; `autocomplete` passes through to the input (e.g. "email",
|
|
665
|
+
* "current-password", "new-password").
|
|
666
|
+
*/
|
|
667
|
+
export const loginFormField = z.object({
|
|
668
|
+
name: z.string().min(1),
|
|
669
|
+
label: z.string().min(1),
|
|
670
|
+
secret: z.boolean(),
|
|
671
|
+
autocomplete: z.string().optional(),
|
|
672
|
+
});
|
|
673
|
+
/**
|
|
674
|
+
* The two — and only two — login-method shapes. Discriminated on `shape`; an
|
|
675
|
+
* unknown shape fails validation loudly (contract drift, not a soft skip).
|
|
676
|
+
* Optional fields are `.optional()` (absent), never `.nullable()`: the server
|
|
677
|
+
* serializes with `exclude_none`, so an absent field is OMITTED, never `null`.
|
|
678
|
+
*/
|
|
679
|
+
export const loginMethod = z.discriminatedUnion('shape', [
|
|
680
|
+
z.object({
|
|
681
|
+
shape: z.literal('form'),
|
|
682
|
+
id: z.string().min(1),
|
|
683
|
+
title: z.string().min(1),
|
|
684
|
+
purpose: z.enum(['login', 'bootstrap', 'invite']).default('login'),
|
|
685
|
+
fields: z.array(loginFormField).min(1),
|
|
686
|
+
submit_path: z.string().min(1),
|
|
687
|
+
}),
|
|
688
|
+
z.object({
|
|
689
|
+
shape: z.literal('button'),
|
|
690
|
+
id: z.string().min(1),
|
|
691
|
+
label: z.string().min(1),
|
|
692
|
+
icon: z.string().optional(),
|
|
693
|
+
href: z.string().min(1),
|
|
694
|
+
}),
|
|
695
|
+
]);
|
|
696
|
+
/**
|
|
697
|
+
* `GET /api/login/methods` (PUBLIC). `bootstrap: true` ⇒ the deployment has zero
|
|
698
|
+
* accounts and `methods` contains the owner-creation form. Non-strict — the
|
|
699
|
+
* skeleton may grow additive fields.
|
|
700
|
+
*/
|
|
701
|
+
export const loginMethods = z.object({
|
|
702
|
+
methods: z.array(loginMethod),
|
|
703
|
+
bootstrap: z.boolean(),
|
|
704
|
+
});
|
|
705
|
+
/**
|
|
706
|
+
* A successful login/exchange: the minted session token (opaque, `tai-sess-`
|
|
707
|
+
* prefixed — the client treats it as an opaque string) + its user.
|
|
708
|
+
*/
|
|
709
|
+
export const loginResult = z.object({
|
|
710
|
+
token: z.string().min(1),
|
|
711
|
+
user_id: z.string().min(1),
|
|
712
|
+
});
|
|
713
|
+
// -- auth: the caller's capability projection (GET /api/auth/me) --------------
|
|
714
|
+
/** A concrete route the caller can reach, with the methods that pass its jq fence. */
|
|
715
|
+
export const routeEntry = z.object({
|
|
716
|
+
path: z.string(),
|
|
717
|
+
methods: z.array(z.string()),
|
|
718
|
+
});
|
|
719
|
+
/**
|
|
720
|
+
* A dynamic route pattern the caller can reach — a mount/pattern surface not
|
|
721
|
+
* enumerable into concrete paths, projected only when its scope AND jq admit it.
|
|
722
|
+
*/
|
|
723
|
+
export const patternEntry = z.object({
|
|
724
|
+
pattern: z.string(),
|
|
725
|
+
scope_id: z.string(),
|
|
726
|
+
});
|
|
727
|
+
/** A sub-MCP mount the caller can reach, with the tools it exposes. */
|
|
728
|
+
export const subMcpEntry = z.object({
|
|
729
|
+
slug: z.string(),
|
|
730
|
+
tools: z.array(z.string()),
|
|
731
|
+
transport: z.string(),
|
|
732
|
+
});
|
|
733
|
+
/**
|
|
734
|
+
* `GET /api/auth/me` — the caller's derived capability projection: the concrete
|
|
735
|
+
* (path, method) surface, dynamic patterns, sub-MCP mounts, tools, and agents it
|
|
736
|
+
* can reach RIGHT NOW (derived server-side, never stored). `admin` is the
|
|
737
|
+
* condition-free ownerless `"*"` discriminator (a TOTAL projection); a scoped
|
|
738
|
+
* session carries `admin: false` and a jq-exact `routes` list. `owner_user_id` is
|
|
739
|
+
* the key's owner claim, `null` for an ownerless key. The invariant is
|
|
740
|
+
* projection ⊆ gate: every projected surface is one the server would admit, so
|
|
741
|
+
* the UI can filter on it without ever advertising a door the gate denies.
|
|
742
|
+
*/
|
|
743
|
+
export const meProjection = z.object({
|
|
744
|
+
user_id: z.string(),
|
|
745
|
+
owner_user_id: z.string().nullable(),
|
|
746
|
+
admin: z.boolean(),
|
|
747
|
+
scopes: z.array(z.string()),
|
|
748
|
+
routes: z.array(routeEntry),
|
|
749
|
+
route_patterns: z.array(patternEntry),
|
|
750
|
+
sub_mcp: z.array(subMcpEntry),
|
|
751
|
+
tools: z.array(z.string()),
|
|
752
|
+
agents: z.array(z.string()),
|
|
753
|
+
mintable: z.boolean(),
|
|
754
|
+
});
|
|
755
|
+
/**
|
|
756
|
+
* `POST /api/auth/claim-links` — a one-time claim link minted in the key-create
|
|
757
|
+
* flow. `claim_path` is a fragment-carrier path (`/login#claim=<token>`, never an
|
|
758
|
+
* absolute URL — the Studio composes the origin); `token` is the raw claim token
|
|
759
|
+
* returned exactly ONCE; `expires_at` is an ISO-8601 instant.
|
|
760
|
+
*/
|
|
761
|
+
export const claimLinkCreated = z.object({
|
|
762
|
+
claim_path: z.string(),
|
|
763
|
+
token: z.string(),
|
|
764
|
+
expires_at: z.string(),
|
|
765
|
+
});
|
|
766
|
+
/**
|
|
767
|
+
* `POST /api/auth/logout` — the single authed logout dispatcher's result.
|
|
768
|
+
* `revoked` is `true` when a live accounts session was revoked; a plain `sk-` key
|
|
769
|
+
* has nothing to revoke and answers `404` (a loud absent-session signal), never a
|
|
770
|
+
* `revoked: false` body. Non-strict, so a SUCCESSFUL logout never throws
|
|
771
|
+
* `ApiSchemaError` on an additive field.
|
|
772
|
+
*/
|
|
773
|
+
export const logoutResult = z.object({ revoked: z.boolean() });
|
|
774
|
+
/**
|
|
775
|
+
* `GET /api/auth/capabilities` — whether this deployment can MINT API keys
|
|
776
|
+
* locally. `mintable: false` means every configured identity provider is
|
|
777
|
+
* validator-only (keys are issued at an external issuer), so the key-creation
|
|
778
|
+
* UI disables up front instead of surfacing a raw mint failure. Non-strict.
|
|
779
|
+
*/
|
|
780
|
+
export const authCapabilities = z.object({
|
|
781
|
+
mintable: z.boolean(),
|
|
782
|
+
providers: z.array(z.object({ name: z.string(), mintable: z.boolean() })),
|
|
783
|
+
});
|
|
784
|
+
// -- access-control policy (authoring + versioning) --------------------------
|
|
785
|
+
/**
|
|
786
|
+
* `POST /api/auth/validate-condition` — the fail-closed jq guard. `ok` is the
|
|
787
|
+
* compile/render result; `result` is the boolean of a sample evaluation, or
|
|
788
|
+
* `null` when no `sample_context` was supplied (compile-only). A broken condition
|
|
789
|
+
* is a `400 {error}` surfaced by the transport, NEVER a false `ok` — so a
|
|
790
|
+
* syntactically-broken condition can be caught before it locks a key out. A drift
|
|
791
|
+
* throws `ApiSchemaError`.
|
|
792
|
+
*/
|
|
793
|
+
export const validateConditionResult = z.object({
|
|
794
|
+
ok: z.boolean(),
|
|
795
|
+
result: z.boolean().nullable(),
|
|
796
|
+
});
|
|
797
|
+
/**
|
|
798
|
+
* The enforced policy shape a version body carries. SECRET-ADJACENT — `condition`
|
|
799
|
+
* can reveal auth logic, so its fixtures are hand-authored, never captured.
|
|
800
|
+
* `condition`/`condition_id` are mutually exclusive at author time; both are
|
|
801
|
+
* nullable here because whichever mode is unused is stored as `null`.
|
|
802
|
+
*/
|
|
803
|
+
export const policyBody = z.object({
|
|
804
|
+
scopes: z.array(z.string()),
|
|
805
|
+
policy_data: jsonValue,
|
|
806
|
+
condition: z.string().nullable(),
|
|
807
|
+
condition_id: z.string().nullable(),
|
|
808
|
+
condition_kwargs: jsonValue.nullable(),
|
|
809
|
+
});
|
|
810
|
+
/**
|
|
811
|
+
* One immutable policy version row
|
|
812
|
+
* (`GET /api/auth/api-keys/{user_id}/policy/versions`). `is_current` flags the
|
|
813
|
+
* active pointer; `tags` is the generic per-version label list (kind-agnostic),
|
|
814
|
+
* mirroring the preset version shape.
|
|
815
|
+
*/
|
|
816
|
+
export const policyVersion = z.object({
|
|
817
|
+
version: z.number(),
|
|
818
|
+
body: policyBody,
|
|
819
|
+
tags: z.array(z.string()),
|
|
820
|
+
created_at: z.string(),
|
|
821
|
+
is_current: z.boolean(),
|
|
822
|
+
});
|
|
823
|
+
export const policyVersionList = z.array(policyVersion);
|
|
824
|
+
/** `POST /api/auth/api-keys/{user_id}/policy/rollback` — the re-pointed version. */
|
|
825
|
+
export const policyRollback = z.object({ user_id: z.string(), active_version: z.number() });
|
|
826
|
+
// -- backup ------------------------------------------------------------------
|
|
827
|
+
export const backupSections = z.array(z.object({ name: z.string(), secret: z.boolean() }));
|
|
828
|
+
/** The exported backup document — validated when the UI reads a picked file. */
|
|
829
|
+
export const backupDocument = z.object({
|
|
830
|
+
version: z.literal(1),
|
|
831
|
+
created_at: z.string(),
|
|
832
|
+
sections: z.record(z.string(), z.unknown()),
|
|
833
|
+
errors: z.record(z.string(), z.string()).optional(),
|
|
834
|
+
});
|
|
835
|
+
export const backupImportReport = z.object({
|
|
836
|
+
ok: z.boolean(),
|
|
837
|
+
sections: z.record(z.string(), z.object({
|
|
838
|
+
created: z.number(),
|
|
839
|
+
updated: z.number(),
|
|
840
|
+
skipped: z.number(),
|
|
841
|
+
errors: z.array(z.string()),
|
|
842
|
+
new_api_keys: z
|
|
843
|
+
.array(z.object({
|
|
844
|
+
user_id: z.string(),
|
|
845
|
+
description: z.string(),
|
|
846
|
+
api_key: z.string(),
|
|
847
|
+
}))
|
|
848
|
+
.optional(),
|
|
849
|
+
// The `manifest` and `env` sections apply through the config pipeline, so their
|
|
850
|
+
// report carries the mode-wrapped fleet `fanout` of the restore's reload; other
|
|
851
|
+
// sections omit it. Parsed EXPLICITLY so the shared fleet-report handler can
|
|
852
|
+
// surface a failed restore propagation — otherwise zod would strip it silently.
|
|
853
|
+
fanout: fleetReportFanout.optional(),
|
|
854
|
+
})),
|
|
855
|
+
});
|
|
856
|
+
// -- schedules ---------------------------------------------------------------
|
|
857
|
+
// The skeleton returns opaque backend-tool results; the table reads a few known
|
|
858
|
+
// fields and passes the rest through untouched.
|
|
859
|
+
export const scheduleItem = z
|
|
860
|
+
.object({
|
|
861
|
+
name: z.string(),
|
|
862
|
+
enabled: z.boolean().optional(),
|
|
863
|
+
schedule: jsonValue.optional(),
|
|
864
|
+
target: jsonValue.optional(),
|
|
865
|
+
args: z.array(jsonValue).optional(),
|
|
866
|
+
kwargs: z.record(z.string(), jsonValue).optional(),
|
|
867
|
+
})
|
|
868
|
+
.passthrough();
|
|
869
|
+
export const scheduleList = z.array(scheduleItem);
|
|
870
|
+
export const serverDateTime = z
|
|
871
|
+
.object({
|
|
872
|
+
utc: jsonValue,
|
|
873
|
+
local: jsonValue.optional(),
|
|
874
|
+
system: jsonValue.optional(),
|
|
875
|
+
})
|
|
876
|
+
.passthrough();
|
|
877
|
+
// -- observability -----------------------------------------------------------
|
|
878
|
+
export const dashboardMetrics = z.object({
|
|
879
|
+
summary: z.object({
|
|
880
|
+
totalRuns: z.number(),
|
|
881
|
+
totalCost: z.number(),
|
|
882
|
+
totalTokens: z.number(),
|
|
883
|
+
averageLatencyMs: z.number(),
|
|
884
|
+
avgCostPerRun: z.number(),
|
|
885
|
+
avgTokensPerRun: z.number(),
|
|
886
|
+
timeToFirstTokenMs: z.number().nullable(),
|
|
887
|
+
}),
|
|
888
|
+
timeSeries: z.array(z.object({
|
|
889
|
+
bucket: z.string().nullable(),
|
|
890
|
+
runs: z.number(),
|
|
891
|
+
cost: z.number(),
|
|
892
|
+
avgLatencyMs: z.number(),
|
|
893
|
+
totalTokens: z.number(),
|
|
894
|
+
})),
|
|
895
|
+
byModel: z.array(z.object({
|
|
896
|
+
model: z.string(),
|
|
897
|
+
calls: z.number(),
|
|
898
|
+
cost: z.number(),
|
|
899
|
+
totalTokens: z.number(),
|
|
900
|
+
avgLatencyMs: z.number(),
|
|
901
|
+
})),
|
|
902
|
+
granularity: z.enum(['hour', 'day', 'week']),
|
|
903
|
+
});
|
|
904
|
+
export const run = z.object({
|
|
905
|
+
id: z.string(),
|
|
906
|
+
traceId: z.string(),
|
|
907
|
+
createdAt: z.string().nullable(),
|
|
908
|
+
tags: z.array(z.string()),
|
|
909
|
+
status: z.enum(['error', 'success']),
|
|
910
|
+
cost: z.number().nullable(),
|
|
911
|
+
latencyMs: z.number().nullable(),
|
|
912
|
+
totalTokens: z.number().nullable(),
|
|
913
|
+
model: z.string().nullable(),
|
|
914
|
+
inputPreview: jsonValue,
|
|
915
|
+
outputPreview: jsonValue,
|
|
916
|
+
fetchError: z.string().nullable(),
|
|
917
|
+
});
|
|
918
|
+
export const runsPage = z.object({
|
|
919
|
+
items: z.array(run),
|
|
920
|
+
page: z.number(),
|
|
921
|
+
nextPage: z.number().nullable(),
|
|
922
|
+
});
|
|
923
|
+
export const runSpan = z.object({
|
|
924
|
+
id: z.string(),
|
|
925
|
+
parentId: z.string().nullable(),
|
|
926
|
+
traceId: z.string().nullable(),
|
|
927
|
+
name: z.string().nullable(),
|
|
928
|
+
type: z.string().nullable(),
|
|
929
|
+
level: z.string().nullable(),
|
|
930
|
+
statusMessage: z.string().nullable(),
|
|
931
|
+
start: z.string().nullable(),
|
|
932
|
+
end: z.string().nullable(),
|
|
933
|
+
model: z.string().nullable(),
|
|
934
|
+
usage: jsonValue.nullable(),
|
|
935
|
+
metadata: jsonValue.nullable(),
|
|
936
|
+
input: jsonValue,
|
|
937
|
+
output: jsonValue,
|
|
938
|
+
nodeId: z.string().nullable(),
|
|
939
|
+
});
|
|
940
|
+
export const runTrace = z.object({
|
|
941
|
+
traceId: z.string(),
|
|
942
|
+
timestamp: z.string().nullable(),
|
|
943
|
+
tags: z.array(z.string()),
|
|
944
|
+
totalCost: z.number().nullable(),
|
|
945
|
+
input: jsonValue,
|
|
946
|
+
output: jsonValue,
|
|
947
|
+
metadata: jsonValue.nullable(),
|
|
948
|
+
availability: z.enum(['unavailable', 'full', 'partial']),
|
|
949
|
+
fetchError: z.string().nullable(),
|
|
950
|
+
spans: z.array(runSpan),
|
|
951
|
+
});
|
|
952
|
+
// -- marketplace -------------------------------------------------------------
|
|
953
|
+
/**
|
|
954
|
+
* One contained item of a marketplace plugin (a searchable row): its kind
|
|
955
|
+
* (tool / agent / extension / …), name, description, and free-form tags.
|
|
956
|
+
*/
|
|
957
|
+
export const marketplaceItem = z.object({
|
|
958
|
+
kind: z.string(),
|
|
959
|
+
name: z.string(),
|
|
960
|
+
description: z.string(),
|
|
961
|
+
tags: z.array(z.string()),
|
|
962
|
+
});
|
|
963
|
+
/**
|
|
964
|
+
* One item-level search row (`GET /api/marketplace/search`): the contained item
|
|
965
|
+
* plus the listing it belongs to. `ref` is the "namespace/name" install target
|
|
966
|
+
* (install targets the LISTING), so every row carries enough to group under and
|
|
967
|
+
* link to its plugin. `latest_version` is nullable defensively; the search
|
|
968
|
+
* relation only emits listings with a published version.
|
|
969
|
+
*/
|
|
970
|
+
export const marketplaceSearchRow = z.object({
|
|
971
|
+
item: marketplaceItem,
|
|
972
|
+
ref: z.string(),
|
|
973
|
+
namespace: z.string(),
|
|
974
|
+
name: z.string(),
|
|
975
|
+
display_name: z.string().nullable(),
|
|
976
|
+
icon_url: z.string().nullable(),
|
|
977
|
+
package: z.string(),
|
|
978
|
+
description: z.string(),
|
|
979
|
+
categories: z.array(z.string()),
|
|
980
|
+
tags: z.array(z.string()),
|
|
981
|
+
trust_tier: z.string(),
|
|
982
|
+
pricing: z.string(),
|
|
983
|
+
latest_version: z.string().nullable(),
|
|
984
|
+
downloads: z.number(),
|
|
985
|
+
updated_at: z.string(),
|
|
986
|
+
});
|
|
987
|
+
/**
|
|
988
|
+
* One page of search rows. The wire carries no next-page field — has-next is
|
|
989
|
+
* COMPUTED: `page * page_size < total`.
|
|
990
|
+
*/
|
|
991
|
+
export const marketplaceSearchPage = z.object({
|
|
992
|
+
items: z.array(marketplaceSearchRow),
|
|
993
|
+
total: z.number(),
|
|
994
|
+
page: z.number(),
|
|
995
|
+
page_size: z.number(),
|
|
996
|
+
});
|
|
997
|
+
/**
|
|
998
|
+
* One version of a listing as the versions list carries it (`version`, its
|
|
999
|
+
* publication `status`, the `contract_range` it was validated against, and when
|
|
1000
|
+
* it was `published_at`). `contract_range` and `published_at` are null until a
|
|
1001
|
+
* version reaches the published state, so both are nullable.
|
|
1002
|
+
*/
|
|
1003
|
+
export const marketplaceVersion = z.object({
|
|
1004
|
+
version: z.string(),
|
|
1005
|
+
contract_range: z.string().nullable(),
|
|
1006
|
+
status: z.string(),
|
|
1007
|
+
published_at: z.string().nullable(),
|
|
1008
|
+
});
|
|
1009
|
+
/**
|
|
1010
|
+
* The listing's latest published version as embedded in its detail: the version
|
|
1011
|
+
* plus the items that version contains.
|
|
1012
|
+
*/
|
|
1013
|
+
export const marketplaceLatestVersion = marketplaceVersion.extend({
|
|
1014
|
+
items: z.array(marketplaceItem),
|
|
1015
|
+
});
|
|
1016
|
+
/** One advisory row; `withdrawn_at` non-null means it no longer applies. */
|
|
1017
|
+
export const marketplaceAdvisory = z.object({
|
|
1018
|
+
id: z.number(),
|
|
1019
|
+
// The "namespace/name" listing ref exactly as the registry serves it — the
|
|
1020
|
+
// wire field consumers match installed plugins against.
|
|
1021
|
+
listing: z.string(),
|
|
1022
|
+
affected_versions: z.string(),
|
|
1023
|
+
severity: z.string(),
|
|
1024
|
+
summary: z.string(),
|
|
1025
|
+
created_at: z.string(),
|
|
1026
|
+
withdrawn_at: z.string().nullable(),
|
|
1027
|
+
});
|
|
1028
|
+
/**
|
|
1029
|
+
* A plugin's full detail (`GET /api/marketplace/plugins/{ns}/{name}`): the
|
|
1030
|
+
* listing, its latest published version (`null` when nothing is published yet,
|
|
1031
|
+
* carrying the contained items), and the known version history.
|
|
1032
|
+
*/
|
|
1033
|
+
export const marketplacePluginDetail = z.object({
|
|
1034
|
+
namespace: z.string(),
|
|
1035
|
+
name: z.string(),
|
|
1036
|
+
display_name: z.string().nullable(),
|
|
1037
|
+
icon_url: z.string().nullable(),
|
|
1038
|
+
package: z.string(),
|
|
1039
|
+
description: z.string(),
|
|
1040
|
+
readme_md: z.string().nullable(),
|
|
1041
|
+
license: z.string().nullable(),
|
|
1042
|
+
homepage_url: z.string().nullable(),
|
|
1043
|
+
repository_url: z.string().nullable(),
|
|
1044
|
+
categories: z.array(z.string()),
|
|
1045
|
+
tags: z.array(z.string()),
|
|
1046
|
+
trust_tier: z.string(),
|
|
1047
|
+
pricing: z.string(),
|
|
1048
|
+
downloads: z.number(),
|
|
1049
|
+
latest: marketplaceLatestVersion.nullable(),
|
|
1050
|
+
versions: z.array(marketplaceVersion),
|
|
1051
|
+
});
|
|
1052
|
+
/**
|
|
1053
|
+
* One installed marketplace plugin as the local attribution store records it.
|
|
1054
|
+
* `latest` is the newest registry version (`null` when unknown); update
|
|
1055
|
+
* availability comes from the wire field `update_available` (computed
|
|
1056
|
+
* server-side as a PEP 440 comparison). `missing_upstream` flags a plugin the
|
|
1057
|
+
* registry no longer lists.
|
|
1058
|
+
*/
|
|
1059
|
+
export const marketplaceInstalledPlugin = z.object({
|
|
1060
|
+
ref: z.string(),
|
|
1061
|
+
version: z.string(),
|
|
1062
|
+
source: z.string(),
|
|
1063
|
+
installed_at: z.string(),
|
|
1064
|
+
latest: z.string().nullable(),
|
|
1065
|
+
update_available: z.boolean(),
|
|
1066
|
+
missing_upstream: z.boolean(),
|
|
1067
|
+
});
|
|
1068
|
+
export const marketplaceInstalled = z.array(marketplaceInstalledPlugin);
|
|
1069
|
+
/** The advisory state for installed plugins, with when it was last fetched. */
|
|
1070
|
+
export const marketplaceAdvisories = z.object({
|
|
1071
|
+
advisories: z.array(marketplaceAdvisory),
|
|
1072
|
+
fetched_at: z.string(),
|
|
1073
|
+
});
|
|
1074
|
+
/** The registry's controlled category list, proxied through the skeleton. */
|
|
1075
|
+
export const marketplaceCategories = z.array(z.string());
|
|
1076
|
+
/**
|
|
1077
|
+
* The receipt of an install / update: what landed and at which version.
|
|
1078
|
+
* `notes` (env-selected items naming their activating env var) and any
|
|
1079
|
+
* non-critical `advisories` ride BOTH the install and update receipts. The wire
|
|
1080
|
+
* also carries `package`, `reload`, and `pip_output`, which the receipt does not
|
|
1081
|
+
* model — zod's default strip drops them; the UI renders the receipt, not the
|
|
1082
|
+
* raw install log.
|
|
1083
|
+
*/
|
|
1084
|
+
export const marketplaceInstallResult = z.object({
|
|
1085
|
+
ref: z.string(),
|
|
1086
|
+
version: z.string(),
|
|
1087
|
+
notes: z.array(z.string()),
|
|
1088
|
+
advisories: z.array(marketplaceAdvisory),
|
|
1089
|
+
});
|
|
1090
|
+
/**
|
|
1091
|
+
* The receipt of an uninstall. `notes` carries operator warnings (e.g. a config
|
|
1092
|
+
* provider still selected by its env var); render them like the install
|
|
1093
|
+
* receipt's notes.
|
|
1094
|
+
*/
|
|
1095
|
+
export const marketplaceUninstallResult = z.object({
|
|
1096
|
+
ref: z.string(),
|
|
1097
|
+
uninstalled: z.literal(true),
|
|
1098
|
+
notes: z.array(z.string()),
|
|
1099
|
+
});
|
|
1100
|
+
// Background tool-run schemas live in their own module; re-exported here so the
|
|
1101
|
+
// shared fixture-validation contract resolves them by name alongside the rest.
|
|
1102
|
+
export { toolRunSubmitResult, toolRunRecord, toolRunList } from './tool-runs';
|
|
1103
|
+
//# sourceMappingURL=schemas.js.map
|