@tai42/api-client 2.0.0 → 4.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/client.ts CHANGED
@@ -213,12 +213,14 @@ export interface ValidatePresetBody {
213
213
  * untouched — there is no full-row replace, so a display-name+tags editor sends
214
214
  * ONLY those two keys with no risk to `folder_id`/`hidden`. At least one field must
215
215
  * be present. A blank/whitespace-only `display_name` is rejected by the API (422) —
216
- * callers map an emptied label to `null`, never `""`.
216
+ * callers map an emptied label to `null`, never `""`. A present `badges` array
217
+ * replaces the whole overlay set (INFORMATIONAL labels, never an enforced gate).
217
218
  */
218
219
  export interface ToolMetaPatch {
219
220
  readonly display_name?: string | null;
220
221
  readonly folder_id?: string | null;
221
222
  readonly tags?: readonly string[];
223
+ readonly badges?: readonly string[];
222
224
  readonly hidden?: boolean | null;
223
225
  }
224
226
 
@@ -234,11 +236,23 @@ export interface StorageUploadBody {
234
236
  readonly content_base64?: string;
235
237
  }
236
238
 
239
+ /**
240
+ * The thread-list filter set (GET `/api/conversations/{route}/threads` query
241
+ * params, alongside `page`/`pageSize`). `status` narrows to threads whose newest
242
+ * record sits in one delivery state; `address` is a SUBSTRING match over the
243
+ * client address. Both omitted when undefined — an unfiltered listing.
244
+ */
245
+ export interface ConversationThreadFilters {
246
+ readonly status?: s.ConversationDeliveryStatus;
247
+ readonly address?: string;
248
+ }
249
+
237
250
  /**
238
251
  * One transcript read (GET `/api/conversations/{route}/transcript` query params).
239
252
  * `order` is not a default the caller may skip: `asc` reads the thread from its
240
253
  * beginning and `desc` from its newest end, and which one a screen wants is a
241
- * decision that screen must make.
254
+ * decision that screen must make. `q` narrows the page to records whose text
255
+ * matches it (a SUBSTRING), omitted for an unfiltered read.
242
256
  */
243
257
  export interface ConversationTranscriptQuery {
244
258
  readonly routeName: string;
@@ -246,6 +260,20 @@ export interface ConversationTranscriptQuery {
246
260
  readonly page: number;
247
261
  readonly pageSize: number;
248
262
  readonly order: s.TranscriptOrder;
263
+ readonly q?: string;
264
+ }
265
+
266
+ /**
267
+ * A route-scoped message search (GET
268
+ * `/api/conversations/{route}/messages/search` query params). `q` is the required
269
+ * needle — a substring over record text, across every thread on the route; `page`
270
+ * is supplied per-request by the infinite query.
271
+ */
272
+ export interface ConversationMessageSearchQuery {
273
+ readonly routeName: string;
274
+ readonly q: string;
275
+ readonly page: number;
276
+ readonly pageSize: number;
249
277
  }
250
278
 
251
279
  /**
@@ -318,12 +346,31 @@ export interface MarketplaceSearchQuery {
318
346
  * the same transaction that writes the entry: `env` supplies the values,
319
347
  * `secret_keys` marks which land in the secret band. Both omitted for a plain
320
348
  * install (no required markers).
349
+ *
350
+ * `route_mounts` maps a route-carrying item name to the base prefix its routes
351
+ * mount under (the default or the operator's remap); `accept_public_routes`
352
+ * explicitly consents to the routes served without authentication. Omitted for a
353
+ * plugin that registers no routes.
321
354
  */
322
355
  export interface MarketplaceInstallBody {
323
356
  readonly ref: string;
324
357
  readonly version?: string;
325
358
  readonly env?: Record<string, string>;
326
359
  readonly secret_keys?: string[];
360
+ readonly route_mounts?: Record<string, string>;
361
+ readonly accept_public_routes?: boolean;
362
+ }
363
+
364
+ /**
365
+ * Body for previewing an install / update (`POST /api/marketplace/install/preview`):
366
+ * resolves the spec and computes the routes, collisions, and public rows WITHOUT
367
+ * side effects. `route_mounts` applies base overrides so the operator sees the
368
+ * effect of a remap before committing.
369
+ */
370
+ export interface MarketplaceInstallPreviewBody {
371
+ readonly ref: string;
372
+ readonly version?: string;
373
+ readonly route_mounts?: Record<string, string>;
327
374
  }
328
375
 
329
376
  /** Body for uninstalling a plugin. */
@@ -771,11 +818,12 @@ export function createApiClient(config: ApiConfig) {
771
818
  routeName: string,
772
819
  page: number,
773
820
  pageSize: number,
821
+ filters: ConversationThreadFilters = {},
774
822
  signal?: AbortSignal,
775
823
  ) =>
776
824
  req(`/api/conversations/${encodeSegment(routeName)}/threads`, s.conversationThreadsPage, {
777
825
  signal,
778
- query: { page, pageSize },
826
+ query: { page, pageSize, status: filters.status, address: filters.address },
779
827
  }),
780
828
  // One thread's transcript, caller-scoped, read in the asked-for direction.
781
829
  // The `thread_id` rides the QUERY STRING, never the path: it embeds the api
@@ -794,9 +842,19 @@ export function createApiClient(config: ApiConfig) {
794
842
  page: query.page,
795
843
  pageSize: query.pageSize,
796
844
  order: query.order,
845
+ q: query.q,
797
846
  },
798
847
  },
799
848
  ),
849
+ // Search every record on a route by text, across threads, newest first. The
850
+ // needle rides the QUERY (`q`); paging is the infinite query's own window. The
851
+ // page's `truncated` flag says the match set outran the door's scan cap.
852
+ searchConversationMessages: (query: ConversationMessageSearchQuery, signal?: AbortSignal) =>
853
+ req(
854
+ `/api/conversations/${encodeSegment(query.routeName)}/messages/search`,
855
+ s.conversationMessageSearchPage,
856
+ { signal, query: { q: query.q, page: query.page, pageSize: query.pageSize } },
857
+ ),
800
858
  // Send a human message into a thread. The `thread_id` rides the BODY (a POST,
801
859
  // never a path segment) — same encode-once reasoning as the transcript read.
802
860
  sendConversationThreadMessage: (routeName: string, body: ConversationThreadMessageBody) =>
@@ -1167,6 +1225,15 @@ export function createApiClient(config: ApiConfig) {
1167
1225
  req('/api/marketplace/kinds', s.marketplaceKinds, { signal }),
1168
1226
  listInstalledMarketplacePlugins: (signal?: AbortSignal) =>
1169
1227
  req('/api/marketplace/installed', s.marketplaceInstalled, { signal }),
1228
+ // Preview resolves the spec and computes routes/collisions/public rows with
1229
+ // NO side effects, so the install dialog can show absolute paths, block on a
1230
+ // collision, and surface the public routes before the operator commits.
1231
+ previewMarketplaceInstall: (body: MarketplaceInstallPreviewBody, signal?: AbortSignal) =>
1232
+ req('/api/marketplace/install/preview', s.marketplaceInstallPreview, {
1233
+ method: 'POST',
1234
+ body,
1235
+ signal,
1236
+ }),
1170
1237
  installMarketplacePlugin: (body: MarketplaceInstallBody) =>
1171
1238
  req('/api/marketplace/install', s.marketplaceInstallResult, { method: 'POST', body }),
1172
1239
  uninstallMarketplacePlugin: (body: MarketplaceUninstallBody) =>
package/src/index.ts CHANGED
@@ -25,12 +25,15 @@ export type {
25
25
  ValidatePresetBody,
26
26
  ToolMetaPatch,
27
27
  StorageUploadBody,
28
+ ConversationThreadFilters,
28
29
  ConversationTranscriptQuery,
30
+ ConversationMessageSearchQuery,
29
31
  ConversationThreadMessageBody,
30
32
  MetricsQuery,
31
33
  RunsQuery,
32
34
  MarketplaceSearchQuery,
33
35
  MarketplaceInstallBody,
36
+ MarketplaceInstallPreviewBody,
34
37
  MarketplaceUninstallBody,
35
38
  } from './client';
36
39
  export type { ApiConfig, RequestOptions } from './http';
@@ -127,6 +130,7 @@ export type {
127
130
  ConversationRecordOrigin,
128
131
  TranscriptOrder,
129
132
  ConversationTranscriptPage,
133
+ ConversationMessageSearchPage,
130
134
  ConversationThreadMode,
131
135
  ConversationThreadModeState,
132
136
  ConversationThreadMessageSent,
@@ -182,6 +186,8 @@ export type {
182
186
  RunSpan,
183
187
  RunTrace,
184
188
  MarketplaceItem,
189
+ MarketplaceRouteDecl,
190
+ MarketplaceRoutesDecl,
185
191
  MarketplaceSearchKind,
186
192
  MarketplaceSearchGroup,
187
193
  MarketplaceSearchRow,
@@ -197,5 +203,11 @@ export type {
197
203
  MarketplaceUpgradeAllRow,
198
204
  MarketplaceUpgradeAllResult,
199
205
  MarketplaceInstallResult,
206
+ MarketplaceMountedRoute,
207
+ MarketplacePreviewRoute,
208
+ MarketplacePreviewItem,
209
+ MarketplacePreviewCollision,
210
+ MarketplacePreviewPublicRoute,
211
+ MarketplaceInstallPreview,
200
212
  MarketplaceUninstallResult,
201
213
  } from './schemas';
package/src/schemas.ts CHANGED
@@ -58,10 +58,15 @@ export type ToolMediaResult = z.infer<typeof toolMediaResult>;
58
58
  * FastMCP `meta` under `tai42/hidden`; a tool that never declared it is not hidden).
59
59
  * The tool_meta overlay's tri-state override is merged on top client-side; this read
60
60
  * exposes only the declaration.
61
+ *
62
+ * `badges` are the tool's plugin-DECLARED capability labels — purely INFORMATIONAL
63
+ * (e.g. `network`, `filesystem`), never a gate the server enforces. The overlay's
64
+ * own badges are merged on top client-side; this read exposes only the declaration.
61
65
  */
62
66
  export const toolTagEntry = z.object({
63
67
  name: z.string(),
64
68
  tags: z.array(z.string()),
69
+ badges: z.array(z.string()),
65
70
  hidden: z.boolean(),
66
71
  });
67
72
  export const toolTags = z.array(toolTagEntry);
@@ -238,7 +243,9 @@ export type FolderRecord = z.infer<typeof folderRecord>;
238
243
  * The organizational overlay for one tool, keyed by `tool_name`. `display_name`
239
244
  * overrides the rendered label (`display_name ?? name`); `folder_id` places the tool
240
245
  * in a folder (`null` = unfiled); `tags` is the user's editable categorization,
241
- * merged with the tool's read-only native tags by the UI; `hidden` is TRI-STATE —
246
+ * merged with the tool's read-only native tags by the UI; `badges` is the operator's
247
+ * editable capability-label overlay, merged with the tool's read-only plugin-declared
248
+ * badges by the UI (INFORMATIONAL, never an enforced gate); `hidden` is TRI-STATE —
242
249
  * `null` = defer to the plugin declaration, `true` = force hidden, `false` = force
243
250
  * visible. A drift throws `ApiSchemaError`.
244
251
  */
@@ -247,6 +254,7 @@ export const toolMetaRecord = z.object({
247
254
  display_name: z.string().nullable(),
248
255
  folder_id: z.string().nullable(),
249
256
  tags: z.array(z.string()),
257
+ badges: z.array(z.string()),
250
258
  hidden: z.boolean().nullable(),
251
259
  });
252
260
  export type ToolMetaRecord = z.infer<typeof toolMetaRecord>;
@@ -1056,12 +1064,18 @@ export const conversationThread = z.object({
1056
1064
  });
1057
1065
  export type ConversationThread = z.infer<typeof conversationThread>;
1058
1066
 
1059
- /** The page window both thread read doors carry; `next_page` is null on the last. */
1067
+ /**
1068
+ * The page window every conversation read door carries; `next_page` is null on the
1069
+ * last page. `truncated` is TRUE when the door had more matches than it would scan
1070
+ * or return under its filter/search cap — the UI must surface that LOUDLY, never a
1071
+ * silent cut — and false whenever the window is exhaustive.
1072
+ */
1060
1073
  const conversationPageWindow = {
1061
1074
  total: z.number(),
1062
1075
  page: z.number(),
1063
1076
  page_size: z.number(),
1064
1077
  next_page: z.number().nullable(),
1078
+ truncated: z.boolean(),
1065
1079
  };
1066
1080
 
1067
1081
  export const conversationThreadsPage = z.object({
@@ -1117,6 +1131,19 @@ export const conversationTranscriptPage = z.object({
1117
1131
  });
1118
1132
  export type ConversationTranscriptPage = z.infer<typeof conversationTranscriptPage>;
1119
1133
 
1134
+ /**
1135
+ * A route-scoped message search (`GET /api/conversations/{route}/messages/search?q`):
1136
+ * every record on the route whose text matches `q`, across threads, newest first.
1137
+ * The record shape is the transcript's exactly, so a hit renders with the same
1138
+ * projection. `truncated` says the match set outran the door's scan cap — surfaced
1139
+ * loudly, never a silent cut.
1140
+ */
1141
+ export const conversationMessageSearchPage = z.object({
1142
+ items: z.array(conversationMessage),
1143
+ ...conversationPageWindow,
1144
+ });
1145
+ export type ConversationMessageSearchPage = z.infer<typeof conversationMessageSearchPage>;
1146
+
1120
1147
  /**
1121
1148
  * Who answers a thread's incoming messages: `agent` lets the flow reply on its
1122
1149
  * own; `manual` holds the flow back so a human answers. `source` says whether the
@@ -1857,10 +1884,43 @@ export type RunTrace = z.infer<typeof runTrace>;
1857
1884
 
1858
1885
  // -- marketplace -------------------------------------------------------------
1859
1886
 
1887
+ /**
1888
+ * The CLOSED HTTP-method enum fixed by the contract's RouteMethod, the one source
1889
+ * for every route-method field (declaration and install/preview responses alike):
1890
+ * a wire method the contract does not define fails loudly as drift rather than
1891
+ * passing through silently.
1892
+ */
1893
+ export const routeMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
1894
+ export type RouteMethod = z.infer<typeof routeMethod>;
1895
+
1896
+ /**
1897
+ * One HTTP route an item declares in its `tai-plugin.yml`: a `path` relative to
1898
+ * the item's mount base, the `methods` it answers, and whether it is served
1899
+ * `public` (without authentication). The absolute path an operator sees is
1900
+ * resolved from the base at install time.
1901
+ */
1902
+ export const marketplaceRouteDecl = z.object({
1903
+ path: z.string(),
1904
+ methods: z.array(routeMethod),
1905
+ public: z.boolean(),
1906
+ });
1907
+ export type MarketplaceRouteDecl = z.infer<typeof marketplaceRouteDecl>;
1908
+
1909
+ /**
1910
+ * A route-carrying item's declared mount: the default `base` prefix the routes
1911
+ * hang off (an operator may remap it at install) and the `paths` it registers.
1912
+ */
1913
+ export const marketplaceRoutesDecl = z.object({
1914
+ base: z.string(),
1915
+ paths: z.array(marketplaceRouteDecl),
1916
+ });
1917
+ export type MarketplaceRoutesDecl = z.infer<typeof marketplaceRoutesDecl>;
1918
+
1860
1919
  /**
1861
1920
  * One contained item of a marketplace plugin's published version: its kind
1862
1921
  * (tool / agent / extension / …), name, description, free-form tags, and the
1863
- * logical group it belongs to (`null` when ungrouped).
1922
+ * logical group it belongs to (`null` when ungrouped). `routes` is present only
1923
+ * on a route-carrying item (a router or channel); absent/null on every other.
1864
1924
  */
1865
1925
  export const marketplaceItem = z.object({
1866
1926
  kind: z.string(),
@@ -1868,6 +1928,7 @@ export const marketplaceItem = z.object({
1868
1928
  description: z.string(),
1869
1929
  tags: z.array(z.string()),
1870
1930
  group: z.string().nullable(),
1931
+ routes: marketplaceRoutesDecl.nullish(),
1871
1932
  });
1872
1933
  export type MarketplaceItem = z.infer<typeof marketplaceItem>;
1873
1934
 
@@ -2071,6 +2132,10 @@ export const marketplaceInstalledPlugin = z
2071
2132
  // (local truth). The McpTab joins the mcp-server item names against the
2072
2133
  // manifest's mcp-entry titles to render an installer-written entry read-only.
2073
2134
  items: z.array(marketplaceItem.pick({ kind: true, name: true })),
2135
+ // The persisted `{item_name: base}` mount this plugin is installed at (local
2136
+ // truth; `{}` means every item at its declared base). Studio seeds the update
2137
+ // flow and renders routes at the ACTUAL mounted base from this.
2138
+ route_mounts: z.record(z.string(), z.string()),
2074
2139
  })
2075
2140
  .strict();
2076
2141
  export type MarketplaceInstalledPlugin = z.infer<typeof marketplaceInstalledPlugin>;
@@ -2130,22 +2195,98 @@ export const marketplaceCategories = z.array(z.string());
2130
2195
  /** The registry's controlled item-kind list, proxied through the skeleton. */
2131
2196
  export const marketplaceKinds = z.array(z.string());
2132
2197
 
2198
+ /**
2199
+ * One route the install / update actually mounted: the item that owns it, the
2200
+ * absolute `full_path` it now answers, the `methods`, and whether it is served
2201
+ * `public`. The receipt lists these so the operator sees what was opened where.
2202
+ */
2203
+ export const marketplaceMountedRoute = z.object({
2204
+ item: z.string(),
2205
+ full_path: z.string(),
2206
+ methods: z.array(routeMethod),
2207
+ public: z.boolean(),
2208
+ });
2209
+ export type MarketplaceMountedRoute = z.infer<typeof marketplaceMountedRoute>;
2210
+
2133
2211
  /**
2134
2212
  * The receipt of an install / update: what landed and at which version.
2135
2213
  * `notes` (env-selected items naming their activating env var) and any
2136
- * non-critical `advisories` ride BOTH the install and update receipts. The wire
2137
- * also carries `package`, `reload`, and `pip_output`, which the receipt does not
2138
- * model — zod's default strip drops them; the UI renders the receipt, not the
2139
- * raw install log.
2214
+ * non-critical `advisories` ride BOTH the install and update receipts.
2215
+ * `routes` is the mounted-route list — empty for a plugin that registers none.
2216
+ * The wire also carries `package`, `reload`, and `pip_output`, which the receipt
2217
+ * does not model — zod's default strip drops them; the UI renders the receipt,
2218
+ * not the raw install log.
2140
2219
  */
2141
2220
  export const marketplaceInstallResult = z.object({
2142
2221
  ref: z.string(),
2143
2222
  version: z.string(),
2144
2223
  notes: z.array(z.string()),
2145
2224
  advisories: z.array(marketplaceAdvisory),
2225
+ routes: z.array(marketplaceMountedRoute),
2146
2226
  });
2147
2227
  export type MarketplaceInstallResult = z.infer<typeof marketplaceInstallResult>;
2148
2228
 
2229
+ /** One resolved route in an install preview: the declared `path`, its resolved
2230
+ * absolute `full_path`, the `methods`, and whether it is `public`. */
2231
+ export const marketplacePreviewRoute = z.object({
2232
+ path: z.string(),
2233
+ full_path: z.string(),
2234
+ methods: z.array(routeMethod),
2235
+ public: z.boolean(),
2236
+ });
2237
+ export type MarketplacePreviewRoute = z.infer<typeof marketplacePreviewRoute>;
2238
+
2239
+ /** One route-carrying item in an install preview: its resolved `base` (the
2240
+ * override or the default), the `default_base` it declares, and the routes that
2241
+ * mount under it. */
2242
+ export const marketplacePreviewItem = z.object({
2243
+ item: z.string(),
2244
+ kind: z.string(),
2245
+ base: z.string(),
2246
+ default_base: z.string(),
2247
+ routes: z.array(marketplacePreviewRoute),
2248
+ });
2249
+ export type MarketplacePreviewItem = z.infer<typeof marketplacePreviewItem>;
2250
+
2251
+ /** One collision the preview found: a route whose resolved shape+method clashes
2252
+ * with an already-owned route. `conflict_owner` is `core` or `plugin:<ref>`; the
2253
+ * remedy is to remap the item's base. */
2254
+ export const marketplacePreviewCollision = z.object({
2255
+ item: z.string(),
2256
+ full_path: z.string(),
2257
+ methods: z.array(routeMethod),
2258
+ conflict_owner: z.string(),
2259
+ conflict_path: z.string(),
2260
+ });
2261
+ export type MarketplacePreviewCollision = z.infer<typeof marketplacePreviewCollision>;
2262
+
2263
+ /** One public route the operator is asked to accept: served WITHOUT
2264
+ * authentication once installed. */
2265
+ export const marketplacePreviewPublicRoute = z.object({
2266
+ item: z.string(),
2267
+ full_path: z.string(),
2268
+ methods: z.array(routeMethod),
2269
+ });
2270
+ export type MarketplacePreviewPublicRoute = z.infer<typeof marketplacePreviewPublicRoute>;
2271
+
2272
+ /**
2273
+ * The install preview (`POST /api/marketplace/install/preview`): the resolved
2274
+ * routes per item with any base overrides applied, the collisions against the
2275
+ * live registry, the public routes requiring acceptance, and whether acceptance
2276
+ * is required at all. `new_public_routes` narrows to rows not already approved
2277
+ * (the update case); on a fresh install it equals `public_routes`.
2278
+ */
2279
+ export const marketplaceInstallPreview = z.object({
2280
+ ref: z.string(),
2281
+ version: z.string(),
2282
+ items: z.array(marketplacePreviewItem),
2283
+ collisions: z.array(marketplacePreviewCollision),
2284
+ public_routes: z.array(marketplacePreviewPublicRoute),
2285
+ new_public_routes: z.array(marketplacePreviewPublicRoute),
2286
+ requires_public_acceptance: z.boolean(),
2287
+ });
2288
+ export type MarketplaceInstallPreview = z.infer<typeof marketplaceInstallPreview>;
2289
+
2149
2290
  /**
2150
2291
  * The receipt of an uninstall. `notes` carries operator warnings (e.g. a config
2151
2292
  * provider still selected by its env var); render them like the install