@tai42/api-client 3.0.0 → 5.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
  /**
@@ -790,11 +818,12 @@ export function createApiClient(config: ApiConfig) {
790
818
  routeName: string,
791
819
  page: number,
792
820
  pageSize: number,
821
+ filters: ConversationThreadFilters = {},
793
822
  signal?: AbortSignal,
794
823
  ) =>
795
824
  req(`/api/conversations/${encodeSegment(routeName)}/threads`, s.conversationThreadsPage, {
796
825
  signal,
797
- query: { page, pageSize },
826
+ query: { page, pageSize, status: filters.status, address: filters.address },
798
827
  }),
799
828
  // One thread's transcript, caller-scoped, read in the asked-for direction.
800
829
  // The `thread_id` rides the QUERY STRING, never the path: it embeds the api
@@ -813,9 +842,19 @@ export function createApiClient(config: ApiConfig) {
813
842
  page: query.page,
814
843
  pageSize: query.pageSize,
815
844
  order: query.order,
845
+ q: query.q,
816
846
  },
817
847
  },
818
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
+ ),
819
858
  // Send a human message into a thread. The `thread_id` rides the BODY (a POST,
820
859
  // never a path segment) — same encode-once reasoning as the transcript read.
821
860
  sendConversationThreadMessage: (routeName: string, body: ConversationThreadMessageBody) =>
package/src/index.ts CHANGED
@@ -25,7 +25,9 @@ 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,
@@ -128,6 +130,7 @@ export type {
128
130
  ConversationRecordOrigin,
129
131
  TranscriptOrder,
130
132
  ConversationTranscriptPage,
133
+ ConversationMessageSearchPage,
131
134
  ConversationThreadMode,
132
135
  ConversationThreadModeState,
133
136
  ConversationThreadMessageSent,
@@ -183,6 +186,9 @@ export type {
183
186
  RunSpan,
184
187
  RunTrace,
185
188
  MarketplaceItem,
189
+ MarketplaceDelivery,
190
+ MarketplaceSource,
191
+ MarketplaceRequiredEnvVar,
186
192
  MarketplaceRouteDecl,
187
193
  MarketplaceRoutesDecl,
188
194
  MarketplaceSearchKind,
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>;
@@ -750,7 +758,10 @@ export const providerView = z.object({
750
758
  id: z.string(),
751
759
  display_name: z.string(),
752
760
  description: z.string(),
753
- icon_url: z.string(),
761
+ // Required and a real URL: the contract's `ProviderDescriptor.icon_url` runs
762
+ // `check_web_url(schemes=("https",))`, which rejects an empty/relative value, so
763
+ // an empty or malformed one here is drift and fails the parse loudly.
764
+ icon_url: z.string().url(),
754
765
  kind: z.enum(['oauth', 'none']),
755
766
  origin: z.enum(['system', 'community']),
756
767
  category: z.string(),
@@ -1056,12 +1067,18 @@ export const conversationThread = z.object({
1056
1067
  });
1057
1068
  export type ConversationThread = z.infer<typeof conversationThread>;
1058
1069
 
1059
- /** The page window both thread read doors carry; `next_page` is null on the last. */
1070
+ /**
1071
+ * The page window every conversation read door carries; `next_page` is null on the
1072
+ * last page. `truncated` is TRUE when the door had more matches than it would scan
1073
+ * or return under its filter/search cap — the UI must surface that LOUDLY, never a
1074
+ * silent cut — and false whenever the window is exhaustive.
1075
+ */
1060
1076
  const conversationPageWindow = {
1061
1077
  total: z.number(),
1062
1078
  page: z.number(),
1063
1079
  page_size: z.number(),
1064
1080
  next_page: z.number().nullable(),
1081
+ truncated: z.boolean(),
1065
1082
  };
1066
1083
 
1067
1084
  export const conversationThreadsPage = z.object({
@@ -1117,6 +1134,19 @@ export const conversationTranscriptPage = z.object({
1117
1134
  });
1118
1135
  export type ConversationTranscriptPage = z.infer<typeof conversationTranscriptPage>;
1119
1136
 
1137
+ /**
1138
+ * A route-scoped message search (`GET /api/conversations/{route}/messages/search?q`):
1139
+ * every record on the route whose text matches `q`, across threads, newest first.
1140
+ * The record shape is the transcript's exactly, so a hit renders with the same
1141
+ * projection. `truncated` says the match set outran the door's scan cap — surfaced
1142
+ * loudly, never a silent cut.
1143
+ */
1144
+ export const conversationMessageSearchPage = z.object({
1145
+ items: z.array(conversationMessage),
1146
+ ...conversationPageWindow,
1147
+ });
1148
+ export type ConversationMessageSearchPage = z.infer<typeof conversationMessageSearchPage>;
1149
+
1120
1150
  /**
1121
1151
  * Who answers a thread's incoming messages: `agent` lets the flow reply on its
1122
1152
  * own; `manual` holds the flow back so a human answers. `source` says whether the
@@ -1866,6 +1896,31 @@ export type RunTrace = z.infer<typeof runTrace>;
1866
1896
  export const routeMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
1867
1897
  export type RouteMethod = z.infer<typeof routeMethod>;
1868
1898
 
1899
+ /**
1900
+ * How the plugin is delivered, derived server-side from whether its spec names a
1901
+ * package: `package` is pip-installed, `descriptor` (marketplace `source` `spec`)
1902
+ * installs nothing but the manifest entry. A CLOSED enum, so a wire value the
1903
+ * server does not derive fails loudly as drift.
1904
+ */
1905
+ export const marketplaceDelivery = z.enum(['package', 'descriptor']);
1906
+ export type MarketplaceDelivery = z.infer<typeof marketplaceDelivery>;
1907
+
1908
+ /**
1909
+ * The install-provenance vocabulary a listing/version resolves from: a pip index
1910
+ * (`pypi`), a git tag (`github`), or a descriptor yml (`spec`). CLOSED, so a wire
1911
+ * value outside it fails as drift.
1912
+ */
1913
+ export const marketplaceSource = z.enum(['pypi', 'github', 'spec']);
1914
+ export type MarketplaceSource = z.infer<typeof marketplaceSource>;
1915
+
1916
+ /**
1917
+ * One env var an item requires before install: its `name` and whether its value is
1918
+ * a `secret` (an OAuth client secret is; a client id or a plain marker var is not).
1919
+ * The install dialog seeds each toggle from `secret` and collects the missing ones.
1920
+ */
1921
+ export const marketplaceRequiredEnvVar = z.object({ name: z.string(), secret: z.boolean() });
1922
+ export type MarketplaceRequiredEnvVar = z.infer<typeof marketplaceRequiredEnvVar>;
1923
+
1869
1924
  /**
1870
1925
  * One HTTP route an item declares in its `tai-plugin.yml`: a `path` relative to
1871
1926
  * the item's mount base, the `methods` it answers, and whether it is served
@@ -1894,6 +1949,12 @@ export type MarketplaceRoutesDecl = z.infer<typeof marketplaceRoutesDecl>;
1894
1949
  * (tool / agent / extension / …), name, description, free-form tags, and the
1895
1950
  * logical group it belongs to (`null` when ungrouped). `routes` is present only
1896
1951
  * on a route-carrying item (a router or channel); absent/null on every other.
1952
+ * `required_env` is the env vars the item needs before install — but the REGISTRY's
1953
+ * plugin-detail body does NOT carry it per item: required-env is server-computed
1954
+ * (`required_env_for_spec`) and rides the install PREVIEW, never the listing detail,
1955
+ * so the registry OMITS it here and it parses as `undefined`. Optional (not
1956
+ * defaulted) so the type mirrors the wire; the authority the install dialog collects
1957
+ * from is the preview's `required_env`/`missing_env`, never this field.
1897
1958
  */
1898
1959
  export const marketplaceItem = z.object({
1899
1960
  kind: z.string(),
@@ -1902,6 +1963,7 @@ export const marketplaceItem = z.object({
1902
1963
  tags: z.array(z.string()),
1903
1964
  group: z.string().nullable(),
1904
1965
  routes: marketplaceRoutesDecl.nullish(),
1966
+ required_env: z.array(marketplaceRequiredEnvVar).optional(),
1905
1967
  });
1906
1968
  export type MarketplaceItem = z.infer<typeof marketplaceItem>;
1907
1969
 
@@ -1943,7 +2005,8 @@ export const marketplaceSearchRow = z.object({
1943
2005
  name: z.string(),
1944
2006
  display_name: z.string().nullable(),
1945
2007
  icon_url: z.string().nullable(),
1946
- package: z.string(),
2008
+ // Null for a descriptor listing (`source` `spec`): it names no package.
2009
+ package: z.string().nullable(),
1947
2010
  description: z.string(),
1948
2011
  categories: z.array(z.string()),
1949
2012
  tags: z.array(z.string()),
@@ -1987,35 +2050,16 @@ export const marketplaceVersion = z.object({
1987
2050
  export type MarketplaceVersion = z.infer<typeof marketplaceVersion>;
1988
2051
 
1989
2052
  /**
1990
- * The listing's latest published version as embedded in its detail: the version,
1991
- * the items it contains, and a minimal typed passthrough of the published spec.
1992
- *
1993
- * `spec` is carried so the install dialog can read an mcp-server item's public
1994
- * `mcp.env` marker strings (`provides[].mcp.env`) and collect its required `!ENV`
1995
- * vars before install. Only that path is typed; everything else on the spec is
1996
- * `.passthrough()`-preserved but unmodelled (zod would otherwise strip the whole
1997
- * `spec` as an unknown key). Nullable + optional: a listing with nothing published
1998
- * carries no spec.
2053
+ * The listing's latest published version as embedded in its detail: the version
2054
+ * (with the `marketplaceVersion` lifecycle fields) and the items it contains. The
2055
+ * registry's detail body carries NO per-version `delivery` field — delivery is
2056
+ * server-computed from whether the spec names a package, and the detail surface
2057
+ * derives it from the listing's `package` (null ⇒ `descriptor`, else `package`).
2058
+ * Env to collect comes from the install preview (`required_env` / `missing_env`),
2059
+ * never from these items.
1999
2060
  */
2000
2061
  export const marketplaceLatestVersion = marketplaceVersion.extend({
2001
2062
  items: z.array(marketplaceItem),
2002
- spec: z
2003
- .object({
2004
- provides: z.array(
2005
- z
2006
- .object({
2007
- kind: z.string(),
2008
- name: z.string(),
2009
- mcp: z
2010
- .object({ env: z.record(z.string(), z.string()).nullish() })
2011
- .passthrough()
2012
- .nullish(),
2013
- })
2014
- .passthrough(),
2015
- ),
2016
- })
2017
- .passthrough()
2018
- .nullish(),
2019
2063
  });
2020
2064
  export type MarketplaceLatestVersion = z.infer<typeof marketplaceLatestVersion>;
2021
2065
 
@@ -2043,7 +2087,11 @@ export const marketplacePluginDetail = z.object({
2043
2087
  name: z.string(),
2044
2088
  display_name: z.string().nullable(),
2045
2089
  icon_url: z.string().nullable(),
2046
- package: z.string(),
2090
+ // Null for a descriptor listing (`source` `spec`): it names no package.
2091
+ package: z.string().nullable(),
2092
+ // The install-provenance vocabulary (`pypi` / `github` / `spec`). Nullish: an
2093
+ // older registry omits it, and a listing with nothing published carries none.
2094
+ source: marketplaceSource.nullish(),
2047
2095
  description: z.string(),
2048
2096
  readme_md: z.string().nullable(),
2049
2097
  license: z.string().nullable(),
@@ -2095,6 +2143,9 @@ export const marketplaceInstalledPlugin = z
2095
2143
  ref: z.string(),
2096
2144
  version: z.string(),
2097
2145
  source: z.string(),
2146
+ // How the row was delivered: `package` for a pip-installed plugin, `descriptor`
2147
+ // for a descriptor-only plugin whose stored spec names no package.
2148
+ delivery: marketplaceDelivery,
2098
2149
  installed_at: z.string(),
2099
2150
  latest: z.string().nullable(),
2100
2151
  update_available: z.boolean(),
@@ -2248,6 +2299,11 @@ export type MarketplacePreviewPublicRoute = z.infer<typeof marketplacePreviewPub
2248
2299
  * live registry, the public routes requiring acceptance, and whether acceptance
2249
2300
  * is required at all. `new_public_routes` narrows to rows not already approved
2250
2301
  * (the update case); on a fresh install it equals `public_routes`.
2302
+ *
2303
+ * `required_env` is every env var the spec requires with its derived secret-ness;
2304
+ * `missing_env` is the bare names not already present in the env store union
2305
+ * process env (server-computed — the ONE authority the dialog collects), and
2306
+ * `delivery` is the one word the surface shows (`package` / `descriptor`).
2251
2307
  */
2252
2308
  export const marketplaceInstallPreview = z.object({
2253
2309
  ref: z.string(),
@@ -2257,6 +2313,9 @@ export const marketplaceInstallPreview = z.object({
2257
2313
  public_routes: z.array(marketplacePreviewPublicRoute),
2258
2314
  new_public_routes: z.array(marketplacePreviewPublicRoute),
2259
2315
  requires_public_acceptance: z.boolean(),
2316
+ required_env: z.array(marketplaceRequiredEnvVar),
2317
+ missing_env: z.array(z.string()),
2318
+ delivery: marketplaceDelivery,
2260
2319
  });
2261
2320
  export type MarketplaceInstallPreview = z.infer<typeof marketplaceInstallPreview>;
2262
2321