@tai42/api-client 0.11.0 → 0.13.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
@@ -28,21 +28,15 @@ export const allToolSchemas = z.record(z.string(), toolSchema);
28
28
  export const runToolResult = jsonValue;
29
29
 
30
30
  /**
31
- * The MEDIA variant a direct tool run (`/api/run-tool`) can return:
32
- * fastmcp serializes a returned `Image`/`Audio` MediaBlock to the MCP content
33
- * object `{ type: 'image' | 'audio', data: <base64>, mimeType: <mime> }`. The
34
- * `ResultViewer` detects this shape and renders an `<img>`/`<audio>` from a
35
- * `data:` URI. A tool result is still arbitrary JSON (`runToolResult`); this is
36
- * one recognised shape it can take, kept as its own schema so the viewer's
37
- * detection is a schema parse, not an ad-hoc shape check.
31
+ * The MEDIA content shape a direct tool run may return — fastmcp's serialized
32
+ * `Image`/`Audio` MediaBlock (`{ type, data: <base64>, mimeType }`). One recognised
33
+ * shape of the otherwise-arbitrary `runToolResult`, kept as its own schema so the
34
+ * viewer detects media by parse, not by ad-hoc shape check.
38
35
  *
39
- * SECURITY: the refinement pins `mimeType` to its media kind — an `image` block
40
- * MUST carry an `image/*` type, an `audio` block an `audio/*` type. A drifting or
41
- * arbitrary mimeType (e.g. a script type) fails the parse, so the viewer never
42
- * turns an unexpected mime into a `data:` URI — it falls back to the JSON/text
43
- * view instead. A `File` MediaBlock serializes to a distinct `EmbeddedResource`
44
- * shape (no top-level `data`/`mimeType`), so it also fails here and is not
45
- * media-rendered.
36
+ * SECURITY: the refinement pins `mimeType` to its kind — `image` MUST be `image/*`,
37
+ * `audio` MUST be `audio/*` — so a drifting/arbitrary mime fails the parse and is
38
+ * never turned into a `data:` URI (a `File` MediaBlock's distinct `EmbeddedResource`
39
+ * shape also fails here, by design).
46
40
  */
47
41
  export const toolMediaResult = z
48
42
  .object({
@@ -424,8 +418,36 @@ export type FleetReportFanout = z.infer<typeof fleetReportFanout>;
424
418
 
425
419
  // -- manifest / mcp ----------------------------------------------------------
426
420
 
421
+ /**
422
+ * The back-reference a connector writes onto a manifest MCP entry to mark it
423
+ * connector-owned: the connection and the provider sub-service that own it.
424
+ * Hand-authored entries carry no `managed` marker.
425
+ */
426
+ export const connectorRef = z.object({
427
+ connection_id: z.string(),
428
+ provider_id: z.string(),
429
+ sub_service: z.string(),
430
+ });
431
+ export type ConnectorRef = z.infer<typeof connectorRef>;
432
+
433
+ /**
434
+ * One entry of the live manifest's MCP section. Only the `managed` provenance
435
+ * marker is typed — the connect/disconnect flows write it server-side to point a
436
+ * connector-owned entry back at its connection, and the editor renders such
437
+ * entries read-only. `.nullish()` because a hand-authored entry carries `null`
438
+ * (present) or omits the key. Every other field (title, config, extensions) rides
439
+ * through `.passthrough()` unmodeled so the config editor keeps editing the whole
440
+ * entry — narrowing it to a typed subset would silently strip the fields it saves.
441
+ */
442
+ export const manifestMcpEntry = z
443
+ .object({
444
+ managed: connectorRef.nullish(),
445
+ })
446
+ .passthrough();
447
+ export type ManifestMcpEntry = z.infer<typeof manifestMcpEntry>;
448
+
427
449
  export const manifestView = z.object({
428
- mcp: z.array(z.record(z.string(), z.unknown())),
450
+ mcp: z.array(manifestMcpEntry),
429
451
  user_tools: z.array(z.string()),
430
452
  });
431
453
  /**
@@ -488,8 +510,26 @@ export type McpReloadResult = z.infer<typeof mcpReloadResult>;
488
510
 
489
511
  // -- sub-mcp -----------------------------------------------------------------
490
512
 
491
- export const subMcpList = z.record(z.string(), z.record(z.string(), z.unknown()));
492
- export const subMcpCreated = z.object({ slug: z.string(), tools: z.array(z.string()) });
513
+ /**
514
+ * One registered sub-MCP mount as it rides the registry list and the projection:
515
+ * the tools it exposes and the transport it serves on. In `GET /api/sub-mcp` the
516
+ * slug is the map key, so the mount value carries no slug; {@link subMcpEntry}
517
+ * extends this with `slug` for the capability projection where mounts are a flat
518
+ * list. `transport` is always present on the wire (the server defaults it to
519
+ * `http`), so it is required — its absence is a drift, not a legal shape.
520
+ */
521
+ export const subMcpMount = z.object({
522
+ tools: z.array(z.string()),
523
+ transport: z.string(),
524
+ });
525
+ export type SubMcpMount = z.infer<typeof subMcpMount>;
526
+
527
+ export const subMcpList = z.record(z.string(), subMcpMount);
528
+ export const subMcpCreated = z.object({
529
+ slug: z.string(),
530
+ tools: z.array(z.string()),
531
+ transport: z.string(),
532
+ });
493
533
  export const subMcpRemoved = z.object({ slug: z.string(), removed: z.literal(true) });
494
534
 
495
535
  // -- config ------------------------------------------------------------------
@@ -500,12 +540,9 @@ export const envConfig = z.object({
500
540
  });
501
541
  /**
502
542
  * `GET /api/config/mode` — the active config backend mode (`file` / `k8s`). The
503
- * skeleton emits ONLY `config_mode`; `read_only` is a UI-side gate that no config
504
- * backend currently drives (both the `file` and `k8s` providers implement writes),
505
- * so it is tolerated-absent and defaults to `false` (writable). A deployment whose
506
- * config is genuinely read-only can still opt in by emitting `read_only: true`,
507
- * which the settings surface honors — but its absence is the normal case, not a
508
- * drift, so requiring it here would make every real `getConfigMode()` throw.
543
+ * skeleton emits only `config_mode`; `read_only` is a UI-side gate, tolerated-absent
544
+ * and defaulting to `false` (writable) so a normal response never drifts, while a
545
+ * genuinely read-only deployment can opt in by emitting `read_only: true`.
509
546
  */
510
547
  export const configMode = z.object({
511
548
  config_mode: z.string(),
@@ -540,9 +577,31 @@ export const providerView = z.object({
540
577
  sub_services: z.array(subServiceView),
541
578
  config_fields: z.array(configFieldView),
542
579
  });
543
- export const providers = z.array(providerView);
544
580
  export type ProviderView = z.infer<typeof providerView>;
545
581
 
582
+ /**
583
+ * One `connector_category` grouping row served alongside the catalog: the UI's
584
+ * label and sort key for the providers it groups, so the list can group, order,
585
+ * and label providers by category without a second read.
586
+ */
587
+ export const connectorCategoryView = z.object({
588
+ id: z.string(),
589
+ display_name: z.string(),
590
+ sort_order: z.number(),
591
+ });
592
+ export type ConnectorCategoryView = z.infer<typeof connectorCategoryView>;
593
+
594
+ /**
595
+ * `GET /api/connectors/providers` — the provider catalog: the providers plus the
596
+ * category groupings the UI arranges them under. Reshaped from a bare provider
597
+ * array so the category label/order surface rides the same read.
598
+ */
599
+ export const providers = z.object({
600
+ providers: z.array(providerView),
601
+ categories: z.array(connectorCategoryView),
602
+ });
603
+ export type ProviderCatalogResponse = z.infer<typeof providers>;
604
+
546
605
  export const connectionView = z.object({
547
606
  connection_id: z.string(),
548
607
  provider_id: z.string(),
@@ -551,12 +610,26 @@ export const connectionView = z.object({
551
610
  account_identity: z.string().nullable(),
552
611
  enabled_sub_services: z.array(z.string()),
553
612
  granted_scopes: z.array(z.string()),
613
+ // Sub-services whose MCP server did not answer a live reachability probe.
614
+ // Populated only on the single-connection GET (the list projection is
615
+ // probe-free and always sends `[]`). Reachability is distinct from
616
+ // `auth_health_state`: a healthy connection can still have a down sub-service.
617
+ // Defaulted so a projection that omits it (an older worker mid-rollout) still
618
+ // parses rather than false-failing the whole view.
619
+ unreachable_sub_services: z.array(z.string()).default([]),
554
620
  auth_health_state: z.enum(['healthy', 'reconnect_required', 'refresh_failing']),
555
621
  created_at: z.string(),
556
622
  });
557
623
  export type ConnectionView = z.infer<typeof connectionView>;
558
624
 
559
- export const connectionsList = z.object({ items: z.array(connectionView), total: z.number() });
625
+ export const connectionsList = z.object({
626
+ items: z.array(connectionView),
627
+ total: z.number(),
628
+ // Count of connections whose `auth_health_state` is not healthy across the whole
629
+ // set (independent of any filter/limit) — the attention-badge count. Optional:
630
+ // no UI consumes it this mission, and an older worker mid-rollout omits it.
631
+ unhealthy: z.number().optional(),
632
+ });
560
633
 
561
634
  // A connector mutation that writes the manifest embeds the mode-wrapped fleet
562
635
  // `fanout` of the reload it broadcast; it is `null` when the response path mutated no
@@ -1113,12 +1186,12 @@ export const patternEntry = z.object({
1113
1186
  });
1114
1187
  export type PatternEntry = z.infer<typeof patternEntry>;
1115
1188
 
1116
- /** A sub-MCP mount the caller can reach, with the tools it exposes. */
1117
- export const subMcpEntry = z.object({
1118
- slug: z.string(),
1119
- tools: z.array(z.string()),
1120
- transport: z.string(),
1121
- });
1189
+ /**
1190
+ * A sub-MCP mount the caller can reach, with the tools it exposes and its
1191
+ * transport. Shares the {@link subMcpMount} shape with the registry list, adding
1192
+ * the `slug` the projection carries inline (the list keys mounts by slug instead).
1193
+ */
1194
+ export const subMcpEntry = subMcpMount.extend({ slug: z.string() });
1122
1195
  export type SubMcpEntry = z.infer<typeof subMcpEntry>;
1123
1196
 
1124
1197
  /**