@tai42/api-client 1.1.0 → 1.3.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
@@ -319,77 +319,117 @@ export type BackendInfo = z.infer<typeof backendInfo>;
319
319
 
320
320
  /** The two worker kinds that join the bus: an ASGI `serve` worker or a
321
321
  * `backend` runtime process. A drift throws `ApiSchemaError`. */
322
- export const originKind = z.enum(['serve', 'backend']);
323
- export type OriginKind = z.infer<typeof originKind>;
322
+ export const workerKind = z.enum(['serve', 'backend']);
323
+ export type WorkerKind = z.infer<typeof workerKind>;
324
+
325
+ /** The lifecycle state a worker advertises in its presence value: `ready` once its
326
+ * resync has converged, `resyncing` while a boot/reconnect resync runs, `recycling`
327
+ * once it has begun a graceful self-exit. A drift throws `ApiSchemaError`. */
328
+ export const workerState = z.enum(['ready', 'resyncing', 'recycling']);
329
+ export type WorkerState = z.infer<typeof workerState>;
330
+
331
+ /** The last op a worker applied, stamped onto its presence row after the terminal
332
+ * reply. `outcome` is the free op-outcome string; `at` is the ISO instant it landed. */
333
+ export const workerLastOp = z.object({
334
+ op: z.string(),
335
+ outcome: z.string(),
336
+ at: z.string(),
337
+ });
338
+ export type WorkerLastOp = z.infer<typeof workerLastOp>;
324
339
 
325
340
  /**
326
- * One live worker on the bus — a presence-census entry. `origin` is the
327
- * `{kind}-{uuid}` identity used as a reload target; `kind` tells the two worker
328
- * kinds apart; `pid` is the process id the presence key carries.
341
+ * One live worker on the bus — a presence-census row. `name` is the stable slot
342
+ * (`{kind}-{n}`) that doubles as a reload target; `generation` is the monotonic life
343
+ * counter minted with the worker's claim; `kind` tells the two worker kinds apart;
344
+ * `pid` is the process id. `joined_at`/`beat_at` are ISO instants (first-seen and last
345
+ * heartbeat) for the cosmetic seen-since display; `state` is the advertised lifecycle
346
+ * state; `stale` is SERVER-computed from the row's remaining presence TTL against the
347
+ * bus cadence (a quiet row past the freshness bound — reconnecting or dead) and is the
348
+ * only freshness signal a client reads, never a client-side threshold; `last_op` is the
349
+ * worker's last applied op, or `null` before it has applied one.
329
350
  */
330
- export const fleetOrigin = z.object({
331
- origin: z.string(),
332
- kind: originKind,
351
+ export const fleetWorker = z.object({
352
+ name: z.string(),
353
+ kind: workerKind,
333
354
  pid: z.number(),
355
+ generation: z.number(),
356
+ joined_at: z.string(),
357
+ beat_at: z.string(),
358
+ state: workerState,
359
+ stale: z.boolean(),
360
+ last_op: workerLastOp.nullable(),
334
361
  });
335
- export type FleetOrigin = z.infer<typeof fleetOrigin>;
362
+ export type FleetWorker = z.infer<typeof fleetWorker>;
336
363
 
337
364
  /**
338
365
  * `GET /api/fleet/workers` — the live worker fleet from the bus presence census.
339
- * The census lists ALL subscribed origins (ASGI `serve` workers and backend-runtime
366
+ * The census lists ALL subscribed workers (ASGI `serve` workers and backend-runtime
340
367
  * processes alike), not only backend workers. A census read that fails surfaces as a
341
368
  * loud 500, never a fabricated empty fleet.
342
369
  */
343
- export const fleetWorkers = z.object({ workers: z.array(fleetOrigin) });
370
+ export const fleetWorkers = z.object({ workers: z.array(fleetWorker) });
344
371
  export type FleetWorkers = z.infer<typeof fleetWorkers>;
345
372
 
346
373
  /**
347
- * One origin's outcome within a fleet broadcast. `applied`/`failed` are terminal
348
- * wire replies from a subscriber; `missing`/`departed`/`timed_out` are
349
- * publisher-computed from the presence census (a silent origin never sends them).
350
- */
351
- export const fleetOutcome = z.enum(['applied', 'failed', 'missing', 'departed', 'timed_out']);
374
+ * One worker's outcome within a fleet broadcast. `applied`/`failed` are terminal wire
375
+ * replies from a subscriber; `missing`/`departed`/`timed_out` are publisher-computed
376
+ * from the presence census (a silent worker never sends them); `resyncing`/`recycling`/
377
+ * `stale` are the gap outcomes for a row that failed the ready+fresh gate — reported as
378
+ * its own actual condition rather than dropped (`resyncing` converges on its resync,
379
+ * `recycling` is departing, `stale` is a quiet row past the freshness bound carrying no
380
+ * convergence promise).
381
+ */
382
+ export const fleetOutcome = z.enum([
383
+ 'applied',
384
+ 'failed',
385
+ 'missing',
386
+ 'departed',
387
+ 'timed_out',
388
+ 'resyncing',
389
+ 'recycling',
390
+ 'stale',
391
+ ]);
352
392
  export type FleetOutcome = z.infer<typeof fleetOutcome>;
353
393
 
354
394
  /**
355
- * One origin's result within a {@link fleetResult}. `payload` carries query-op data
395
+ * One worker's result within a {@link fleetResult}. `payload` carries query-op data
356
396
  * (opaque to the UI); `error` carries a failed apply's message; `detail` carries the
357
- * publisher's report text for a computed `missing`/`departed`/`timed_out`. A drift
358
- * throws `ApiSchemaError`.
397
+ * publisher's report text for a computed gap outcome (`missing`/`departed`/`timed_out`/
398
+ * `resyncing`/`recycling`/`stale`). A drift throws `ApiSchemaError`.
359
399
  */
360
- export const fleetOriginResult = z.object({
361
- origin: z.string(),
400
+ export const fleetWorkerResult = z.object({
401
+ name: z.string(),
362
402
  outcome: fleetOutcome,
363
403
  payload: jsonValue,
364
404
  error: z.string().nullable(),
365
405
  detail: z.string().nullable(),
366
406
  });
367
- export type FleetOriginResult = z.infer<typeof fleetOriginResult>;
407
+ export type FleetWorkerResult = z.infer<typeof fleetWorkerResult>;
368
408
 
369
409
  /**
370
- * The awaited per-origin report of one fleet broadcast. Two honest shapes ride the
410
+ * The awaited per-worker report of one fleet broadcast. Two honest shapes ride the
371
411
  * same schema. Reachable (`reachable: true`): `results` holds one entry per expected
372
- * origin (the serving worker's own entry included), so an unconfirmed sibling is a
373
- * visible `failed`/`missing`/`departed`/`timed_out` entry, never a silent stale
374
- * worker. Bus-unreachable (`reachable: false`): the transport failed before any
375
- * origin could reply, so `results` is empty and only `error` is carried. This is the
376
- * bare body of `POST /api/fleet/reload-config` and the single-MCP reload; it is also
377
- * the body the mode-wrapped {@link fleetReportFanout} carries under its `fleet` /
378
- * `unreachable` modes.
412
+ * worker (the serving worker's own entry included) plus the gap rows, so an unconfirmed
413
+ * sibling is a visible `failed`/`missing`/`departed`/`timed_out`/`resyncing`/`recycling`/
414
+ * `stale` entry, never a silent stale worker. Bus-unreachable (`reachable: false`): the
415
+ * transport failed before any worker could reply, so `results` is empty and only `error`
416
+ * is carried. This is the bare body of `POST /api/fleet/reload-config` and the single-MCP
417
+ * reload; it is also the body the mode-wrapped {@link fleetReportFanout} carries under its
418
+ * `fleet` / `unreachable` modes.
379
419
  */
380
420
  export const fleetResult = z.object({
381
421
  op: z.string(),
382
422
  reachable: z.boolean(),
383
423
  local_only: z.boolean(),
384
- results: z.array(fleetOriginResult),
424
+ results: z.array(fleetWorkerResult),
385
425
  error: z.string().nullable(),
386
426
  });
387
427
  export type FleetResult = z.infer<typeof fleetResult>;
388
428
 
389
429
  /**
390
- * `POST /api/fleet/reload-config` — soft-restart the fleet and report per-origin. The
430
+ * `POST /api/fleet/reload-config` — soft-restart the fleet and report per-worker. The
391
431
  * serving worker applies its own reload first, then broadcasts; the returned report
392
- * names every origin's outcome. A failed local apply does not abort the broadcast, so
432
+ * names every worker's outcome. A failed local apply does not abort the broadcast, so
393
433
  * the report is embedded even on the convergence-failure path.
394
434
  */
395
435
  export const fleetReloadResult = fleetResult;
@@ -403,7 +443,7 @@ export type FleetReloadResult = z.infer<typeof fleetReloadResult>;
403
443
  * - `fleet` — a reachable multi-worker broadcast; carries the full {@link
404
444
  * fleetResult} so an unconfirmed sibling is visible.
405
445
  * - `unreachable` — the bus itself could not be reached; carries the
406
- * bus-unreachable {@link fleetResult} (no origin list, only `error`).
446
+ * bus-unreachable {@link fleetResult} (no worker list, only `error`).
407
447
  *
408
448
  * Every mutation whose response embeds this parses it EXPLICITLY: zod's default
409
449
  * unknown-key stripping would otherwise silently drop the field, hiding a failed
@@ -501,7 +541,7 @@ export const mcpStatus = z.object({
501
541
 
502
542
  /**
503
543
  * `POST /api/mcp-status/{title}/reload` — re-probe a single MCP server and broadcast
504
- * the reload to the fleet. The response is the bare per-origin {@link fleetResult}
544
+ * the reload to the fleet. The response is the bare per-worker {@link fleetResult}
505
545
  * (the serving worker's re-probe rides its own `results` entry as the payload); there
506
546
  * is no local/workers split. A drift throws `ApiSchemaError`.
507
547
  */
@@ -615,16 +655,28 @@ export type SettingsProfileDiff = z.infer<typeof settingsProfileDiff>;
615
655
  * `POST /api/config/profiles/{name}/apply` — the dedicated `profile_apply_response`
616
656
  * (action=fenced, destructive). NAMES-ONLY (no env value in any
617
657
  * section): `hot` is the hot-swapped key names; `recycle` is the orchestrator's
618
- * per-origin section — the applier's OWN entry carries the `self-deferred` status
619
- * literal when the diff has serve-affecting recycle keys; `refused` is `[]` on
620
- * success by construction (populated `{key, reason}` only on a refusal), the
621
- * populated refusal surfaces being the diff preview and the refusal error report.
622
- * `fanout` is the mode-wrapped fleet broadcast, parsed EXPLICITLY so a failed
623
- * propagation reaches the shared fleet-report handler. A drift throws `ApiSchemaError`.
658
+ * per-worker section — one `{name, kind, status, generation_before}` row per recycled
659
+ * target, `generation_before` the life that was recycled, the applier's OWN row carrying
660
+ * the `self-deferred` status literal when the diff has serve-affecting recycle keys;
661
+ * `fresh` is the per-kind list of NEW ready lives observed since the pre-apply snapshot
662
+ * (each `{name, kind, generation}`), evidence of fresh capacity and never claimed as any
663
+ * target's successor; `refused` is `[]` on success by construction (populated
664
+ * `{key, reason}` only on a refusal), the populated refusal surfaces being the diff
665
+ * preview and the refusal error report. `fanout` is the mode-wrapped fleet broadcast,
666
+ * parsed EXPLICITLY so a failed propagation reaches the shared fleet-report handler. A
667
+ * drift throws `ApiSchemaError`.
624
668
  */
625
669
  export const profileApplyResponse = z.object({
626
670
  hot: z.array(z.string()),
627
- recycle: z.array(z.object({ origin: z.string(), kind: z.string(), status: z.string() })),
671
+ recycle: z.array(
672
+ z.object({
673
+ name: z.string(),
674
+ kind: z.string(),
675
+ status: z.string(),
676
+ generation_before: z.number(),
677
+ }),
678
+ ),
679
+ fresh: z.array(z.object({ name: z.string(), kind: z.string(), generation: z.number() })),
628
680
  refused: z.array(z.object({ key: z.string(), reason: z.string() })),
629
681
  fanout: fleetReportFanout,
630
682
  });
@@ -900,6 +952,184 @@ export const channels = z.object({
900
952
  });
901
953
  export type Channels = z.infer<typeof channels>;
902
954
 
955
+ // -- conversations -----------------------------------------------------------
956
+ // The cross-channel conversation monitor's READ side: the stored routing rows
957
+ // (the route picker) from `GET /api/conversations`, a route's threads from
958
+ // `GET /api/conversations/{route}/threads`, and one thread's transcript from
959
+ // `GET /api/conversations/{route}/transcript?thread_id=`. Timestamps are float
960
+ // epoch seconds, not ISO strings, and both paged doors carry the same window
961
+ // fields; the transcript adds the direction it was read in.
962
+
963
+ /** The inbound door a route is bound to: the authed API, or a channel medium. */
964
+ export const conversationDoor = z.enum(['api', 'channel']);
965
+ export type ConversationDoor = z.infer<typeof conversationDoor>;
966
+
967
+ /** What a route's turn runs: a registered agent, or a tool dispatch. */
968
+ export const conversationTargetKind = z.enum(['agent', 'tool']);
969
+ export type ConversationTargetKind = z.infer<typeof conversationTargetKind>;
970
+
971
+ /**
972
+ * Where a record sits between intake and a terminal outcome. `failed` is the one
973
+ * the monitor surfaces loudly; `shed` and `silent` are terminal by design and
974
+ * never send.
975
+ */
976
+ export const conversationDeliveryStatus = z.enum([
977
+ 'accepted',
978
+ 'pending_delivery',
979
+ 'provisional',
980
+ 'delivered',
981
+ 'failed',
982
+ 'shed',
983
+ 'silent',
984
+ ]);
985
+ export type ConversationDeliveryStatus = z.infer<typeof conversationDeliveryStatus>;
986
+
987
+ /** The nature of a turn's outcome, orthogonal to where its delivery stands. */
988
+ export const conversationAnswerStatus = z.enum(['answered', 'error', 'silent']);
989
+ export type ConversationAnswerStatus = z.infer<typeof conversationAnswerStatus>;
990
+
991
+ /** A stored routing row as a read door returns it, its `callback_secret` withheld. */
992
+ export const conversationRoute = z.object({
993
+ route_name: z.string(),
994
+ door: conversationDoor,
995
+ target_kind: conversationTargetKind,
996
+ target_name: z.string(),
997
+ payload_expr: z.string().nullable(),
998
+ reply_expr: z.string().nullable(),
999
+ execution_key: z.string(),
1000
+ channel: z.string().nullable(),
1001
+ our_identity: z.string().nullable(),
1002
+ callback_url: z.string().nullable(),
1003
+ execution_key_fingerprint: z.string(),
1004
+ });
1005
+ export type ConversationRoute = z.infer<typeof conversationRoute>;
1006
+
1007
+ export const conversationRoutes = z.object({
1008
+ items: z.array(conversationRoute),
1009
+ total: z.number(),
1010
+ });
1011
+ export type ConversationRoutes = z.infer<typeof conversationRoutes>;
1012
+
1013
+ /**
1014
+ * One thread of a route, summarized from its newest record. A `thread_id` holds
1015
+ * an api-door address (`{principal}/{end user}`, percent-encoded), so it never
1016
+ * goes back into a URL as a path segment — the transcript door takes it as a
1017
+ * query value.
1018
+ */
1019
+ export const conversationThread = z.object({
1020
+ thread_id: z.string(),
1021
+ client_address: z.string(),
1022
+ last_activity_at: z.number(),
1023
+ message_count: z.number(),
1024
+ last_delivery_status: conversationDeliveryStatus,
1025
+ });
1026
+ export type ConversationThread = z.infer<typeof conversationThread>;
1027
+
1028
+ /** The page window both thread read doors carry; `next_page` is null on the last. */
1029
+ const conversationPageWindow = {
1030
+ total: z.number(),
1031
+ page: z.number(),
1032
+ page_size: z.number(),
1033
+ next_page: z.number().nullable(),
1034
+ };
1035
+
1036
+ export const conversationThreadsPage = z.object({
1037
+ items: z.array(conversationThread),
1038
+ ...conversationPageWindow,
1039
+ });
1040
+ export type ConversationThreadsPage = z.infer<typeof conversationThreadsPage>;
1041
+
1042
+ /**
1043
+ * One exchange: the visitor's `inbound_text` — every record answers a message, so
1044
+ * both projections always carry it — and the agent's `answer`, plus where that
1045
+ * answer's delivery stands. The fields after `updated_at` are ADMIN ONLY —
1046
+ * the same door serves a caller-scoped projection that withholds them — so they
1047
+ * are optional here and absence means "not published to this reader".
1048
+ */
1049
+ export const conversationMessage = z.object({
1050
+ message_id: z.string(),
1051
+ route_name: z.string(),
1052
+ door: conversationDoor,
1053
+ thread_id: z.string(),
1054
+ client_address: z.string(),
1055
+ caller_principal: z.string().nullable(),
1056
+ inbound_text: z.string(),
1057
+ answer_status: conversationAnswerStatus.nullable(),
1058
+ answer: z.string().nullable(),
1059
+ delivery_status: conversationDeliveryStatus,
1060
+ created_at: z.number(),
1061
+ updated_at: z.number(),
1062
+ channel: z.string().nullable().optional(),
1063
+ our_identity: z.string().nullable().optional(),
1064
+ provider_message_id: z.string().nullable().optional(),
1065
+ callback_url: z.string().nullable().optional(),
1066
+ error: z.string().nullable().optional(),
1067
+ outbound_message_ids: z.array(z.string()).optional(),
1068
+ attempts: z.number().optional(),
1069
+ });
1070
+ export type ConversationMessage = z.infer<typeof conversationMessage>;
1071
+
1072
+ /**
1073
+ * The direction a transcript page reads in. `asc` is the reading order (oldest
1074
+ * first); `desc` is the live-tail order, where page 1 is always the newest page
1075
+ * and paging forward walks BACKWARDS through the thread's history.
1076
+ */
1077
+ export const transcriptOrder = z.enum(['asc', 'desc']);
1078
+ export type TranscriptOrder = z.infer<typeof transcriptOrder>;
1079
+
1080
+ /** A transcript page, echoing the order it was read in. */
1081
+ export const conversationTranscriptPage = z.object({
1082
+ items: z.array(conversationMessage),
1083
+ ...conversationPageWindow,
1084
+ order: transcriptOrder,
1085
+ });
1086
+ export type ConversationTranscriptPage = z.infer<typeof conversationTranscriptPage>;
1087
+
1088
+ // -- channel-web entry gate --------------------------------------------------
1089
+ // The web channel's entry gate: a web route can be gated so its chat page is
1090
+ // served only to a navigation carrying a live entry code. These four doors are
1091
+ // keyed by the route's `our_identity`. A raw code exists only in flight (the
1092
+ // mint reply); a listed code carries its hash id, never a raw value (none is
1093
+ // stored).
1094
+
1095
+ /** One minted entry code as the gate read door lists it — its hash id, its
1096
+ * optional label, and when it was created / dies. Never a raw code. */
1097
+ export const webEntryCode = z.object({
1098
+ code_id: z.string(),
1099
+ label: z.string().nullable(),
1100
+ created_at: z.string(),
1101
+ expires_at: z.string().nullable(),
1102
+ });
1103
+ export type WebEntryCode = z.infer<typeof webEntryCode>;
1104
+
1105
+ /** `GET /api/channels/web/gates/{identity}` — the gate flag and its live codes. */
1106
+ export const webEntryGate = z.object({
1107
+ enabled: z.boolean(),
1108
+ codes: z.array(webEntryCode),
1109
+ });
1110
+ export type WebEntryGate = z.infer<typeof webEntryGate>;
1111
+
1112
+ /** `PUT /api/channels/web/gates/{identity}` — the gate flag after the write. */
1113
+ export const webEntryGateState = z.object({ enabled: z.boolean() });
1114
+ export type WebEntryGateState = z.infer<typeof webEntryGateState>;
1115
+
1116
+ /**
1117
+ * `POST /api/channels/web/gates/{identity}/codes` — a freshly minted code. The
1118
+ * raw `code` rides this reply ONCE (nothing else stores or lists it — a listed
1119
+ * code's raw value is unrecoverable by design); `code_id` is its hash id and
1120
+ * `expires_at` an ISO-8601 instant, or `null` for a code that never expires.
1121
+ */
1122
+ export const webEntryCodeMinted = z.object({
1123
+ code: z.string(),
1124
+ code_id: z.string(),
1125
+ expires_at: z.string().nullable(),
1126
+ });
1127
+ export type WebEntryCodeMinted = z.infer<typeof webEntryCodeMinted>;
1128
+
1129
+ /** `DELETE /api/channels/web/gates/{identity}/codes/{code_id}` — the revoked code. */
1130
+ export const webEntryCodeRevoked = z.object({ status: z.literal('revoked') });
1131
+ export type WebEntryCodeRevoked = z.infer<typeof webEntryCodeRevoked>;
1132
+
903
1133
  // -- notifications -----------------------------------------------------------
904
1134
  // The internal notifications sink: the messages the `notify_user` operation records
905
1135
  // with no channel, so they land only in the Studio inbox. Each record carries the