@tai42/api-client 15.0.0 → 16.0.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/src/schemas.ts CHANGED
@@ -6,41 +6,61 @@
6
6
  */
7
7
  import { z } from 'zod';
8
8
 
9
+ import { templatedText } from './templated-text';
10
+ import * as generated from './generated/served-schemas';
11
+
12
+ export { templatedText, requiredTemplatedText } from './templated-text';
13
+ export type { TemplatedText } from './templated-text';
14
+
15
+ // Served-document schemas are GENERATED from the platform contract JSON-schema
16
+ // bundle (src/generated/served-schemas.ts) and re-exported here under the names the
17
+ // rest of the client uses. The generator maps every `x-tai42-templated-text` field
18
+ // to the shared `templatedText` zod, so a served body can never be a bare string.
19
+ // The plain response DTOs below (list wrappers, acks, version rows) stay hand-written.
20
+ export const stateInjection = generated.stateInjection;
21
+ export type StateInjection = z.infer<typeof stateInjection>;
22
+ export const stateUpdate = generated.stateUpdate;
23
+ export type StateUpdate = z.infer<typeof stateUpdate>;
24
+ export const stateAttach = generated.stateAttach;
25
+ export type StateAttach = z.infer<typeof stateAttach>;
26
+ export const stateBinding = generated.stateBinding;
27
+ export type StateBinding = z.infer<typeof stateBinding>;
28
+ export const presetBody = generated.presetBody;
29
+ export type PresetBody = z.input<typeof presetBody>;
30
+ export const conversationRoute = generated.conversationRoute;
31
+ export type ConversationRoute = z.infer<typeof conversationRoute>;
32
+ export const conversationRouteCreate = generated.conversationRouteCreate;
33
+ export type ConversationRouteCreate = z.input<typeof conversationRouteCreate>;
34
+ export const targetConversationConfig = generated.targetConversationConfig;
35
+ export type TargetConversationConfig = z.input<typeof targetConversationConfig>;
36
+ export const channelTemplate = generated.channelTemplate;
37
+ export type ChannelTemplate = z.infer<typeof channelTemplate>;
38
+ export const hookSubject = generated.hookSubject;
39
+ export type HookSubject = z.infer<typeof hookSubject>;
40
+ export const hookParams = generated.hookParams;
41
+ export type HookParams = z.infer<typeof hookParams>;
42
+ export const hookRegister = generated.hookRegister;
43
+ export type HookRegister = z.input<typeof hookRegister>;
44
+ export const stateDeclaration = generated.stateDeclaration;
45
+ export type StateDeclaration = z.infer<typeof stateDeclaration>;
46
+ export const stateTemplateDocument = generated.stateTemplateDocument;
47
+ export type StateTemplateDocument = z.infer<typeof stateTemplateDocument>;
48
+ export const templateJq = generated.stateTemplateJq;
49
+ export type TemplateJq = z.infer<typeof templateJq>;
50
+ export const templateReconcile = generated.stateTemplateReconcile;
51
+ export type TemplateReconcile = z.infer<typeof templateReconcile>;
52
+ export const templateDeclarations = generated.stateTemplateDeclarations;
53
+ export type TemplateDeclarations = z.infer<typeof templateDeclarations>;
54
+ export const policyBody = generated.accessPolicy;
55
+ export type PolicyBody = z.infer<typeof policyBody>;
56
+ export const roleBody = generated.roleDefinition;
57
+ export type RoleBody = z.infer<typeof roleBody>;
9
58
  /** An arbitrary JSON value (tool results, rendered payloads). */
10
59
  export const jsonValue: z.ZodType = z.unknown();
11
60
 
12
61
  /** A JSON Schema object (Pydantic-emitted). Permissive; parsed by the auto-form. */
13
62
  export const jsonSchema = z.record(z.string(), z.unknown());
14
63
 
15
- /**
16
- * One authored-text value: either inline `content` (text written in place) or a
17
- * stored template `id` (a template resource fetched and rendered), never both and
18
- * never neither. `kwargs` are the render parameters and apply in BOTH cases — a
19
- * prompt with placeholders needs its values whether it was typed inline or fetched.
20
- * `.strict()` refuses any other key so a drifting field fails the parse loudly.
21
- */
22
- export const templatedText = z
23
- .object({
24
- content: z.string().optional(),
25
- id: z.string().optional(),
26
- kwargs: z.record(z.string(), z.unknown()).optional(),
27
- })
28
- .strict()
29
- .refine((value) => (value.content === undefined) !== (value.id === undefined), {
30
- message: 'A templated-text value sets exactly one of `content` or `id`.',
31
- });
32
- export type TemplatedText = z.infer<typeof templatedText>;
33
-
34
- /**
35
- * A templated-text value that must carry a source: inline `content` is non-empty, or
36
- * a stored template `id` is chosen. Used where the platform contract requires a
37
- * value (e.g. a state binding's subject, an update's adapter when present).
38
- */
39
- export const requiredTemplatedText = templatedText.refine(
40
- (value) => (value.id !== undefined ? value.id.length > 0 : (value.content ?? '').length > 0),
41
- { message: 'Provide inline text or choose a stored template.' },
42
- );
43
-
44
64
  // -- tools -------------------------------------------------------------------
45
65
 
46
66
  export const toolNames = z.array(z.string());
@@ -178,93 +198,6 @@ export const presetDetail = presetRecord.extend({
178
198
  });
179
199
  export type PresetDetail = z.infer<typeof presetDetail>;
180
200
 
181
- /**
182
- * One input injection of a state binding: BEFORE the run, a value is produced and
183
- * placed into the run input under `into` (the run-input field name). A template
184
- * `input` jq (`template_jq`, named `name` or attachment-qualified `template.name`) reads the
185
- * record — its input is the record (`.`), with declared params bound as `$params`; a
186
- * custom `jq` reads `{record, input}` → the value. Exactly one of `template_jq`/`jq`
187
- * carries the program; the other is `null`.
188
- */
189
- export const stateInjection = z.object({
190
- // `template_jq` names a program the attached state template ships; it is resolved
191
- // through compiled programs, never rendered as text, so it stays a plain string.
192
- template_jq: z.string().nullable().default(null),
193
- jq: templatedText.nullable().default(null),
194
- into: z.string(),
195
- });
196
- export type StateInjection = z.infer<typeof stateInjection>;
197
-
198
- /**
199
- * One update of a state binding: AFTER the run, a template-relative op batch is
200
- * applied through the store. A NAMED update (`template_jq`) runs the template `update`
201
- * jq over `{record, input}` where `input` is built by `adapter` — a jq over
202
- * `{output, input}` that constructs the update jq's declared input object. A custom
203
- * `jq` runs over `{record, output, input}` → `[{op, path, value}]` and carries no
204
- * adapter. `op_id` is an optional idempotency-key expression. Exactly one of
205
- * `template_jq`/`jq` carries the program.
206
- */
207
- export const stateUpdate = z.object({
208
- // `template_jq` names a program the attached state template ships; resolved through
209
- // compiled programs, never rendered as text, so it stays a plain string.
210
- template_jq: z.string().nullable().default(null),
211
- jq: templatedText.nullable().default(null),
212
- // A named update's adapter is either an authored value or `null` (no adapter — the
213
- // run input passes through). When present it must carry a source (the platform
214
- // compile-checks a non-null adapter and refuses an empty one).
215
- adapter: requiredTemplatedText.nullable().default(null),
216
- op_id: templatedText.nullable().default(null),
217
- });
218
- export type StateUpdate = z.infer<typeof stateUpdate>;
219
-
220
- /**
221
- * One attached state of a binding: the `state`, one or more `templates`
222
- * (attach-on-use — attached to the state on the binding's save), the `subject_expr`
223
- * (a jq yielding the record KEY — a bare key string OR a full subject object — that
224
- * this binding reads and updates), an optional `scope_expr` (a boolean jq predicate;
225
- * when it is false this state is skipped for the run), and the ordered
226
- * `input_injections` (before the run) and `updates` (after it).
227
- */
228
- export const stateAttach = z.object({
229
- state: z.string(),
230
- templates: z.array(z.string()).default([]),
231
- // The platform contract enforces a subject that carries a source; an empty one is
232
- // rejected here, never sent for the server to refuse.
233
- subject_expr: requiredTemplatedText,
234
- scope_expr: templatedText.nullable().default(null),
235
- input_injections: z.array(stateInjection).default([]),
236
- updates: z.array(stateUpdate).default([]),
237
- });
238
- export type StateAttach = z.infer<typeof stateAttach>;
239
-
240
- /**
241
- * The one generic binding shape carried, optionally, on every runnable definition
242
- * (a preset, a channel route, a schedule, a hook) and repeated per node by the
243
- * flow engine. It attaches one or more states, each with its templates, subject,
244
- * injections and updates. `null` on a definition means no binding. Mirrors the
245
- * platform `StateBinding` document.
246
- */
247
- export const stateBinding = z.object({
248
- // At least one attached state: the platform contract refuses a zero-state binding, so
249
- // "no binding" is spelled `null` on the definition, never `{ states: [] }`.
250
- states: z.array(stateAttach).min(1),
251
- });
252
- export type StateBinding = z.infer<typeof stateBinding>;
253
-
254
- /** The full version body a preset stores under `kind="preset"`. */
255
- export const presetBody = z.object({
256
- base_tool: z.string(),
257
- description: z.string(),
258
- fixed_kwargs: z.record(z.string(), z.unknown()),
259
- extensions: presetExtensions,
260
- output_schema: z.record(z.string(), z.unknown()).nullable(),
261
- input_schema: z.record(z.string(), z.unknown()).nullable(),
262
- // The optional door-layer state binding this preset applies around every run
263
- // (`null` when the preset touches no state).
264
- state_binding: stateBinding.nullable().default(null),
265
- });
266
- export type PresetBody = z.infer<typeof presetBody>;
267
-
268
201
  /**
269
202
  * One immutable version row (`GET /api/presets/{name}/versions[/{version}]`).
270
203
  * `is_current` flags the active version; `tags` here is the GENERIC per-version
@@ -1225,74 +1158,12 @@ export type ConversationAnswerStatus = z.infer<typeof conversationAnswerStatus>;
1225
1158
  export const conversationRecordOrigin = z.enum(['client', 'operator']);
1226
1159
  export type ConversationRecordOrigin = z.infer<typeof conversationRecordOrigin>;
1227
1160
 
1228
- /**
1229
- * A stored routing row as a read door returns it, its `callback_secret` withheld.
1230
- *
1231
- * `initial_mode`, `turns_per_hour_override`, and `error_reply_text` carry defaults
1232
- * because a stored row always returns them, while a hand-built stub/fixture may
1233
- * omit them — the default keeps such a payload valid without inventing a field the
1234
- * server never sends.
1235
- */
1236
- export const conversationRoute = z.object({
1237
- route_name: z.string(),
1238
- door: conversationDoor,
1239
- target_kind: conversationTargetKind,
1240
- target_name: z.string(),
1241
- payload_expr: z.string().nullable(),
1242
- reply_expr: z.string().nullable(),
1243
- initial_mode: conversationMode.default('agent'),
1244
- execution_key: z.string(),
1245
- channel: z.string().nullable(),
1246
- our_identity: z.string().nullable(),
1247
- callback_url: z.string().nullable(),
1248
- turns_per_hour_override: z.number().int().nullable().default(null),
1249
- error_reply_text: z.string().nullable().default(null),
1250
- execution_key_fingerprint: z.string(),
1251
- // The door-layer state binding the backend stores on the route's target config and
1252
- // returns on read (`null` when the route touches no state) — the edit form prefills it.
1253
- state_binding: stateBinding.nullable().default(null),
1254
- });
1255
- export type ConversationRoute = z.infer<typeof conversationRoute>;
1256
-
1257
1161
  export const conversationRoutes = z.object({
1258
1162
  items: z.array(conversationRoute),
1259
1163
  total: z.number(),
1260
1164
  });
1261
1165
  export type ConversationRoutes = z.infer<typeof conversationRoutes>;
1262
1166
 
1263
- /**
1264
- * The client-facing create/edit body for a conversation route — the wire model of
1265
- * `tai42_contract.conversations.ConversationRouteCreate`. `POST
1266
- * /api/conversations/{route_name}` is an UPSERT, so this one shape is both the
1267
- * create AND the edit body. `route_name` rides the URL path; it is carried here too
1268
- * (the door rejects a body whose `route_name` disagrees with the path).
1269
- *
1270
- * The contract's cross-field validators are mirrored where the form authors this
1271
- * body: `payload_expr`/`reply_expr` are tool-only; an `api` door carries a
1272
- * `callback_url` (and no channel identity); a `channel` door carries `channel` +
1273
- * `our_identity` (and no callback). Sent explicitly (including `null`) so an edit
1274
- * clears a field the operator emptied rather than leaving the stored value.
1275
- */
1276
- export const conversationRouteCreate = z.object({
1277
- route_name: z.string(),
1278
- door: conversationDoor,
1279
- target_kind: conversationTargetKind,
1280
- target_name: z.string().min(1),
1281
- payload_expr: z.string().nullable().default(null),
1282
- reply_expr: z.string().nullable().default(null),
1283
- initial_mode: conversationMode.default('agent'),
1284
- execution_key: z.string().min(1),
1285
- channel: z.string().nullable().default(null),
1286
- our_identity: z.string().nullable().default(null),
1287
- callback_url: z.string().nullable().default(null),
1288
- turns_per_hour_override: z.number().int().positive().nullable().default(null),
1289
- error_reply_text: z.string().min(1).max(2000).nullable().default(null),
1290
- // The optional door-layer state binding this route applies around every turn's
1291
- // tool run (`null` when the route touches no state).
1292
- state_binding: stateBinding.nullable().default(null),
1293
- });
1294
- export type ConversationRouteCreate = z.infer<typeof conversationRouteCreate>;
1295
-
1296
1167
  /**
1297
1168
  * The create/replace reply: whether THIS call created the row (vs. replaced an
1298
1169
  * existing one), the row itself (its `callback_secret` withheld), and the
@@ -1421,28 +1292,6 @@ export const conversationThreadMessageSent = z.object({
1421
1292
  });
1422
1293
  export type ConversationThreadMessageSent = z.infer<typeof conversationThreadMessageSent>;
1423
1294
 
1424
- /**
1425
- * Per-target presentation config for the conversation bridge, keyed by
1426
- * `(target_kind, target_name)` — the wire model of
1427
- * `tai42_contract.conversations.TargetConversationConfig`. The row carries no
1428
- * server-derived fields, so this ONE shape is the stored row AND the upsert body
1429
- * (`PUT /api/conversation-configs/{target_kind}/{target_name}` is the create AND
1430
- * the edit path).
1431
- *
1432
- * `multichannel` opts the target into person linking; `greeting_template` is the
1433
- * first-contact greeting. `greeting_template` is a `str.format` template (NOT a jq
1434
- * expression) that may reference at most the `{pairing_code}` placeholder — it
1435
- * carries no `x-tai42-expression`. `null` is the explicit spelling for "no
1436
- * greeting"; a blank string is refused server-side.
1437
- */
1438
- export const targetConversationConfig = z.object({
1439
- target_kind: conversationTargetKind,
1440
- target_name: z.string().min(1),
1441
- multichannel: z.boolean().default(false),
1442
- greeting_template: z.string().nullable().default(null),
1443
- });
1444
- export type TargetConversationConfig = z.infer<typeof targetConversationConfig>;
1445
-
1446
1295
  /** Every stored per-target config (`GET /api/conversation-configs`). */
1447
1296
  export const conversationConfigs = z.object({
1448
1297
  items: z.array(targetConversationConfig),
@@ -1563,19 +1412,6 @@ export type WebEntryCodeRevoked = z.infer<typeof webEntryCodeRevoked>;
1563
1412
  // send), `options` (tappable option labels) — plus the `audience` identity the
1564
1413
  // record is addressed to; the inbox renders them.
1565
1414
 
1566
- /**
1567
- * A pre-approved channel template a notification carried, stored raw on the sink
1568
- * record when the notify supplied one (server-side it is mutually exclusive with
1569
- * `media`/`options`). `parameters` is the POSITIONAL list of body-text values
1570
- * substituted into the template's placeholders in order; empty when it has none.
1571
- */
1572
- export const channelTemplate = z.object({
1573
- name: z.string(),
1574
- language: z.string(),
1575
- parameters: z.array(z.string()).default([]),
1576
- });
1577
- export type ChannelTemplate = z.infer<typeof channelTemplate>;
1578
-
1579
1415
  export const notification = z.object({
1580
1416
  id: z.string(),
1581
1417
  message: z.string(),
@@ -1608,9 +1444,10 @@ export const notifications = z.object({
1608
1444
  export type Notifications = z.infer<typeof notifications>;
1609
1445
 
1610
1446
  // -- hooks -------------------------------------------------------------------
1611
- // Shapes match tai42_contract.hooks.HookParams: a hook fires a `tool` on a `topic`,
1612
- // optionally gated by a `condition` and shaped by an `expr` (each an authored
1613
- // templated-text value: inline content or a stored template id, with render kwargs).
1447
+ // The hand-written hook response DTOs: the fire-door authority enum, the list rows and
1448
+ // their wrapper, and the per-topic verifier/trigger metadata. The served hook DOCUMENT
1449
+ // schemas (`hookSubject`/`hookParams`/`hookRegister`) are generated from the contract
1450
+ // bundle and re-exported at the top of this file, not defined here.
1614
1451
 
1615
1452
  /** A fire door — who may trigger a path, not what the fire may do. A string enum. */
1616
1453
  export const triggerAuth = z.enum([
@@ -1622,38 +1459,6 @@ export const triggerAuth = z.enum([
1622
1459
  ]);
1623
1460
  export type TriggerAuth = z.infer<typeof triggerAuth>;
1624
1461
 
1625
- /**
1626
- * The optional state subject a hook fire targets (mirrors
1627
- * `tai42_contract.hooks.HookSubject`): the conversation-target scope plus the subject
1628
- * `kind` and a `key_expr` jq evaluated over the event payload at fire. `null` leaves the
1629
- * fire with no ambient state context.
1630
- */
1631
- export const hookSubject = z.object({
1632
- target_kind: conversationTargetKind,
1633
- target_name: z.string(),
1634
- kind: z.string(),
1635
- key_expr: z.string(),
1636
- });
1637
- export type HookSubject = z.infer<typeof hookSubject>;
1638
-
1639
- export const hookParams = z.object({
1640
- name: z.string(),
1641
- topic: z.string(),
1642
- tool: z.string(),
1643
- // The api-key `user_id` the fire runs AS.
1644
- execution_key: z.string().min(1),
1645
- tool_kwargs: z.record(z.string(), z.unknown()).default({}),
1646
- // The optional state subject the fire targets; `null` leaves it with no ambient
1647
- // state context (a state tool it calls must then carry an explicit subject).
1648
- subject: hookSubject.nullable().default(null),
1649
- condition: templatedText.nullable().default(null),
1650
- expr: templatedText.nullable().default(null),
1651
- // The optional door-layer state binding this hook applies around its fire's tool
1652
- // run (`null` when the hook touches no state).
1653
- state_binding: stateBinding.nullable().default(null),
1654
- });
1655
- export type HookParams = z.infer<typeof hookParams>;
1656
-
1657
1462
  export const hookList = z.object({
1658
1463
  items: z.array(hookParams),
1659
1464
  total: z.number(),
@@ -1824,24 +1629,6 @@ export type GrantLevel = z.infer<typeof grantLevel>;
1824
1629
  export const roleGrants = z.record(z.string(), grantLevel);
1825
1630
  export type RoleGrants = z.infer<typeof roleGrants>;
1826
1631
 
1827
- /**
1828
- * A role body (`GET /api/auth/roles`, and the write routes' echo). Layer 1 is the
1829
- * read-only base-tier ceiling — the seeded jq `condition` plus `base_tier`
1830
- * (`editor`/`viewer`, or `null` for the reserved `allow_all` admin). Layer 2 is the
1831
- * editable per-tag `grants` map. `allow_all` (admin) carries an empty `grants` map
1832
- * and reaches everything, un-lockable. `condition` is the base-tier jq (an authored
1833
- * templated-text value) — READ-ONLY here (no raw-jq authoring surface).
1834
- */
1835
- export const roleBody = z.object({
1836
- name: z.string(),
1837
- description: z.string(),
1838
- scopes: z.array(z.string()),
1839
- condition: templatedText.nullable(),
1840
- base_tier: z.string().nullable(),
1841
- allow_all: z.boolean(),
1842
- grants: roleGrants,
1843
- });
1844
- export type RoleBody = z.infer<typeof roleBody>;
1845
1632
  export const roleList = z.array(roleBody);
1846
1633
 
1847
1634
  /** `DELETE /api/auth/roles/{name}` — the deleted role's name. */
@@ -2081,19 +1868,6 @@ export const validateConditionResult = z.object({
2081
1868
  });
2082
1869
  export type ValidateConditionResult = z.infer<typeof validateConditionResult>;
2083
1870
 
2084
- /**
2085
- * The enforced policy shape a version body carries. SECRET-ADJACENT — `condition`
2086
- * can reveal auth logic, so its fixtures are hand-authored, never captured.
2087
- * `condition` is an authored templated-text value (inline content or a stored
2088
- * template id); `null` when the policy carries no condition.
2089
- */
2090
- export const policyBody = z.object({
2091
- scopes: z.array(z.string()),
2092
- policy_data: jsonValue,
2093
- condition: templatedText.nullable(),
2094
- });
2095
- export type PolicyBody = z.infer<typeof policyBody>;
2096
-
2097
1871
  /**
2098
1872
  * One immutable policy version row
2099
1873
  * (`GET /api/auth/api-keys/{user_id}/policy/versions`). `is_current` flags the
@@ -2717,12 +2491,13 @@ export type MarketplaceUninstallResult = z.infer<typeof marketplaceUninstallResu
2717
2491
  export { toolRunSubmitResult, toolRunRecord, toolRunList } from './tool-runs';
2718
2492
 
2719
2493
  // -- states ------------------------------------------------------------------
2720
- // Shapes mirror tai42_contract.states.models: a declared JSON document, one per
2721
- // subject. The base document, template fragments, write regimes and effective schema
2722
- // are permissive JSON records — the auto-form and JsonTree interpret them at runtime;
2723
- // a drift throws ApiSchemaError. Server-composed fields (`effective_schema`, `regimes`,
2724
- // list/stats derived columns) are modelled optional so a response that omits them still
2725
- // parses.
2494
+ // The hand-written state response DTOs — list rows and detail (built on the generated
2495
+ // `stateDeclaration`), attachments, per-subject records and subjects, stats and consumers.
2496
+ // The served DOCUMENT schemas (`stateDeclaration`/`stateTemplateDocument` and the template
2497
+ // sub-shapes) are generated from the contract bundle and re-exported at the top of this
2498
+ // file. The composed `effective_schema` and write `regimes` stay permissive JSON records
2499
+ // (the auto-form and JsonTree interpret them at runtime) and are server-computed, so they
2500
+ // are modelled optional and a response that omits them still parses.
2726
2501
 
2727
2502
  /** One addressed subject: the conversation-target scope plus the (kind, key) within it. */
2728
2503
  export const stateSubject = z.object({
@@ -2736,25 +2511,6 @@ export type StateSubject = z.infer<typeof stateSubject>;
2736
2511
  /** One absolute write-regime rule composed over the attachments: `{path, regime}`, permissive. */
2737
2512
  export const stateRegime = z.record(z.string(), z.unknown());
2738
2513
 
2739
- /**
2740
- * A declared state served on every read. `schema` is the base JSON Schema;
2741
- * `effective_schema` (the base composed with every attached template's fragment) and
2742
- * `regimes` (the absolute write-regime rules) are platform-COMPUTED and served on every
2743
- * read — modelled optional so a write body that omits them, or a server yet to compose
2744
- * them, still parses. A drift throws ApiSchemaError.
2745
- */
2746
- export const stateDeclaration = z.object({
2747
- name: z.string(),
2748
- description: z.string().default(''),
2749
- schema: jsonSchema,
2750
- subject_kinds: z.array(z.string()),
2751
- default_subject_kind: z.string(),
2752
- retention_days: z.number().nullable().default(null),
2753
- effective_schema: jsonSchema.nullable().optional(),
2754
- regimes: z.array(stateRegime).nullable().optional(),
2755
- });
2756
- export type StateDeclaration = z.infer<typeof stateDeclaration>;
2757
-
2758
2514
  /**
2759
2515
  * One row of `GET /api/states` — a served declaration (`list_states` dumps every
2760
2516
  * `StateDeclaration`) plus `updated_at` (the row's last-write timestamp, ISO or
@@ -2797,69 +2553,6 @@ export const stateStats = z.object({
2797
2553
  });
2798
2554
  export type StateStats = z.infer<typeof stateStats>;
2799
2555
 
2800
- /**
2801
- * One template jq: `jq` is a program over the subject's attached subtree, authored as a
2802
- * templated text (inline `content` or a stored template `id`); its `purpose` is either
2803
- * `input` (a read-only program feeding a tool argument) or `update` (a program returning
2804
- * an op batch that mutates the record). `params` are its declared argument names;
2805
- * `reads`/`writes` are the template-relative record paths an `update` declares, each a
2806
- * list of key segments; `description` is the human label.
2807
- */
2808
- export const templateJq = z.object({
2809
- description: z.string().default(''),
2810
- purpose: z.enum(['input', 'update']),
2811
- params: z.array(z.string()).default([]),
2812
- reads: z.array(z.array(z.string())).default([]),
2813
- writes: z.array(z.array(z.string())).default([]),
2814
- jq: templatedText,
2815
- });
2816
- export type TemplateJq = z.infer<typeof templateJq>;
2817
-
2818
- /**
2819
- * How a declarations edit settles open records: `orphans` returns the items a record
2820
- * orphans against the new declarations, `close` builds the op batch that closes one
2821
- * orphan, `resolutions` names the resolutions a close may name — each a templated-text
2822
- * jq program (inline `content` or a stored template `id`).
2823
- */
2824
- export const templateReconcile = z.object({
2825
- orphans: templatedText,
2826
- close: templatedText,
2827
- resolutions: templatedText,
2828
- });
2829
- export type TemplateReconcile = z.infer<typeof templateReconcile>;
2830
-
2831
- /**
2832
- * A template's declarations section: `schema` is the JSON schema of the static values an
2833
- * attachment stores, and `check` is an optional attach-time predicate authored as a
2834
- * templated text (inline `content` or a stored template `id`), `null` when the template
2835
- * declares none.
2836
- */
2837
- export const templateDeclarations = z.object({
2838
- schema: jsonSchema,
2839
- check: templatedText.nullable().default(null),
2840
- });
2841
- export type TemplateDeclarations = z.infer<typeof templateDeclarations>;
2842
-
2843
- /**
2844
- * The platform half of a state-template document (mirrors StateTemplateDocument).
2845
- * `template_jq` maps a name to its definition (each carrying its `purpose`);
2846
- * `reconcile` is the settle policy; `declarations` is the static-values section — all
2847
- * optional (`null` when the template declares none).
2848
- */
2849
- export const stateTemplateDocument = z.object({
2850
- kind: z.literal('state-template').default('state-template'),
2851
- name: z.string(),
2852
- description: z.string().default(''),
2853
- parameters: z.record(z.string(), z.unknown()).default({}),
2854
- schema: jsonSchema,
2855
- regimes: z.array(stateRegime).default([]),
2856
- declarations: templateDeclarations.nullable().default(null),
2857
- trace: z.record(z.string(), z.unknown()).default({}),
2858
- template_jq: z.record(z.string(), templateJq).nullable().default(null),
2859
- reconcile: templateReconcile.nullable().default(null),
2860
- });
2861
- export type StateTemplateDocument = z.infer<typeof stateTemplateDocument>;
2862
-
2863
2556
  /**
2864
2557
  * One row of `GET /api/state-templates`. The template document plus the two derived columns
2865
2558
  * the Templates tab shows: `attached_to` (how many states attach it — a delete is refused
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * One authored-text value: either inline `content` (text written in place) or a
5
+ * stored template `id` (a template resource fetched and rendered), never both and
6
+ * never neither. `kwargs` are the render parameters and apply in BOTH cases — a
7
+ * prompt with placeholders needs its values whether it was typed inline or fetched.
8
+ * `.strict()` refuses any other key so a drifting field fails the parse loudly.
9
+ *
10
+ * This is the target the generated served-document schemas map every platform
11
+ * `x-tai42-templated-text` field to, so a body can never regenerate as a bare
12
+ * `z.string()`. It lives in its own module because the generated module imports it,
13
+ * and the generated module is imported back by `schemas.ts`.
14
+ */
15
+ export const templatedText = z
16
+ .object({
17
+ content: z.string().optional(),
18
+ id: z.string().optional(),
19
+ kwargs: z.record(z.string(), z.unknown()).optional(),
20
+ })
21
+ .strict()
22
+ .refine((value) => (value.content === undefined) !== (value.id === undefined), {
23
+ message: 'A templated-text value sets exactly one of `content` or `id`.',
24
+ });
25
+ export type TemplatedText = z.infer<typeof templatedText>;
26
+
27
+ /**
28
+ * A templated-text value that must carry a source: inline `content` is non-empty, or
29
+ * a stored template `id` is chosen. Used where the platform contract requires a
30
+ * value (e.g. a state binding's subject, an update's adapter when present).
31
+ */
32
+ export const requiredTemplatedText = templatedText.refine(
33
+ (value) => (value.id !== undefined ? value.id.length > 0 : (value.content ?? '').length > 0),
34
+ { message: 'Provide inline text or choose a stored template.' },
35
+ );