@tai42/api-client 1.0.1 → 1.2.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).
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).
350
381
  */
351
- export const fleetOutcome = z.enum(['applied', 'failed', 'missing', 'departed', 'timed_out']);
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
  */
@@ -549,6 +589,123 @@ export const configMode = z.object({
549
589
  read_only: z.boolean().default(false),
550
590
  });
551
591
 
592
+ // -- settings profiles -------------------------------------------------------
593
+ // The admin-only `/api/config/profiles/*` surface. Secrets ride the wire in the
594
+ // clear (secret/fenced routes); masking is CLIENT-SIDE per the secret-mask union. A drift
595
+ // on any of these throws `ApiSchemaError`.
596
+
597
+ /**
598
+ * `GET /api/config/profiles` — one row per stored profile. The list is
599
+ * names+descriptions ONLY (action=read); no `env`, no `secret_keys`, no bodies.
600
+ * (No `last_modified`: the list carries identity, not history — version metadata
601
+ * is the `/versions` surface — so the list renders no last-modified column.)
602
+ */
603
+ export const settingsProfileSummary = z.object({
604
+ name: z.string(),
605
+ description: z.string(),
606
+ });
607
+ export type SettingsProfileSummary = z.infer<typeof settingsProfileSummary>;
608
+ export const settingsProfileList = z.array(settingsProfileSummary);
609
+
610
+ /**
611
+ * `GET /api/config/profiles/{name}` and each version body — the stored profile
612
+ * document (action=secret). `env` carries REAL values; `secret_keys` is the
613
+ * per-profile secret-marks band folded into the secret-mask union for masking.
614
+ */
615
+ export const settingsProfileBody = z.object({
616
+ description: z.string(),
617
+ env: z.record(z.string(), z.string()),
618
+ secret_keys: z.array(z.string()),
619
+ });
620
+ export type SettingsProfileBody = z.infer<typeof settingsProfileBody>;
621
+
622
+ /** `PUT /api/config/profiles/{name}` — a minimal save ack; `version` is the new store version. */
623
+ export const settingsProfileSaved = z.object({
624
+ ok: z.literal(true),
625
+ version: z.number().optional(),
626
+ });
627
+ export type SettingsProfileSaved = z.infer<typeof settingsProfileSaved>;
628
+
629
+ /** `DELETE /api/config/profiles/{name}` — a minimal removal ack. */
630
+ export const settingsProfileDeleted = z.object({ ok: z.literal(true) });
631
+
632
+ /** `POST /api/config/profiles/{name}/rollback` — the restore ack; `version` is the resulting active version. */
633
+ export const settingsProfileRollback = z.object({
634
+ ok: z.literal(true),
635
+ version: z.number(),
636
+ });
637
+ export type SettingsProfileRollback = z.infer<typeof settingsProfileRollback>;
638
+
639
+ /**
640
+ * `POST /api/config/profiles/{name}/diff` — the SAVED profile vs the stored env
641
+ * (action=secret). `changed` carries real `old`/`new` values (UI masks via
642
+ * JsonDiff); `recycle_keys`/`refused_keys` are the diff's recycle-class and
643
+ * boundary-refused key names, called out in the UI before apply.
644
+ */
645
+ export const settingsProfileDiff = z.object({
646
+ added: z.array(z.string()),
647
+ removed: z.array(z.string()),
648
+ changed: z.array(z.object({ key: z.string(), old: z.string(), new: z.string() })),
649
+ recycle_keys: z.array(z.string()),
650
+ refused_keys: z.array(z.string()),
651
+ });
652
+ export type SettingsProfileDiff = z.infer<typeof settingsProfileDiff>;
653
+
654
+ /**
655
+ * `POST /api/config/profiles/{name}/apply` — the dedicated `profile_apply_response`
656
+ * (action=fenced, destructive). NAMES-ONLY (no env value in any
657
+ * section): `hot` is the hot-swapped key names; `recycle` is the orchestrator's
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`.
668
+ */
669
+ export const profileApplyResponse = z.object({
670
+ hot: z.array(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() })),
680
+ refused: z.array(z.object({ key: z.string(), reason: z.string() })),
681
+ fanout: fleetReportFanout,
682
+ });
683
+ export type ProfileApplyResponse = z.infer<typeof profileApplyResponse>;
684
+
685
+ /**
686
+ * `GET /api/config/profiles/{name}/versions` — version METADATA only (action=read;
687
+ * no bodies), the generic versioned-store row minus `body`. `is_current` flags the
688
+ * active pointer.
689
+ */
690
+ export const settingsProfileVersionMeta = z.object({
691
+ version: z.number(),
692
+ tags: z.array(z.string()),
693
+ created_at: z.string(),
694
+ is_current: z.boolean(),
695
+ });
696
+ export type SettingsProfileVersionMeta = z.infer<typeof settingsProfileVersionMeta>;
697
+ export const settingsProfileVersionList = z.array(settingsProfileVersionMeta);
698
+
699
+ /**
700
+ * `GET /api/config/profiles/{name}/versions/{version}` — one immutable version WITH
701
+ * its `body` (action=secret; body rendered masked per the secret-mask union). The generic
702
+ * versioned-store row shape.
703
+ */
704
+ export const settingsProfileVersion = settingsProfileVersionMeta.extend({
705
+ body: settingsProfileBody,
706
+ });
707
+ export type SettingsProfileVersion = z.infer<typeof settingsProfileVersion>;
708
+
552
709
  // -- connectors --------------------------------------------------------------
553
710
 
554
711
  export const subServiceView = z.object({
@@ -795,6 +952,139 @@ export const channels = z.object({
795
952
  });
796
953
  export type Channels = z.infer<typeof channels>;
797
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
+
798
1088
  // -- notifications -----------------------------------------------------------
799
1089
  // The internal notifications sink: the messages the `notify_user` operation records
800
1090
  // with no channel, so they land only in the Studio inbox. Each record carries the