@tai42/api-client 1.1.0 → 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/dist/client.d.ts +137 -51
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +27 -2
- package/dist/client.js.map +1 -1
- package/dist/fleet-report.d.ts +12 -12
- package/dist/fleet-report.d.ts.map +1 -1
- package/dist/fleet-report.js +2 -2
- package/dist/fleet-report.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +925 -364
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +195 -40
- package/dist/schemas.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +54 -2
- package/src/fleet-report.ts +14 -14
- package/src/index.ts +18 -4
- package/src/schemas.ts +227 -42
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -223,6 +223,20 @@ export interface StorageUploadBody {
|
|
|
223
223
|
readonly content_base64?: string;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* One transcript read (GET `/api/conversations/{route}/transcript` query params).
|
|
228
|
+
* `order` is not a default the caller may skip: `asc` reads the thread from its
|
|
229
|
+
* beginning and `desc` from its newest end, and which one a screen wants is a
|
|
230
|
+
* decision that screen must make.
|
|
231
|
+
*/
|
|
232
|
+
export interface ConversationTranscriptQuery {
|
|
233
|
+
readonly routeName: string;
|
|
234
|
+
readonly threadId: string;
|
|
235
|
+
readonly page: number;
|
|
236
|
+
readonly pageSize: number;
|
|
237
|
+
readonly order: s.TranscriptOrder;
|
|
238
|
+
}
|
|
239
|
+
|
|
226
240
|
/** The observability-metrics window + bucket size (GET query params). */
|
|
227
241
|
export interface MetricsQuery {
|
|
228
242
|
readonly from?: string;
|
|
@@ -535,14 +549,14 @@ export function createApiClient(config: ApiConfig) {
|
|
|
535
549
|
// the FLEET doors, which ride the app's worker bus and need no backend at all.
|
|
536
550
|
// `getBackendInfo` reports `present: false` (a 200 empty state) when no backend
|
|
537
551
|
// plugin is registered. `listFleetWorkers` reads the live bus presence census
|
|
538
|
-
// (every subscribed
|
|
552
|
+
// (every subscribed worker, ASGI + backend-runtime) and never fabricates an empty
|
|
539
553
|
// fleet — a census read that fails surfaces as a loud 500.
|
|
540
554
|
getBackendInfo: (signal?: AbortSignal) => req('/api/backend', s.backendInfo, { signal }),
|
|
541
555
|
listFleetWorkers: (signal?: AbortSignal) =>
|
|
542
556
|
req('/api/fleet/workers', s.fleetWorkers, { signal }),
|
|
543
557
|
// Soft-restart the fleet: `targets` names the workers to reload, or `null` for
|
|
544
558
|
// the whole fleet. The serving worker applies locally then broadcasts; a 200
|
|
545
|
-
// response carries the per-
|
|
559
|
+
// response carries the per-worker fleet report, so workers that did not converge
|
|
546
560
|
// are named, not hidden. A failed local apply instead re-raises as a loud error
|
|
547
561
|
// (the transport throws with the failure message), not a returned report.
|
|
548
562
|
reloadFleetConfig: (targets: string[] | null) =>
|
|
@@ -709,6 +723,44 @@ export function createApiClient(config: ApiConfig) {
|
|
|
709
723
|
// The installed channel-plugin names (delivery media for ask_user questions).
|
|
710
724
|
listChannels: (signal?: AbortSignal) => req('/api/channels', s.channels, { signal }),
|
|
711
725
|
|
|
726
|
+
// -- conversations ---------------------------------------------------------
|
|
727
|
+
// The stored routing rows, each with its `callback_secret` withheld — the
|
|
728
|
+
// conversation monitor's route picker.
|
|
729
|
+
listConversationRoutes: (signal?: AbortSignal) =>
|
|
730
|
+
req('/api/conversations', s.conversationRoutes, { signal }),
|
|
731
|
+
// One route's threads, newest activity first. Admin-only server-side: a
|
|
732
|
+
// listing spans every caller on the route, so a scoped session gets a 403.
|
|
733
|
+
listConversationThreads: (
|
|
734
|
+
routeName: string,
|
|
735
|
+
page: number,
|
|
736
|
+
pageSize: number,
|
|
737
|
+
signal?: AbortSignal,
|
|
738
|
+
) =>
|
|
739
|
+
req(`/api/conversations/${encodeSegment(routeName)}/threads`, s.conversationThreadsPage, {
|
|
740
|
+
signal,
|
|
741
|
+
query: { page, pageSize },
|
|
742
|
+
}),
|
|
743
|
+
// One thread's transcript, caller-scoped, read in the asked-for direction.
|
|
744
|
+
// The `thread_id` rides the QUERY STRING, never the path: it embeds the api
|
|
745
|
+
// door's `{principal}/{end user}` address with the principal already
|
|
746
|
+
// percent-encoded, which a path segment would have to encode a second time
|
|
747
|
+
// and the server would then decode once too few. A query value is decoded
|
|
748
|
+
// exactly once whatever it holds, so it is passed through verbatim.
|
|
749
|
+
readConversationTranscript: (query: ConversationTranscriptQuery, signal?: AbortSignal) =>
|
|
750
|
+
req(
|
|
751
|
+
`/api/conversations/${encodeSegment(query.routeName)}/transcript`,
|
|
752
|
+
s.conversationTranscriptPage,
|
|
753
|
+
{
|
|
754
|
+
signal,
|
|
755
|
+
query: {
|
|
756
|
+
thread_id: query.threadId,
|
|
757
|
+
page: query.page,
|
|
758
|
+
pageSize: query.pageSize,
|
|
759
|
+
order: query.order,
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
),
|
|
763
|
+
|
|
712
764
|
// -- notifications ---------------------------------------------------------
|
|
713
765
|
// The internal notifications sink, newest-first — the messages the `notify_user`
|
|
714
766
|
// operation records with no channel, so they surface only in this inbox.
|
package/src/fleet-report.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* fleet broadcast. Every mutation surface (connector, backup, mcp-config, env,
|
|
4
4
|
* extensions, single-MCP reload, fleet reload) routes its broadcast report through
|
|
5
5
|
* this one function so a FAILED propagation is never invisible: an unconfirmed
|
|
6
|
-
* sibling (`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* sibling (any non-`applied` outcome) or an unreachable bus becomes an explicit
|
|
7
|
+
* summary the UI renders as a loud, user-visible state — never a faked success on a
|
|
8
|
+
* worker that did not converge.
|
|
9
9
|
*
|
|
10
10
|
* A mutation embeds its broadcast in one of two shapes: the mode-wrapped
|
|
11
11
|
* {@link FleetReportFanout} (`fanout` field) or the bare {@link FleetResult} (the
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { FleetOutcome, FleetReportFanout, FleetResult } from './schemas';
|
|
17
17
|
|
|
18
|
-
/** A per-
|
|
18
|
+
/** A per-worker propagation FAILURE: a worker that did not confirm `applied`. */
|
|
19
19
|
export type FleetFailureOutcome = Exclude<FleetOutcome, 'applied'>;
|
|
20
20
|
|
|
21
|
-
/** One
|
|
22
|
-
export interface
|
|
23
|
-
readonly
|
|
21
|
+
/** One worker that failed to converge, with the report's message for it. */
|
|
22
|
+
export interface FleetWorkerFailure {
|
|
23
|
+
readonly name: string;
|
|
24
24
|
readonly outcome: FleetFailureOutcome;
|
|
25
25
|
/** The failed apply's `error`, or the publisher's computed-outcome `detail`. */
|
|
26
26
|
readonly message: string | null;
|
|
@@ -28,10 +28,10 @@ export interface FleetOriginFailure {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* The interpreted state of one broadcast.
|
|
31
|
-
* - `converged` — the bus was reached and every
|
|
31
|
+
* - `converged` — the bus was reached and every worker applied (or the change was
|
|
32
32
|
* local-only on a lone worker). The caller renders its own success; the report
|
|
33
33
|
* needs no loud state.
|
|
34
|
-
* - `degraded` — the bus was reached but one or more
|
|
34
|
+
* - `degraded` — the bus was reached but one or more workers did not confirm; the
|
|
35
35
|
* named `failures` are stale/unreached siblings the operator must see.
|
|
36
36
|
* - `unreachable` — the bus itself could not be reached, so no sibling was told;
|
|
37
37
|
* `error` carries the transport failure.
|
|
@@ -42,8 +42,8 @@ export interface FleetReportSummary {
|
|
|
42
42
|
readonly status: FleetReportStatus;
|
|
43
43
|
/** The single-worker `local-only` note, when the broadcast reached no sibling. */
|
|
44
44
|
readonly note: string | null;
|
|
45
|
-
/** The
|
|
46
|
-
readonly failures: readonly
|
|
45
|
+
/** The workers that did not apply — empty unless `status` is `degraded`. */
|
|
46
|
+
readonly failures: readonly FleetWorkerFailure[];
|
|
47
47
|
/** The bus-unreachable transport error — non-null only when `unreachable`. */
|
|
48
48
|
readonly error: string | null;
|
|
49
49
|
}
|
|
@@ -56,16 +56,16 @@ export function isFleetReportFailure(summary: FleetReportSummary | null): boolea
|
|
|
56
56
|
/**
|
|
57
57
|
* Interpret a bare {@link FleetResult} (the fleet-reload / single-MCP-reload body).
|
|
58
58
|
* An unreachable bus is `unreachable`; a reachable report with any non-`applied`
|
|
59
|
-
*
|
|
59
|
+
* worker is `degraded` (those workers named); otherwise `converged`.
|
|
60
60
|
*/
|
|
61
61
|
export function summarizeFleetResult(result: FleetResult): FleetReportSummary {
|
|
62
62
|
if (!result.reachable) {
|
|
63
63
|
return { status: 'unreachable', note: null, failures: [], error: result.error };
|
|
64
64
|
}
|
|
65
|
-
const failures:
|
|
65
|
+
const failures: FleetWorkerFailure[] = result.results
|
|
66
66
|
.filter((entry) => entry.outcome !== 'applied')
|
|
67
67
|
.map((entry) => ({
|
|
68
|
-
|
|
68
|
+
name: entry.name,
|
|
69
69
|
outcome: entry.outcome as FleetFailureOutcome,
|
|
70
70
|
message: entry.error ?? entry.detail ?? null,
|
|
71
71
|
}));
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type {
|
|
|
24
24
|
ValidatePresetBody,
|
|
25
25
|
ToolMetaPatch,
|
|
26
26
|
StorageUploadBody,
|
|
27
|
+
ConversationTranscriptQuery,
|
|
27
28
|
MetricsQuery,
|
|
28
29
|
RunsQuery,
|
|
29
30
|
MarketplaceSearchQuery,
|
|
@@ -62,7 +63,7 @@ export { summarizeFleetResult, summarizeFleetFanout, isFleetReportFailure } from
|
|
|
62
63
|
export type {
|
|
63
64
|
FleetReportSummary,
|
|
64
65
|
FleetReportStatus,
|
|
65
|
-
|
|
66
|
+
FleetWorkerFailure,
|
|
66
67
|
FleetFailureOutcome,
|
|
67
68
|
} from './fleet-report';
|
|
68
69
|
export * as schemas from './schemas';
|
|
@@ -110,6 +111,17 @@ export type {
|
|
|
110
111
|
MediaKind,
|
|
111
112
|
AnswerFormat,
|
|
112
113
|
Channels,
|
|
114
|
+
ConversationDoor,
|
|
115
|
+
ConversationTargetKind,
|
|
116
|
+
ConversationDeliveryStatus,
|
|
117
|
+
ConversationAnswerStatus,
|
|
118
|
+
ConversationRoute,
|
|
119
|
+
ConversationRoutes,
|
|
120
|
+
ConversationThread,
|
|
121
|
+
ConversationThreadsPage,
|
|
122
|
+
ConversationMessage,
|
|
123
|
+
TranscriptOrder,
|
|
124
|
+
ConversationTranscriptPage,
|
|
113
125
|
Notification,
|
|
114
126
|
Notifications,
|
|
115
127
|
HookParams,
|
|
@@ -139,11 +151,13 @@ export type {
|
|
|
139
151
|
StorageInfo,
|
|
140
152
|
StorageResourceStat,
|
|
141
153
|
BackendInfo,
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
WorkerKind,
|
|
155
|
+
WorkerState,
|
|
156
|
+
WorkerLastOp,
|
|
157
|
+
FleetWorker,
|
|
144
158
|
FleetWorkers,
|
|
145
159
|
FleetOutcome,
|
|
146
|
-
|
|
160
|
+
FleetWorkerResult,
|
|
147
161
|
FleetResult,
|
|
148
162
|
FleetReloadResult,
|
|
149
163
|
FleetReportFanout,
|
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
|
|
323
|
-
export type
|
|
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
|
|
327
|
-
* `{kind}-{
|
|
328
|
-
*
|
|
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
|
|
331
|
-
|
|
332
|
-
kind:
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
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
|
|
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
|
|
361
|
-
|
|
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
|
|
407
|
+
export type FleetWorkerResult = z.infer<typeof fleetWorkerResult>;
|
|
368
408
|
|
|
369
409
|
/**
|
|
370
|
-
* The awaited per-
|
|
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
|
-
*
|
|
373
|
-
* visible `failed`/`missing`/`departed`/`timed_out
|
|
374
|
-
* worker. Bus-unreachable (`reachable: false`): the
|
|
375
|
-
*
|
|
376
|
-
* bare body of `POST /api/fleet/reload-config` and the single-MCP
|
|
377
|
-
* the body the mode-wrapped {@link fleetReportFanout} carries under its
|
|
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(
|
|
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-
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
* `
|
|
623
|
-
*
|
|
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(
|
|
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,139 @@ 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
|
+
|
|
903
1088
|
// -- notifications -----------------------------------------------------------
|
|
904
1089
|
// The internal notifications sink: the messages the `notify_user` operation records
|
|
905
1090
|
// with no channel, so they land only in the Studio inbox. Each record carries the
|