@tai42/api-client 15.0.0 → 16.0.1

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.
Files changed (383) hide show
  1. package/dist/client/agents-client.d.ts +26 -0
  2. package/dist/client/agents-client.d.ts.map +1 -0
  3. package/dist/client/agents-client.js +19 -0
  4. package/dist/client/agents-client.js.map +1 -0
  5. package/dist/client/auth-client.d.ts +205 -0
  6. package/dist/client/auth-client.d.ts.map +1 -0
  7. package/dist/client/auth-client.js +59 -0
  8. package/dist/client/auth-client.js.map +1 -0
  9. package/dist/client/backend-client.d.ts +39 -0
  10. package/dist/client/backend-client.d.ts.map +1 -0
  11. package/dist/client/backend-client.js +22 -0
  12. package/dist/client/backend-client.js.map +1 -0
  13. package/dist/client/backup-client.d.ts +56 -0
  14. package/dist/client/backup-client.d.ts.map +1 -0
  15. package/dist/client/backup-client.js +12 -0
  16. package/dist/client/backup-client.js.map +1 -0
  17. package/dist/client/capabilities-client.d.ts +35 -0
  18. package/dist/client/capabilities-client.d.ts.map +1 -0
  19. package/dist/client/capabilities-client.js +19 -0
  20. package/dist/client/capabilities-client.js.map +1 -0
  21. package/dist/client/channels-client.d.ts +7 -0
  22. package/dist/client/channels-client.d.ts.map +1 -0
  23. package/dist/client/channels-client.js +10 -0
  24. package/dist/client/channels-client.js.map +1 -0
  25. package/dist/client/config-client.d.ts +59 -0
  26. package/dist/client/config-client.d.ts.map +1 -0
  27. package/dist/client/config-client.js +18 -0
  28. package/dist/client/config-client.js.map +1 -0
  29. package/dist/client/connectors-client.d.ts +225 -0
  30. package/dist/client/connectors-client.d.ts.map +1 -0
  31. package/dist/client/connectors-client.js +31 -0
  32. package/dist/client/connectors-client.js.map +1 -0
  33. package/dist/client/conversations-client.d.ts +399 -0
  34. package/dist/client/conversations-client.d.ts.map +1 -0
  35. package/dist/client/conversations-client.js +120 -0
  36. package/dist/client/conversations-client.js.map +1 -0
  37. package/dist/client/hooks-client.d.ts +113 -0
  38. package/dist/client/hooks-client.d.ts.map +1 -0
  39. package/dist/client/hooks-client.js +25 -0
  40. package/dist/client/hooks-client.js.map +1 -0
  41. package/dist/{client.d.ts → client/index.d.ts} +2199 -2508
  42. package/dist/client/index.d.ts.map +1 -0
  43. package/dist/client/index.js +79 -0
  44. package/dist/client/index.js.map +1 -0
  45. package/dist/client/interactions-client.d.ts +52 -0
  46. package/dist/client/interactions-client.d.ts.map +1 -0
  47. package/dist/client/interactions-client.js +84 -0
  48. package/dist/client/interactions-client.js.map +1 -0
  49. package/dist/client/login-client.d.ts +56 -0
  50. package/dist/client/login-client.d.ts.map +1 -0
  51. package/dist/client/login-client.js +103 -0
  52. package/dist/client/login-client.js.map +1 -0
  53. package/dist/client/manifest-client.d.ts +354 -0
  54. package/dist/client/manifest-client.d.ts.map +1 -0
  55. package/dist/client/manifest-client.js +76 -0
  56. package/dist/client/manifest-client.js.map +1 -0
  57. package/dist/client/marketplace-client.d.ts +274 -0
  58. package/dist/client/marketplace-client.d.ts.map +1 -0
  59. package/dist/client/marketplace-client.js +46 -0
  60. package/dist/client/marketplace-client.js.map +1 -0
  61. package/dist/client/mcp-config-schema-client.d.ts +5 -0
  62. package/dist/client/mcp-config-schema-client.d.ts.map +1 -0
  63. package/dist/client/mcp-config-schema-client.js +9 -0
  64. package/dist/client/mcp-config-schema-client.js.map +1 -0
  65. package/dist/client/notifications-client.d.ts +27 -0
  66. package/dist/client/notifications-client.d.ts.map +1 -0
  67. package/dist/client/notifications-client.js +11 -0
  68. package/dist/client/notifications-client.js.map +1 -0
  69. package/dist/client/observability-client.d.ts +103 -0
  70. package/dist/client/observability-client.d.ts.map +1 -0
  71. package/dist/client/observability-client.js +43 -0
  72. package/dist/client/observability-client.js.map +1 -0
  73. package/dist/client/plugins-client.d.ts +17 -0
  74. package/dist/client/plugins-client.d.ts.map +1 -0
  75. package/dist/client/plugins-client.js +9 -0
  76. package/dist/client/plugins-client.js.map +1 -0
  77. package/dist/client/policy-client.d.ts +37 -0
  78. package/dist/client/policy-client.d.ts.map +1 -0
  79. package/dist/client/policy-client.js +26 -0
  80. package/dist/client/policy-client.js.map +1 -0
  81. package/dist/client/presets-client.d.ts +335 -0
  82. package/dist/client/presets-client.d.ts.map +1 -0
  83. package/dist/client/presets-client.js +38 -0
  84. package/dist/client/presets-client.js.map +1 -0
  85. package/dist/client/profiles-client.d.ts +102 -0
  86. package/dist/client/profiles-client.d.ts.map +1 -0
  87. package/dist/client/profiles-client.js +37 -0
  88. package/dist/client/profiles-client.js.map +1 -0
  89. package/dist/client/schedules-client.d.ts +28 -0
  90. package/dist/client/schedules-client.d.ts.map +1 -0
  91. package/dist/client/schedules-client.js +13 -0
  92. package/dist/client/schedules-client.js.map +1 -0
  93. package/dist/client/settings-schema-client.d.ts +23 -0
  94. package/dist/client/settings-schema-client.d.ts.map +1 -0
  95. package/dist/client/settings-schema-client.js +9 -0
  96. package/dist/client/settings-schema-client.js.map +1 -0
  97. package/dist/client/states-client.d.ts +471 -0
  98. package/dist/client/states-client.d.ts.map +1 -0
  99. package/dist/client/states-client.js +144 -0
  100. package/dist/client/states-client.js.map +1 -0
  101. package/dist/client/storage-client.d.ts +40 -0
  102. package/dist/client/storage-client.d.ts.map +1 -0
  103. package/dist/client/storage-client.js +43 -0
  104. package/dist/client/storage-client.js.map +1 -0
  105. package/dist/client/sub-mcp-client.d.ts +17 -0
  106. package/dist/client/sub-mcp-client.d.ts.map +1 -0
  107. package/dist/client/sub-mcp-client.js +14 -0
  108. package/dist/client/sub-mcp-client.js.map +1 -0
  109. package/dist/client/system-client.d.ts +11 -0
  110. package/dist/client/system-client.d.ts.map +1 -0
  111. package/dist/client/system-client.js +13 -0
  112. package/dist/client/system-client.js.map +1 -0
  113. package/dist/client/templates-client.d.ts +29 -0
  114. package/dist/client/templates-client.d.ts.map +1 -0
  115. package/dist/client/templates-client.js +21 -0
  116. package/dist/client/templates-client.js.map +1 -0
  117. package/dist/client/tool-extensions-client.d.ts +54 -0
  118. package/dist/client/tool-extensions-client.d.ts.map +1 -0
  119. package/dist/client/tool-extensions-client.js +21 -0
  120. package/dist/client/tool-extensions-client.js.map +1 -0
  121. package/dist/client/tool-meta-client.d.ts +69 -0
  122. package/dist/client/tool-meta-client.d.ts.map +1 -0
  123. package/dist/client/tool-meta-client.js +38 -0
  124. package/dist/client/tool-meta-client.js.map +1 -0
  125. package/dist/client/tools-client.d.ts +83 -0
  126. package/dist/client/tools-client.d.ts.map +1 -0
  127. package/dist/client/tools-client.js +38 -0
  128. package/dist/client/tools-client.js.map +1 -0
  129. package/dist/client/transport.d.ts +13 -0
  130. package/dist/client/transport.d.ts.map +1 -0
  131. package/dist/client/transport.js +10 -0
  132. package/dist/client/transport.js.map +1 -0
  133. package/dist/client/trigger-links-client.d.ts +44 -0
  134. package/dist/client/trigger-links-client.d.ts.map +1 -0
  135. package/dist/client/trigger-links-client.js +30 -0
  136. package/dist/client/trigger-links-client.js.map +1 -0
  137. package/dist/client/web-entry-gate-client.d.ts +34 -0
  138. package/dist/client/web-entry-gate-client.d.ts.map +1 -0
  139. package/dist/client/web-entry-gate-client.js +25 -0
  140. package/dist/client/web-entry-gate-client.js.map +1 -0
  141. package/dist/generated/served-schemas.d.ts +748 -0
  142. package/dist/generated/served-schemas.d.ts.map +1 -0
  143. package/dist/generated/served-schemas.js +60 -0
  144. package/dist/generated/served-schemas.js.map +1 -0
  145. package/dist/http.d.ts.map +1 -1
  146. package/dist/http.js +33 -17
  147. package/dist/http.js.map +1 -1
  148. package/dist/index.d.ts +1 -1
  149. package/dist/index.d.ts.map +1 -1
  150. package/dist/schemas/api-keys.d.ts +34 -0
  151. package/dist/schemas/api-keys.d.ts.map +1 -0
  152. package/dist/schemas/api-keys.js +22 -0
  153. package/dist/schemas/api-keys.js.map +1 -0
  154. package/dist/schemas/auth-routes.d.ts +78 -0
  155. package/dist/schemas/auth-routes.d.ts.map +1 -0
  156. package/dist/schemas/auth-routes.js +42 -0
  157. package/dist/schemas/auth-routes.js.map +1 -0
  158. package/dist/schemas/backup.d.ts +78 -0
  159. package/dist/schemas/backup.d.ts.map +1 -0
  160. package/dist/schemas/backup.js +33 -0
  161. package/dist/schemas/backup.js.map +1 -0
  162. package/dist/schemas/capabilities.d.ts +99 -0
  163. package/dist/schemas/capabilities.d.ts.map +1 -0
  164. package/dist/schemas/capabilities.js +74 -0
  165. package/dist/schemas/capabilities.js.map +1 -0
  166. package/dist/schemas/channels.d.ts +7 -0
  167. package/dist/schemas/channels.d.ts.map +1 -0
  168. package/dist/schemas/channels.js +9 -0
  169. package/dist/schemas/channels.js.map +1 -0
  170. package/dist/schemas/config.d.ts +17 -0
  171. package/dist/schemas/config.d.ts.map +1 -0
  172. package/dist/schemas/config.js +17 -0
  173. package/dist/schemas/config.js.map +1 -0
  174. package/dist/schemas/connectors.d.ts +383 -0
  175. package/dist/schemas/connectors.d.ts.map +1 -0
  176. package/dist/schemas/connectors.js +122 -0
  177. package/dist/schemas/connectors.js.map +1 -0
  178. package/dist/schemas/conversations.d.ts +600 -0
  179. package/dist/schemas/conversations.d.ts.map +1 -0
  180. package/dist/schemas/conversations.js +208 -0
  181. package/dist/schemas/conversations.js.map +1 -0
  182. package/dist/schemas/extensions.d.ts +44 -0
  183. package/dist/schemas/extensions.d.ts.map +1 -0
  184. package/dist/schemas/extensions.js +29 -0
  185. package/dist/schemas/extensions.js.map +1 -0
  186. package/dist/schemas/fleet.d.ts +271 -0
  187. package/dist/schemas/fleet.d.ts.map +1 -0
  188. package/dist/schemas/fleet.js +132 -0
  189. package/dist/schemas/fleet.js.map +1 -0
  190. package/dist/schemas/hooks.d.ts +120 -0
  191. package/dist/schemas/hooks.d.ts.map +1 -0
  192. package/dist/schemas/hooks.js +44 -0
  193. package/dist/schemas/hooks.js.map +1 -0
  194. package/dist/schemas/index.d.ts +38 -0
  195. package/dist/schemas/index.d.ts.map +1 -0
  196. package/dist/schemas/index.js +38 -0
  197. package/dist/schemas/index.js.map +1 -0
  198. package/dist/schemas/interactions.d.ts +156 -0
  199. package/dist/schemas/interactions.d.ts.map +1 -0
  200. package/dist/schemas/interactions.js +124 -0
  201. package/dist/schemas/interactions.js.map +1 -0
  202. package/dist/schemas/login.d.ts +86 -0
  203. package/dist/schemas/login.d.ts.map +1 -0
  204. package/dist/schemas/login.js +54 -0
  205. package/dist/schemas/login.js.map +1 -0
  206. package/dist/schemas/manifest-mcp.d.ts +125 -0
  207. package/dist/schemas/manifest-mcp.d.ts.map +1 -0
  208. package/dist/schemas/manifest-mcp.js +61 -0
  209. package/dist/schemas/manifest-mcp.js.map +1 -0
  210. package/dist/schemas/marketplace.d.ts +708 -0
  211. package/dist/schemas/marketplace.d.ts.map +1 -0
  212. package/dist/schemas/marketplace.js +387 -0
  213. package/dist/schemas/marketplace.js.map +1 -0
  214. package/dist/schemas/notifications.d.ts +61 -0
  215. package/dist/schemas/notifications.d.ts.map +1 -0
  216. package/dist/schemas/notifications.js +38 -0
  217. package/dist/schemas/notifications.js.map +1 -0
  218. package/dist/schemas/observability.d.ts +114 -0
  219. package/dist/schemas/observability.d.ts.map +1 -0
  220. package/dist/schemas/observability.js +74 -0
  221. package/dist/schemas/observability.js.map +1 -0
  222. package/dist/schemas/policy.d.ts +58 -0
  223. package/dist/schemas/policy.d.ts.map +1 -0
  224. package/dist/schemas/policy.js +32 -0
  225. package/dist/schemas/policy.js.map +1 -0
  226. package/dist/schemas/presets.d.ts +258 -0
  227. package/dist/schemas/presets.d.ts.map +1 -0
  228. package/dist/schemas/presets.js +95 -0
  229. package/dist/schemas/presets.js.map +1 -0
  230. package/dist/schemas/profiles.d.ts +180 -0
  231. package/dist/schemas/profiles.d.ts.map +1 -0
  232. package/dist/schemas/profiles.js +100 -0
  233. package/dist/schemas/profiles.js.map +1 -0
  234. package/dist/schemas/roles.d.ts +125 -0
  235. package/dist/schemas/roles.d.ts.map +1 -0
  236. package/dist/schemas/roles.js +46 -0
  237. package/dist/schemas/roles.js.map +1 -0
  238. package/dist/schemas/schedules.d.ts +25 -0
  239. package/dist/schemas/schedules.d.ts.map +1 -0
  240. package/dist/schemas/schedules.js +24 -0
  241. package/dist/schemas/schedules.js.map +1 -0
  242. package/dist/schemas/served.d.ts +626 -0
  243. package/dist/schemas/served.d.ts.map +1 -0
  244. package/dist/schemas/served.js +28 -0
  245. package/dist/schemas/served.js.map +1 -0
  246. package/dist/schemas/settings-schema.d.ts +23 -0
  247. package/dist/schemas/settings-schema.d.ts.map +1 -0
  248. package/dist/schemas/settings-schema.js +25 -0
  249. package/dist/schemas/settings-schema.js.map +1 -0
  250. package/dist/schemas/shared.d.ts +20 -0
  251. package/dist/schemas/shared.d.ts.map +1 -0
  252. package/dist/schemas/shared.js +20 -0
  253. package/dist/schemas/shared.js.map +1 -0
  254. package/dist/schemas/states.d.ts +499 -0
  255. package/dist/schemas/states.d.ts.map +1 -0
  256. package/dist/schemas/states.js +220 -0
  257. package/dist/schemas/states.js.map +1 -0
  258. package/dist/schemas/storage.d.ts +37 -0
  259. package/dist/schemas/storage.d.ts.map +1 -0
  260. package/dist/schemas/storage.js +24 -0
  261. package/dist/schemas/storage.js.map +1 -0
  262. package/dist/schemas/studio-plugins.d.ts +30 -0
  263. package/dist/schemas/studio-plugins.d.ts.map +1 -0
  264. package/dist/schemas/studio-plugins.js +20 -0
  265. package/dist/schemas/studio-plugins.js.map +1 -0
  266. package/dist/schemas/sub-mcp.d.ts +29 -0
  267. package/dist/schemas/sub-mcp.d.ts.map +1 -0
  268. package/dist/schemas/sub-mcp.js +22 -0
  269. package/dist/schemas/sub-mcp.js.map +1 -0
  270. package/dist/schemas/system-kinds.d.ts +122 -0
  271. package/dist/schemas/system-kinds.d.ts.map +1 -0
  272. package/dist/schemas/system-kinds.js +40 -0
  273. package/dist/schemas/system-kinds.js.map +1 -0
  274. package/dist/schemas/templates.d.ts +26 -0
  275. package/dist/schemas/templates.d.ts.map +1 -0
  276. package/dist/schemas/templates.js +11 -0
  277. package/dist/schemas/templates.js.map +1 -0
  278. package/dist/schemas/tool-meta.d.ts +64 -0
  279. package/dist/schemas/tool-meta.d.ts.map +1 -0
  280. package/dist/schemas/tool-meta.js +44 -0
  281. package/dist/schemas/tool-meta.js.map +1 -0
  282. package/dist/schemas/tools.d.ts +62 -0
  283. package/dist/schemas/tools.d.ts.map +1 -0
  284. package/dist/schemas/tools.js +53 -0
  285. package/dist/schemas/tools.js.map +1 -0
  286. package/dist/schemas/trigger-links.d.ts +71 -0
  287. package/dist/schemas/trigger-links.d.ts.map +1 -0
  288. package/dist/schemas/trigger-links.js +46 -0
  289. package/dist/schemas/trigger-links.js.map +1 -0
  290. package/dist/schemas/web-entry.d.ts +45 -0
  291. package/dist/schemas/web-entry.d.ts.map +1 -0
  292. package/dist/schemas/web-entry.js +36 -0
  293. package/dist/schemas/web-entry.js.map +1 -0
  294. package/dist/templated-text.d.ts +30 -0
  295. package/dist/templated-text.d.ts.map +1 -0
  296. package/dist/templated-text.js +30 -0
  297. package/dist/templated-text.js.map +1 -0
  298. package/dist/tool-runs.d.ts +4 -4
  299. package/package.json +6 -2
  300. package/src/client/agents-client.ts +23 -0
  301. package/src/client/auth-client.ts +144 -0
  302. package/src/client/backend-client.ts +25 -0
  303. package/src/client/backup-client.ts +16 -0
  304. package/src/client/capabilities-client.ts +22 -0
  305. package/src/client/channels-client.ts +11 -0
  306. package/src/client/config-client.ts +21 -0
  307. package/src/client/connectors-client.ts +52 -0
  308. package/src/client/conversations-client.ts +240 -0
  309. package/src/client/hooks-client.ts +44 -0
  310. package/src/client/index.ts +122 -0
  311. package/src/client/interactions-client.ts +89 -0
  312. package/src/client/login-client.ts +109 -0
  313. package/src/client/manifest-client.ts +118 -0
  314. package/src/client/marketplace-client.ts +122 -0
  315. package/src/client/mcp-config-schema-client.ts +11 -0
  316. package/src/client/notifications-client.ts +13 -0
  317. package/src/client/observability-client.ts +82 -0
  318. package/src/client/plugins-client.ts +11 -0
  319. package/src/client/policy-client.ts +41 -0
  320. package/src/client/presets-client.ts +124 -0
  321. package/src/client/profiles-client.ts +51 -0
  322. package/src/client/schedules-client.ts +22 -0
  323. package/src/client/settings-schema-client.ts +11 -0
  324. package/src/client/states-client.ts +292 -0
  325. package/src/client/storage-client.ts +63 -0
  326. package/src/client/sub-mcp-client.ts +17 -0
  327. package/src/client/system-client.ts +16 -0
  328. package/src/client/templates-client.ts +28 -0
  329. package/src/client/tool-extensions-client.ts +24 -0
  330. package/src/client/tool-meta-client.ts +65 -0
  331. package/src/client/tools-client.ts +66 -0
  332. package/src/client/transport.ts +18 -0
  333. package/src/client/trigger-links-client.ts +51 -0
  334. package/src/client/web-entry-gate-client.ts +45 -0
  335. package/src/generated/served-schemas.ts +126 -0
  336. package/src/http.ts +48 -24
  337. package/src/index.ts +1 -0
  338. package/src/schemas/api-keys.ts +26 -0
  339. package/src/schemas/auth-routes.ts +48 -0
  340. package/src/schemas/backup.ts +42 -0
  341. package/src/schemas/capabilities.ts +87 -0
  342. package/src/schemas/channels.ts +11 -0
  343. package/src/schemas/config.ts +17 -0
  344. package/src/schemas/connectors.ts +138 -0
  345. package/src/schemas/conversations.ts +256 -0
  346. package/src/schemas/extensions.ts +34 -0
  347. package/src/schemas/fleet.ts +153 -0
  348. package/src/schemas/hooks.ts +56 -0
  349. package/src/schemas/index.ts +37 -0
  350. package/src/schemas/interactions.ts +146 -0
  351. package/src/schemas/login.ts +62 -0
  352. package/src/schemas/manifest-mcp.ts +69 -0
  353. package/src/schemas/marketplace.ts +447 -0
  354. package/src/schemas/notifications.ts +42 -0
  355. package/src/schemas/observability.ts +86 -0
  356. package/src/schemas/policy.ts +37 -0
  357. package/src/schemas/presets.ts +113 -0
  358. package/src/schemas/profiles.ts +119 -0
  359. package/src/schemas/roles.ts +57 -0
  360. package/src/schemas/schedules.ts +27 -0
  361. package/src/schemas/served.ts +49 -0
  362. package/src/schemas/settings-schema.ts +31 -0
  363. package/src/schemas/shared.ts +21 -0
  364. package/src/schemas/states.ts +272 -0
  365. package/src/schemas/storage.ts +31 -0
  366. package/src/schemas/studio-plugins.ts +21 -0
  367. package/src/schemas/sub-mcp.ts +24 -0
  368. package/src/schemas/system-kinds.ts +46 -0
  369. package/src/schemas/templates.ts +11 -0
  370. package/src/schemas/tool-meta.ts +51 -0
  371. package/src/schemas/tools.ts +61 -0
  372. package/src/schemas/trigger-links.ts +54 -0
  373. package/src/schemas/web-entry.ts +46 -0
  374. package/src/templated-text.ts +35 -0
  375. package/dist/client.d.ts.map +0 -1
  376. package/dist/client.js +0 -920
  377. package/dist/client.js.map +0 -1
  378. package/dist/schemas.d.ts +0 -5152
  379. package/dist/schemas.d.ts.map +0 -1
  380. package/dist/schemas.js +0 -2556
  381. package/dist/schemas.js.map +0 -1
  382. package/src/client.ts +0 -1765
  383. package/src/schemas.ts +0 -3067
package/dist/schemas.js DELETED
@@ -1,2556 +0,0 @@
1
- /**
2
- * One zod schema per consumed endpoint. These mirror the skeleton's router
3
- * response shapes exactly; a drift throws (ApiSchemaError) and the UI shows the
4
- * error state. JSON-schema-shaped fields are kept as permissive records — the
5
- * auto-form renderer interprets them at runtime.
6
- */
7
- import { z } from 'zod';
8
- /** An arbitrary JSON value (tool results, rendered payloads). */
9
- export const jsonValue = z.unknown();
10
- /** A JSON Schema object (Pydantic-emitted). Permissive; parsed by the auto-form. */
11
- export const jsonSchema = z.record(z.string(), z.unknown());
12
- /**
13
- * One authored-text value: either inline `content` (text written in place) or a
14
- * stored template `id` (a template resource fetched and rendered), never both and
15
- * never neither. `kwargs` are the render parameters and apply in BOTH cases — a
16
- * prompt with placeholders needs its values whether it was typed inline or fetched.
17
- * `.strict()` refuses any other key so a drifting field fails the parse loudly.
18
- */
19
- export const templatedText = z
20
- .object({
21
- content: z.string().optional(),
22
- id: z.string().optional(),
23
- kwargs: z.record(z.string(), z.unknown()).optional(),
24
- })
25
- .strict()
26
- .refine((value) => (value.content === undefined) !== (value.id === undefined), {
27
- message: 'A templated-text value sets exactly one of `content` or `id`.',
28
- });
29
- /**
30
- * A templated-text value that must carry a source: inline `content` is non-empty, or
31
- * a stored template `id` is chosen. Used where the platform contract requires a
32
- * value (e.g. a state binding's subject, an update's adapter when present).
33
- */
34
- export const requiredTemplatedText = templatedText.refine((value) => (value.id !== undefined ? value.id.length > 0 : (value.content ?? '').length > 0), { message: 'Provide inline text or choose a stored template.' });
35
- // -- tools -------------------------------------------------------------------
36
- export const toolNames = z.array(z.string());
37
- export const toolSchema = z.object({
38
- input: jsonSchema,
39
- output: jsonSchema.nullable(),
40
- description: z.string().nullable(),
41
- });
42
- export const allToolSchemas = z.record(z.string(), toolSchema);
43
- export const runToolResult = jsonValue;
44
- /**
45
- * The MEDIA content shape a direct tool run may return — fastmcp's serialized
46
- * `Image`/`Audio` MediaBlock (`{ type, data: <base64>, mimeType }`). One recognised
47
- * shape of the otherwise-arbitrary `runToolResult`, kept as its own schema so the
48
- * viewer detects media by parse, not by ad-hoc shape check.
49
- *
50
- * SECURITY: the refinement pins `mimeType` to its kind — `image` MUST be `image/*`,
51
- * `audio` MUST be `audio/*` — so a drifting/arbitrary mime fails the parse and is
52
- * never turned into a `data:` URI (a `File` MediaBlock's distinct `EmbeddedResource`
53
- * shape also fails here, by design).
54
- */
55
- export const toolMediaResult = z
56
- .object({
57
- type: z.enum(['image', 'audio']),
58
- data: z.string(),
59
- mimeType: z.string(),
60
- })
61
- .refine((media) => media.mimeType.startsWith(`${media.type}/`), {
62
- message: 'media mimeType must match its media kind (image/* for image, audio/* for audio)',
63
- path: ['mimeType'],
64
- });
65
- /**
66
- * The ADDITIVE per-tool native surface (`GET /api/tools/tags`). Distinct from the
67
- * flat `toolNames` contract (`GET /api/tools`, unchanged): each entry carries the
68
- * tool's read-only FastMCP `tags` — so the shared `ToolPicker` can group/filter its
69
- * options — plus `hidden`, the tool's OWN plugin-declared visibility (read from the
70
- * FastMCP `meta` under `tai42/hidden`; a tool that never declared it is not hidden).
71
- * The tool_meta overlay's tri-state override is merged on top client-side; this read
72
- * exposes only the declaration.
73
- *
74
- * `badges` are the tool's plugin-DECLARED capability labels — purely INFORMATIONAL
75
- * (e.g. `network`, `filesystem`), never a gate the server enforces. The overlay's
76
- * own badges are merged on top client-side; this read exposes only the declaration.
77
- */
78
- export const toolTagEntry = z.object({
79
- name: z.string(),
80
- tags: z.array(z.string()),
81
- badges: z.array(z.string()),
82
- hidden: z.boolean(),
83
- });
84
- export const toolTags = z.array(toolTagEntry);
85
- // -- extensions --------------------------------------------------------------
86
- export const extension = z.object({ name: z.string(), kind: z.string() });
87
- export const extensions = z.array(extension);
88
- /**
89
- * One element of an extension combo: a bare extension NAME, or a
90
- * `{ name, config }` mapping binding author config (e.g. an `output_schema` JSON
91
- * Schema, or an `ask_external` verifier). Both wire forms are admitted so a
92
- * config-bearing combo parses without dropping server data. A drift throws
93
- * `ApiSchemaError`.
94
- */
95
- export const presetExtensionElement = z.union([
96
- z.string(),
97
- z.object({ name: z.string(), config: z.record(z.string(), z.unknown()) }),
98
- ]);
99
- /**
100
- * `GET /api/tools/{name}/extensions` — a tool's FULL lossless list-of-combos plus
101
- * the extension catalog. `combos` is a list of combos, each an ordered list of
102
- * extension ELEMENTS (a bare name or a config-bearing `{ name, config }` mapping —
103
- * `output_schema` carries its schema this way); `[]` when the tool has no map entry.
104
- * It carries EVERY combo mapped to the base tool (the union across every `tools`/`mcp`
105
- * config), never a single flattened one. `available` is the same flat catalog
106
- * `GET /api/extensions` returns. A drift throws `ApiSchemaError`.
107
- */
108
- export const toolExtensions = z.object({
109
- combos: z.array(z.array(presetExtensionElement)),
110
- available: extensions,
111
- });
112
- // -- presets -----------------------------------------------------------------
113
- /** A list of extension COMBOS — each combo an ordered list of elements. */
114
- export const presetExtensions = z.array(z.array(presetExtensionElement));
115
- /**
116
- * One preset as `GET /api/presets` returns it. Every preset is versioned, so
117
- * `active_version` is always an int; `conflicted` marks a QUARANTINED row (its
118
- * name collided with a foreign tool at boot — delete-only). `extensions` are the
119
- * ACTIVE version's extension combos, `output_schema` the optional author-set
120
- * output JSON Schema, and `input_schema` the optional author-set input JSON
121
- * Schema. Categorization tags live in the tool_meta overlay
122
- * (`GET /api/tool-meta`), never on the record. A drift throws `ApiSchemaError`;
123
- * nothing is coerced.
124
- */
125
- export const presetRecord = z.object({
126
- name: z.string(),
127
- base_tool: z.string(),
128
- description: z.string(),
129
- active_version: z.number(),
130
- extensions: presetExtensions,
131
- output_schema: z.record(z.string(), z.unknown()).nullable(),
132
- input_schema: z.record(z.string(), z.unknown()).nullable(),
133
- conflicted: z.boolean(),
134
- // The human-readable cause a row is quarantined (its name collided with a
135
- // foreign tool at boot), or `null` when the row is not conflicted. Rendered
136
- // verbatim; nothing is coerced.
137
- conflicted_reason: z.string().nullable(),
138
- // Sorted preset-name cross-references: `uses` names the presets this one
139
- // composes as tools; `used_by` names the presets that compose this one.
140
- uses: z.array(z.string()),
141
- used_by: z.array(z.string()),
142
- });
143
- export const presetList = z.array(presetRecord);
144
- /** `GET /api/presets/{name}` — the record plus the active version's baked kwargs. */
145
- export const presetDetail = presetRecord.extend({
146
- fixed_kwargs: z.record(z.string(), z.unknown()),
147
- });
148
- /**
149
- * One input injection of a state binding: BEFORE the run, a value is produced and
150
- * placed into the run input under `into` (the run-input field name). A template
151
- * `input` jq (`template_jq`, named `name` or attachment-qualified `template.name`) reads the
152
- * record — its input is the record (`.`), with declared params bound as `$params`; a
153
- * custom `jq` reads `{record, input}` → the value. Exactly one of `template_jq`/`jq`
154
- * carries the program; the other is `null`.
155
- */
156
- export const stateInjection = z.object({
157
- // `template_jq` names a program the attached state template ships; it is resolved
158
- // through compiled programs, never rendered as text, so it stays a plain string.
159
- template_jq: z.string().nullable().default(null),
160
- jq: templatedText.nullable().default(null),
161
- into: z.string(),
162
- });
163
- /**
164
- * One update of a state binding: AFTER the run, a template-relative op batch is
165
- * applied through the store. A NAMED update (`template_jq`) runs the template `update`
166
- * jq over `{record, input}` where `input` is built by `adapter` — a jq over
167
- * `{output, input}` that constructs the update jq's declared input object. A custom
168
- * `jq` runs over `{record, output, input}` → `[{op, path, value}]` and carries no
169
- * adapter. `op_id` is an optional idempotency-key expression. Exactly one of
170
- * `template_jq`/`jq` carries the program.
171
- */
172
- export const stateUpdate = z.object({
173
- // `template_jq` names a program the attached state template ships; resolved through
174
- // compiled programs, never rendered as text, so it stays a plain string.
175
- template_jq: z.string().nullable().default(null),
176
- jq: templatedText.nullable().default(null),
177
- // A named update's adapter is either an authored value or `null` (no adapter — the
178
- // run input passes through). When present it must carry a source (the platform
179
- // compile-checks a non-null adapter and refuses an empty one).
180
- adapter: requiredTemplatedText.nullable().default(null),
181
- op_id: templatedText.nullable().default(null),
182
- });
183
- /**
184
- * One attached state of a binding: the `state`, one or more `templates`
185
- * (attach-on-use — attached to the state on the binding's save), the `subject_expr`
186
- * (a jq yielding the record KEY — a bare key string OR a full subject object — that
187
- * this binding reads and updates), an optional `scope_expr` (a boolean jq predicate;
188
- * when it is false this state is skipped for the run), and the ordered
189
- * `input_injections` (before the run) and `updates` (after it).
190
- */
191
- export const stateAttach = z.object({
192
- state: z.string(),
193
- templates: z.array(z.string()).default([]),
194
- // The platform contract enforces a subject that carries a source; an empty one is
195
- // rejected here, never sent for the server to refuse.
196
- subject_expr: requiredTemplatedText,
197
- scope_expr: templatedText.nullable().default(null),
198
- input_injections: z.array(stateInjection).default([]),
199
- updates: z.array(stateUpdate).default([]),
200
- });
201
- /**
202
- * The one generic binding shape carried, optionally, on every runnable definition
203
- * (a preset, a channel route, a schedule, a hook) and repeated per node by the
204
- * flow engine. It attaches one or more states, each with its templates, subject,
205
- * injections and updates. `null` on a definition means no binding. Mirrors the
206
- * platform `StateBinding` document.
207
- */
208
- export const stateBinding = z.object({
209
- // At least one attached state: the platform contract refuses a zero-state binding, so
210
- // "no binding" is spelled `null` on the definition, never `{ states: [] }`.
211
- states: z.array(stateAttach).min(1),
212
- });
213
- /** The full version body a preset stores under `kind="preset"`. */
214
- export const presetBody = z.object({
215
- base_tool: z.string(),
216
- description: z.string(),
217
- fixed_kwargs: z.record(z.string(), z.unknown()),
218
- extensions: presetExtensions,
219
- output_schema: z.record(z.string(), z.unknown()).nullable(),
220
- input_schema: z.record(z.string(), z.unknown()).nullable(),
221
- // The optional door-layer state binding this preset applies around every run
222
- // (`null` when the preset touches no state).
223
- state_binding: stateBinding.nullable().default(null),
224
- });
225
- /**
226
- * One immutable version row (`GET /api/presets/{name}/versions[/{version}]`).
227
- * `is_current` flags the active version; `tags` here is the GENERIC per-version
228
- * label list (kind-agnostic) — the only preset tags that remain, distinct from the
229
- * removed record-level categorization and from the tool_meta overlay.
230
- */
231
- export const presetVersion = z.object({
232
- version: z.number(),
233
- body: presetBody,
234
- tags: z.array(z.string()),
235
- created_at: z.string(),
236
- is_current: z.boolean(),
237
- });
238
- export const presetVersionList = z.array(presetVersion);
239
- /** `POST /api/presets/{name}/rollback` — the re-pointed active version. */
240
- export const presetRollback = z.object({ name: z.string(), active_version: z.number() });
241
- /** `DELETE /api/presets/{name}` — the removed preset. */
242
- export const presetDeleted = z.object({ name: z.string(), deleted: z.literal(true) });
243
- /** `POST /api/presets/{name}/rename` — the re-keyed record identity. */
244
- export const presetRenamed = z.object({
245
- name: z.string(),
246
- renamed_from: z.string(),
247
- active_version: z.number(),
248
- });
249
- /**
250
- * `GET /api/presets/{name}/referees` — every OTHER preset whose active authored
251
- * composition names this one as a tool (the referees a rename would strand). An
252
- * empty list is the clean case; a non-empty list means a rename is blocked until
253
- * those presets are updated. A drift throws `ApiSchemaError`.
254
- */
255
- export const presetReferees = z.object({
256
- name: z.string(),
257
- referees: z.array(z.string()),
258
- });
259
- /**
260
- * `POST /api/presets/validate` — the dry-run verdict for a preset draft. `valid`
261
- * is the absence of an `error`; an invalid draft is a SUCCESSFUL validation (HTTP
262
- * 200), so `error` carries the server's verbatim message. A drift throws
263
- * `ApiSchemaError`.
264
- */
265
- export const presetValidation = z.object({
266
- valid: z.boolean(),
267
- error: z.string().nullable(),
268
- });
269
- /**
270
- * `PUT /api/presets/{name}/versions/{version}/tags` — the re-annotated version.
271
- * Version tags are labels on an immutable body, so this rebinds nothing; the
272
- * response echoes the identity plus the new tag list. A drift throws
273
- * `ApiSchemaError`.
274
- */
275
- export const presetVersionTags = z.object({
276
- name: z.string(),
277
- version: z.number(),
278
- tags: z.array(z.string()),
279
- });
280
- // -- tool_meta (the organizational overlay) ----------------------------------
281
- /**
282
- * One folder in the tool-organization tree (`tool_folders`). `parent_id` is `null`
283
- * for a root folder and otherwise the containing folder's `id`; nesting is endless
284
- * via the parent chain. Ids are UUID strings. A drift throws `ApiSchemaError`.
285
- */
286
- export const folderRecord = z.object({
287
- id: z.string(),
288
- name: z.string(),
289
- parent_id: z.string().nullable(),
290
- });
291
- /**
292
- * The organizational overlay for one tool, keyed by `tool_name`. `display_name`
293
- * overrides the rendered label (`display_name ?? name`); `folder_id` places the tool
294
- * in a folder (`null` = unfiled); `tags` is the user's editable categorization,
295
- * merged with the tool's read-only native tags by the UI; `badges` is the operator's
296
- * editable capability-label overlay, merged with the tool's read-only plugin-declared
297
- * badges by the UI (INFORMATIONAL, never an enforced gate); `hidden` is TRI-STATE —
298
- * `null` = defer to the plugin declaration, `true` = force hidden, `false` = force
299
- * visible. A drift throws `ApiSchemaError`.
300
- */
301
- export const toolMetaRecord = z.object({
302
- tool_name: z.string(),
303
- display_name: z.string().nullable(),
304
- folder_id: z.string().nullable(),
305
- tags: z.array(z.string()),
306
- badges: z.array(z.string()),
307
- hidden: z.boolean().nullable(),
308
- });
309
- /**
310
- * `GET /api/tool-meta` — the whole overlay in one read: the flat folder tree plus
311
- * every per-tool row. The UI builds the tree and merges the rows against the live
312
- * tool list client-side. A drift throws `ApiSchemaError`.
313
- */
314
- export const toolMetaOverlay = z.object({
315
- folders: z.array(folderRecord),
316
- meta: z.array(toolMetaRecord),
317
- });
318
- /** `DELETE /api/tool-meta/tools/{tool_name}` — the dropped overlay row (idempotent). */
319
- export const toolMetaDeleted = z.object({ tool_name: z.string(), deleted: z.literal(true) });
320
- /** `DELETE /api/tool-meta/folders/{folder_id}` — the removed empty folder. */
321
- export const folderDeleted = z.object({ folder_id: z.string(), deleted: z.literal(true) });
322
- // -- templates ---------------------------------------------------------------
323
- export const templateNames = z.array(z.string());
324
- export const templateDetail = z.object({ template: z.string(), schema: jsonSchema });
325
- export const templateUploaded = z.object({ path: z.string(), uploaded: z.literal(true) });
326
- export const templateDeleted = z.object({ path: z.string(), deleted: z.literal(true) });
327
- export const templateDirDeleted = z.object({ path: z.string(), deleted: z.literal(true) });
328
- export const templateRendered = z.object({ rendered: z.string() });
329
- export const templateCacheCleared = z.object({ cleared: z.literal(true) });
330
- // -- storage -----------------------------------------------------------------
331
- /** `GET /api/storage` — the storage-provider identity, or the absent sentinel
332
- * (`present: false`, a 200) when no storage-provider plugin is installed. */
333
- export const storageInfo = z.object({
334
- present: z.boolean(),
335
- provider: z.string().nullable(),
336
- module: z.string().nullable(),
337
- });
338
- /** `GET /api/storage/resources` — the full sorted resource-id list. */
339
- export const storageResourceList = z.object({ resources: z.array(z.string()) });
340
- /** `GET /api/storage/resources/{id}/stat` — an object's inferred content type;
341
- * `null` when the provider cannot infer one (e.g. an unknown suffix). */
342
- export const storageResourceStat = z.object({
343
- id: z.string(),
344
- content_type: z.string().nullable(),
345
- });
346
- /** `POST /api/storage/resources` — the stored object's id. */
347
- export const storageResourceStored = z.object({ id: z.string(), stored: z.literal(true) });
348
- /** `DELETE /api/storage/resources/{id}` — the removed object's id. */
349
- export const storageResourceDeleted = z.object({ id: z.string(), deleted: z.literal(true) });
350
- /** `DELETE /api/storage/dirs/{path}` — the removed directory subtree. */
351
- export const storageDirDeleted = z.object({ dir: z.string(), deleted: z.literal(true) });
352
- // -- backend identity + fleet (worker bus) -----------------------------------
353
- /** `GET /api/backend` — the execution-backend identity, or the absent sentinel
354
- * (`present: false`, a 200) when no backend plugin is registered. Backend identity
355
- * is kept distinct from the fleet: the fleet doors ride the app's worker bus and
356
- * need no backend at all. */
357
- export const backendInfo = z.object({
358
- present: z.boolean(),
359
- backend: z.string().nullable(),
360
- module: z.string().nullable(),
361
- });
362
- /** The two worker kinds that join the bus: an ASGI `serve` worker or a
363
- * `backend` runtime process. A drift throws `ApiSchemaError`. */
364
- export const workerKind = z.enum(['serve', 'backend']);
365
- /** The lifecycle state a worker advertises in its presence value: `ready` once its
366
- * resync has converged, `resyncing` while a boot/reconnect resync runs, `recycling`
367
- * once it has begun a graceful self-exit. A drift throws `ApiSchemaError`. */
368
- export const workerState = z.enum(['ready', 'resyncing', 'recycling']);
369
- /** The last op a worker applied, stamped onto its presence row after the terminal
370
- * reply. `outcome` is the free op-outcome string; `at` is the ISO instant it landed. */
371
- export const workerLastOp = z.object({
372
- op: z.string(),
373
- outcome: z.string(),
374
- at: z.string(),
375
- });
376
- /**
377
- * One live worker on the bus — a presence-census row. `name` is the stable slot
378
- * (`{kind}-{n}`) that doubles as a reload target; `generation` is the monotonic life
379
- * counter minted with the worker's claim; `kind` tells the two worker kinds apart;
380
- * `pid` is the process id. `joined_at`/`beat_at` are ISO instants (first-seen and last
381
- * heartbeat) for the cosmetic seen-since display; `state` is the advertised lifecycle
382
- * state; `stale` is SERVER-computed from the row's remaining presence TTL against the
383
- * bus cadence (a quiet row past the freshness bound — reconnecting or dead) and is the
384
- * only freshness signal a client reads, never a client-side threshold; `last_op` is the
385
- * worker's last applied op, or `null` before it has applied one.
386
- */
387
- export const fleetWorker = z.object({
388
- name: z.string(),
389
- kind: workerKind,
390
- pid: z.number(),
391
- generation: z.number(),
392
- joined_at: z.string(),
393
- beat_at: z.string(),
394
- state: workerState,
395
- stale: z.boolean(),
396
- last_op: workerLastOp.nullable(),
397
- });
398
- /**
399
- * `GET /api/fleet/workers` — the live worker fleet from the bus presence census.
400
- * The census lists ALL subscribed workers (ASGI `serve` workers and backend-runtime
401
- * processes alike), not only backend workers. A census read that fails surfaces as a
402
- * loud 500, never a fabricated empty fleet.
403
- */
404
- export const fleetWorkers = z.object({ workers: z.array(fleetWorker) });
405
- /**
406
- * One worker's outcome within a fleet broadcast. `applied`/`failed` are terminal wire
407
- * replies from a subscriber; `missing`/`departed`/`timed_out` are publisher-computed
408
- * from the presence census (a silent worker never sends them); `resyncing`/`recycling`/
409
- * `stale` are the gap outcomes for a row that failed the ready+fresh gate — reported as
410
- * its own actual condition rather than dropped (`resyncing` converges on its resync,
411
- * `recycling` is departing, `stale` is a quiet row past the freshness bound carrying no
412
- * convergence promise).
413
- */
414
- export const fleetOutcome = z.enum([
415
- 'applied',
416
- 'failed',
417
- 'missing',
418
- 'departed',
419
- 'timed_out',
420
- 'resyncing',
421
- 'recycling',
422
- 'stale',
423
- ]);
424
- /**
425
- * One worker's result within a {@link fleetResult}. `payload` carries query-op data
426
- * (opaque to the UI); `error` carries a failed apply's message; `detail` carries the
427
- * publisher's report text for a computed gap outcome (`missing`/`departed`/`timed_out`/
428
- * `resyncing`/`recycling`/`stale`). A drift throws `ApiSchemaError`.
429
- */
430
- export const fleetWorkerResult = z.object({
431
- name: z.string(),
432
- outcome: fleetOutcome,
433
- payload: jsonValue,
434
- error: z.string().nullable(),
435
- detail: z.string().nullable(),
436
- });
437
- /**
438
- * The awaited per-worker report of one fleet broadcast. Two honest shapes ride the
439
- * same schema. Reachable (`reachable: true`): `results` holds one entry per expected
440
- * worker (the serving worker's own entry included) plus the gap rows, so an unconfirmed
441
- * sibling is a visible `failed`/`missing`/`departed`/`timed_out`/`resyncing`/`recycling`/
442
- * `stale` entry, never a silent stale worker. Bus-unreachable (`reachable: false`): the
443
- * transport failed before any worker could reply, so `results` is empty and only `error`
444
- * is carried. This is the bare body of `POST /api/fleet/reload-config` and the single-MCP
445
- * reload; it is also the body the mode-wrapped {@link fleetReportFanout} carries under its
446
- * `fleet` / `unreachable` modes.
447
- */
448
- export const fleetResult = z.object({
449
- op: z.string(),
450
- reachable: z.boolean(),
451
- local_only: z.boolean(),
452
- results: z.array(fleetWorkerResult),
453
- error: z.string().nullable(),
454
- });
455
- /**
456
- * `POST /api/fleet/reload-config` — soft-restart the fleet and report per-worker. The
457
- * serving worker applies its own reload first, then broadcasts; the returned report
458
- * names every worker's outcome. A failed local apply does not abort the broadcast, so
459
- * the report is embedded even on the convergence-failure path.
460
- */
461
- export const fleetReloadResult = fleetResult;
462
- /**
463
- * The mode-wrapped fan-out summary a mutation embeds under its `fanout` field. Three
464
- * honest modes, discriminated on `mode`:
465
- * - `local-only` — the broadcast reached no sibling (a lone worker / no bus), a
466
- * benign single-process note.
467
- * - `fleet` — a reachable multi-worker broadcast; carries the full {@link
468
- * fleetResult} so an unconfirmed sibling is visible.
469
- * - `unreachable` — the bus itself could not be reached; carries the
470
- * bus-unreachable {@link fleetResult} (no worker list, only `error`).
471
- *
472
- * Every mutation whose response embeds this parses it EXPLICITLY: zod's default
473
- * unknown-key stripping would otherwise silently drop the field, hiding a failed
474
- * propagation from the shared fleet-report handler. A drift throws `ApiSchemaError`.
475
- */
476
- export const fleetReportFanout = z.discriminatedUnion('mode', [
477
- z.object({ mode: z.literal('local-only'), note: z.string() }),
478
- fleetResult.extend({ mode: z.literal('fleet') }),
479
- fleetResult.extend({ mode: z.literal('unreachable') }),
480
- ]);
481
- // -- manifest / mcp ----------------------------------------------------------
482
- /**
483
- * The back-reference a connector writes onto a manifest MCP entry to mark it
484
- * connector-owned: the connection and the provider sub-service that own it.
485
- * Hand-authored entries carry no `managed` marker.
486
- */
487
- export const connectorRef = z.object({
488
- connection_id: z.string(),
489
- provider_id: z.string(),
490
- sub_service: z.string(),
491
- });
492
- /**
493
- * One entry of the live manifest's MCP section. Only the `managed` provenance
494
- * marker is typed — the connect/disconnect flows write it server-side to point a
495
- * connector-owned entry back at its connection, and the editor renders such
496
- * entries read-only. `.nullish()` because a hand-authored entry carries `null`
497
- * (present) or omits the key. Every other field (title, config, extensions) rides
498
- * through `.loose()` unmodeled so the config editor keeps editing the whole
499
- * entry — narrowing it to a typed subset would silently strip the fields it saves.
500
- */
501
- export const manifestMcpEntry = z
502
- .object({
503
- managed: connectorRef.nullish(),
504
- })
505
- .loose();
506
- export const manifestView = z.object({
507
- mcp: z.array(manifestMcpEntry),
508
- user_tools: z.array(z.string()),
509
- });
510
- /**
511
- * One `!ENV ${VAR[:default]}` marker the manifest's MCP section carries
512
- * (`GET /api/manifest/mcp-env-refs`) — NAMES and BOOLEANS only, NEVER values.
513
- * `pointer` is the RFC 6901 json-pointer of the leaf (`/mcp/<i>/config/...`);
514
- * `has_default` is whether the marker carries a `:default`; `set` is whether the
515
- * var resolves in the EFFECTIVE env (the resolution truth the install-time
516
- * dangling refusal reads), so a var supplied by the deployment shows green, never
517
- * a false red. The checklist derives from THIS alone — no env value is ever fetched.
518
- */
519
- export const mcpEnvRef = z.object({
520
- var: z.string(),
521
- pointer: z.string(),
522
- has_default: z.boolean(),
523
- set: z.boolean(),
524
- });
525
- export const mcpEnvRefs = z.array(mcpEnvRef);
526
- /**
527
- * The apply result every config-writer mutation returns (`POST /api/mcp-config`,
528
- * `POST /api/config/env`): the serving worker's local reload result (`status` +
529
- * `env_keys`) plus the mode-wrapped fleet fan-out of the reload it broadcast. The
530
- * `fanout` is parsed EXPLICITLY so the shared fleet-report handler can surface a
531
- * failed propagation — zod's unknown-key stripping would otherwise hide it. A drift
532
- * throws `ApiSchemaError`.
533
- */
534
- export const reloadConfigResult = z.object({
535
- status: z.string(),
536
- env_keys: z.number(),
537
- fanout: fleetReportFanout,
538
- });
539
- // -- system kinds ------------------------------------------------------------
540
- // `GET /api/system/kinds` — one row per pluggable kind (identity, accounts,
541
- // monitoring, storage, backend, channels, webhook_verifiers, config,
542
- // studio_plugins). `state` is the kind's active/default/off status: `active` = a
543
- // real plugin serves it, `default` = a built-in fallback serves it, `off` =
544
- // nothing is registered (a legal state, reported not "fixed"). `plugin` is the
545
- // provider/module name when known, else null. Plain (non-strict) `z.object` so a
546
- // server that adds a field to the row still parses.
547
- export const kindStatus = z.object({
548
- kind: z.string(),
549
- state: z.enum(['active', 'default', 'off']),
550
- plugin: z.string().nullable(),
551
- detail: z.string(),
552
- });
553
- export const kindStatusList = z.array(kindStatus);
554
- /**
555
- * `POST /api/tools/{name}/extensions` — the apply result. The write persists the
556
- * manifest, reloads locally, then broadcasts the reload to the whole fleet. The
557
- * response is the one apply-result shape every config-writer returns: the local
558
- * reload result (`status` + `env_keys`) plus the mode-wrapped fleet `fanout`. The
559
- * fan-out rides EVERY deployment (a lone worker reports `local-only`), so this flat
560
- * shape parses every save and the shared fleet-report handler renders any failed
561
- * propagation from `fanout`.
562
- */
563
- export const toolExtensionsApplyResult = reloadConfigResult;
564
- export const mcpStatus = z.object({
565
- bound: z.record(z.string(), z.array(z.string())),
566
- failed: z.array(z.object({ title: z.string(), status: z.string() })),
567
- });
568
- /**
569
- * `POST /api/mcp-status/{title}/reload` — re-probe a single MCP server and broadcast
570
- * the reload to the fleet. The response is the bare per-worker {@link fleetResult}
571
- * (the serving worker's re-probe rides its own `results` entry as the payload); there
572
- * is no local/workers split. A drift throws `ApiSchemaError`.
573
- */
574
- export const mcpReloadResult = fleetResult;
575
- // -- sub-mcp -----------------------------------------------------------------
576
- /**
577
- * One registered sub-MCP mount as it rides the registry list and the projection:
578
- * the tools it exposes and the transport it serves on. In `GET /api/sub-mcp` the
579
- * slug is the map key, so the mount value carries no slug; {@link subMcpEntry}
580
- * extends this with `slug` for the capability projection where mounts are a flat
581
- * list. `transport` is always present on the wire (the server defaults it to
582
- * `http`), so it is required — its absence is a drift, not a legal shape.
583
- */
584
- export const subMcpMount = z.object({
585
- tools: z.array(z.string()),
586
- transport: z.string(),
587
- });
588
- export const subMcpList = z.record(z.string(), subMcpMount);
589
- export const subMcpCreated = z.object({
590
- slug: z.string(),
591
- tools: z.array(z.string()),
592
- transport: z.string(),
593
- });
594
- export const subMcpRemoved = z.object({ slug: z.string(), removed: z.literal(true) });
595
- // -- config ------------------------------------------------------------------
596
- export const envConfig = z.object({
597
- env: z.record(z.string(), z.string()),
598
- secret_keys: z.array(z.string()),
599
- });
600
- /**
601
- * `GET /api/config/mode` — the active config backend mode (`file` / `k8s`). The
602
- * skeleton emits only `config_mode`; `read_only` is a UI-side gate, tolerated-absent
603
- * and defaulting to `false` (writable) so a normal response never drifts, while a
604
- * genuinely read-only deployment can opt in by emitting `read_only: true`.
605
- */
606
- export const configMode = z.object({
607
- config_mode: z.string(),
608
- read_only: z.boolean().default(false),
609
- });
610
- // -- settings profiles -------------------------------------------------------
611
- // The admin-only `/api/config/profiles/*` surface. Secrets ride the wire in the
612
- // clear (secret/fenced routes); masking is CLIENT-SIDE per the secret-mask union. A drift
613
- // on any of these throws `ApiSchemaError`.
614
- /**
615
- * `GET /api/config/profiles` — one row per stored profile. The list is
616
- * names+descriptions ONLY (action=read); no `env`, no `secret_keys`, no bodies.
617
- * (No `last_modified`: the list carries identity, not history — version metadata
618
- * is the `/versions` surface — so the list renders no last-modified column.)
619
- */
620
- export const settingsProfileSummary = z.object({
621
- name: z.string(),
622
- description: z.string(),
623
- });
624
- export const settingsProfileList = z.array(settingsProfileSummary);
625
- /**
626
- * `GET /api/config/profiles/{name}` and each version body — the stored profile
627
- * document (action=secret). `env` carries REAL values; `secret_keys` is the
628
- * per-profile secret-marks band folded into the secret-mask union for masking.
629
- */
630
- export const settingsProfileBody = z.object({
631
- description: z.string(),
632
- env: z.record(z.string(), z.string()),
633
- secret_keys: z.array(z.string()),
634
- });
635
- /** `PUT /api/config/profiles/{name}` — a minimal save ack; `version` is the new store version. */
636
- export const settingsProfileSaved = z.object({
637
- ok: z.literal(true),
638
- version: z.number().optional(),
639
- });
640
- /** `DELETE /api/config/profiles/{name}` — a minimal removal ack. */
641
- export const settingsProfileDeleted = z.object({ ok: z.literal(true) });
642
- /** `POST /api/config/profiles/{name}/rollback` — the restore ack; `version` is the resulting active version. */
643
- export const settingsProfileRollback = z.object({
644
- ok: z.literal(true),
645
- version: z.number(),
646
- });
647
- /**
648
- * `POST /api/config/profiles/{name}/diff` — the SAVED profile vs the stored env
649
- * (action=secret). `changed` carries real `old`/`new` values (UI masks via
650
- * JsonDiff); `recycle_keys`/`refused_keys` are the diff's recycle-class and
651
- * boundary-refused key names, called out in the UI before apply.
652
- */
653
- export const settingsProfileDiff = z.object({
654
- added: z.array(z.string()),
655
- removed: z.array(z.string()),
656
- changed: z.array(z.object({ key: z.string(), old: z.string(), new: z.string() })),
657
- recycle_keys: z.array(z.string()),
658
- refused_keys: z.array(z.string()),
659
- });
660
- /**
661
- * `POST /api/config/profiles/{name}/apply` — the dedicated `profile_apply_response`
662
- * (action=fenced, destructive). NAMES-ONLY (no env value in any
663
- * section): `hot` is the hot-swapped key names; `recycle` is the orchestrator's
664
- * per-worker section — one `{name, kind, status, generation_before}` row per recycled
665
- * target, `generation_before` the life that was recycled, the applier's OWN row carrying
666
- * the `self-deferred` status literal when the diff has serve-affecting recycle keys;
667
- * `fresh` is the per-kind list of NEW ready lives observed since the pre-apply snapshot
668
- * (each `{name, kind, generation}`), evidence of fresh capacity and never claimed as any
669
- * target's successor; `refused` is `[]` on success by construction (populated
670
- * `{key, reason}` only on a refusal), the populated refusal surfaces being the diff
671
- * preview and the refusal error report. `fanout` is the mode-wrapped fleet broadcast,
672
- * parsed EXPLICITLY so a failed propagation reaches the shared fleet-report handler. A
673
- * drift throws `ApiSchemaError`.
674
- */
675
- export const profileApplyResponse = z.object({
676
- hot: z.array(z.string()),
677
- recycle: z.array(z.object({
678
- name: z.string(),
679
- kind: z.string(),
680
- status: z.string(),
681
- generation_before: z.number(),
682
- })),
683
- fresh: z.array(z.object({ name: z.string(), kind: z.string(), generation: z.number() })),
684
- refused: z.array(z.object({ key: z.string(), reason: z.string() })),
685
- fanout: fleetReportFanout,
686
- });
687
- /**
688
- * `GET /api/config/profiles/{name}/versions` — version METADATA only (action=read;
689
- * no bodies), the generic versioned-store row minus `body`. `is_current` flags the
690
- * active pointer.
691
- */
692
- export const settingsProfileVersionMeta = z.object({
693
- version: z.number(),
694
- tags: z.array(z.string()),
695
- created_at: z.string(),
696
- is_current: z.boolean(),
697
- });
698
- export const settingsProfileVersionList = z.array(settingsProfileVersionMeta);
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
- // -- connectors --------------------------------------------------------------
708
- export const subServiceView = z.object({
709
- id: z.string(),
710
- display_name: z.string(),
711
- description: z.string(),
712
- scopes: z.array(z.string()),
713
- });
714
- export const configFieldView = z.object({
715
- key: z.string(),
716
- label: z.string(),
717
- target: z.enum(['env', 'header']),
718
- required: z.boolean(),
719
- secret: z.boolean(),
720
- });
721
- export const providerView = z.object({
722
- id: z.string(),
723
- display_name: z.string(),
724
- description: z.string(),
725
- // Required and a real URL: the contract's `ProviderDescriptor.icon_url` runs
726
- // `check_web_url(schemes=("https",))`, which rejects an empty/relative value, so
727
- // an empty or malformed one here is drift and fails the parse loudly.
728
- icon_url: z.url(),
729
- kind: z.enum(['oauth', 'none']),
730
- origin: z.enum(['system', 'community']),
731
- category: z.string(),
732
- sub_services: z.array(subServiceView),
733
- config_fields: z.array(configFieldView),
734
- });
735
- /**
736
- * One `connector_category` grouping row served alongside the catalog: the UI's
737
- * label and sort key for the providers it groups, so the list can group, order,
738
- * and label providers by category without a second read.
739
- */
740
- export const connectorCategoryView = z.object({
741
- id: z.string(),
742
- display_name: z.string(),
743
- sort_order: z.number(),
744
- });
745
- /**
746
- * `GET /api/connectors/providers` — the provider catalog: the providers plus the
747
- * category groupings the UI arranges them under. Reshaped from a bare provider
748
- * array so the category label/order surface rides the same read.
749
- */
750
- export const providers = z.object({
751
- providers: z.array(providerView),
752
- categories: z.array(connectorCategoryView),
753
- });
754
- export const connectionView = z.object({
755
- connection_id: z.string(),
756
- provider_id: z.string(),
757
- alias: z.string(),
758
- kind: z.enum(['oauth', 'none']),
759
- account_identity: z.string().nullable(),
760
- enabled_sub_services: z.array(z.string()),
761
- granted_scopes: z.array(z.string()),
762
- // Sub-services whose MCP server did not answer a live reachability probe.
763
- // Populated only on the single-connection GET (the list projection is
764
- // probe-free and always sends `[]`). Reachability is distinct from
765
- // `auth_health_state`: a healthy connection can still have a down sub-service.
766
- // Defaulted so a projection that omits it (an older worker mid-rollout) still
767
- // parses rather than false-failing the whole view.
768
- unreachable_sub_services: z.array(z.string()).default([]),
769
- auth_health_state: z.enum(['healthy', 'reconnect_required', 'refresh_failing']),
770
- created_at: z.string(),
771
- });
772
- export const connectionsList = z.object({
773
- items: z.array(connectionView),
774
- total: z.number(),
775
- // Count of connections whose `auth_health_state` is not healthy across the whole
776
- // set (independent of any filter/limit) — the attention-badge count. Optional:
777
- // no UI consumes it, and an older worker mid-rollout omits it.
778
- unhealthy: z.number().optional(),
779
- });
780
- // A connector mutation that writes the manifest embeds the mode-wrapped fleet
781
- // `fanout` of the reload it broadcast; it is `null` when the response path mutated no
782
- // manifest (an OAuth START opens a flow before any write). Parsed EXPLICITLY so the
783
- // shared fleet-report handler surfaces a failed propagation on every connector flow —
784
- // zod's unknown-key stripping would otherwise drop it silently.
785
- export const startConnectResult = z.union([
786
- z.object({ flow_id: z.string(), authorize_url: z.string() }),
787
- z.object({
788
- connection_id: z.string(),
789
- added_manifest_entries: z.array(z.string()),
790
- fanout: fleetReportFanout.nullable(),
791
- }),
792
- ]);
793
- // A reconnect START opens an OAuth flow and writes no manifest, so it carries no
794
- // fan-out (the manifest write lands when the flow completes).
795
- export const reconnectResult = z.object({ flow_id: z.string(), authorize_url: z.string() });
796
- export const disconnectResult = z.object({
797
- connection_id: z.string(),
798
- upstream_revoke_outcome: z.enum(['success', 'failed', 'skipped']),
799
- upstream_revoke_status: z.number().nullable(),
800
- removed_manifest_entries: z.array(z.string()),
801
- fanout: fleetReportFanout.nullable(),
802
- });
803
- export const patchSubServicesResult = z.object({
804
- connection_id: z.string(),
805
- enabled_sub_services: z.array(z.string()),
806
- consent_required: z.boolean(),
807
- flow_id: z.string().nullable(),
808
- authorize_url: z.string().nullable(),
809
- added_manifest_entries: z.array(z.string()),
810
- removed_manifest_entries: z.array(z.string()),
811
- // `null` on a consent-only toggle that forks an OAuth flow without a manifest write.
812
- fanout: fleetReportFanout.nullable(),
813
- });
814
- export const oauthCompleteResult = z.discriminatedUnion('kind', [
815
- z.object({
816
- kind: z.literal('success'),
817
- connection_id: z.string(),
818
- return_url: z.string(),
819
- // `null` when completing the flow mutated no manifest (a reconcile that changed
820
- // nothing).
821
- fanout: fleetReportFanout.nullable(),
822
- }),
823
- z.object({ kind: z.literal('failed'), reason: z.string() }),
824
- z.object({ kind: z.literal('cancelled'), message: z.string() }),
825
- ]);
826
- // -- studio plugin registry --------------------------------------------------
827
- export const studioPluginManifest = z.object({
828
- name: z.string(),
829
- version: z.string(),
830
- api_version: z.number(),
831
- entry: z.string(),
832
- integrity: z.record(z.string(), z.string()),
833
- contributions: z.object({
834
- tool_panels: z.record(z.string(), z.string()).default({}),
835
- pages: z.array(z.string()).default([]),
836
- settings_tabs: z.array(z.string()).default([]),
837
- // Optional with a default so a manifest without the field still parses; the
838
- // non-strict object would otherwise SILENTLY STRIP an undeclared field, so a
839
- // server that serves `nav_entries` would never reach the UI.
840
- nav_entries: z.array(z.string()).default([]),
841
- }),
842
- });
843
- export const studioPluginRegistry = z.array(studioPluginManifest);
844
- /**
845
- * The page window every paged read door carries; `next_page` is null on the last
846
- * page. `truncated` is TRUE when the door had more matches than it would scan or
847
- * return under its cap — the UI must surface that LOUDLY, never a silent cut — and
848
- * false whenever the window is exhaustive.
849
- */
850
- const pageWindow = {
851
- total: z.number(),
852
- page: z.number(),
853
- page_size: z.number(),
854
- next_page: z.number().nullable(),
855
- truncated: z.boolean(),
856
- };
857
- // -- interactions ------------------------------------------------------------
858
- // Shapes match the skeleton's interactions SSE contract: the SSE
859
- // `interaction.add` frame carries the question and its answer format; the answer
860
- // door returns { interaction_id, status }. `answered` is not a wire field — it
861
- // is a client-side flag the stream hook flips on an `interaction.answered` event.
862
- export const answerFormat = z.enum(['text', 'confirm', 'select', 'form', 'external']);
863
- /** The kind of a question's display-only media item: a remote/inline image, or a link. */
864
- export const mediaKind = z.enum(['image', 'link']);
865
- /**
866
- * One item of a question's display-only media. Applied PER ITEM by the renderer
867
- * (never as a strict array on the frame): the frame's `media` field is a loose
868
- * `z.array(z.unknown())`, and the renderer `safeParse`s each item with this schema
869
- * so one malformed/hostile item is a loud per-item notice, not a whole-frame parse
870
- * failure that would silently vanish an answerable question.
871
- *
872
- * `caption` is `.nullish()` (absent OR `null` both parse to a caption-less item):
873
- * a caption-less item may arrive with the key omitted OR with an explicit `null`, so
874
- * `.optional()` here would false-fail the explicit `null` and render a good image as
875
- * malformed. `url` and `kind` are the only load-bearing fields; the image
876
- * src scheme gate and the link neutralization are enforced by the renderer.
877
- */
878
- export const interactionMediaItem = z.object({
879
- kind: mediaKind,
880
- url: z.string(),
881
- caption: z.string().nullish(),
882
- });
883
- /**
884
- * One per-send choice for a `form` property whose schema is a string (or array of
885
- * strings). `value` is what the answer carries; `label` (absent OR null both parse
886
- * to no label) is the human text shown in its place. A send may replace a property's
887
- * `enum` this way for one ask without republishing the form. Applied per property by
888
- * the form preview (safeParsed), so a malformed entry is a loud notice, never silent.
889
- */
890
- export const formOption = z.object({
891
- value: z.string(),
892
- label: z.string().nullish(),
893
- });
894
- /**
895
- * Per-send `form` data: `values` prefills top-level properties (keyed by property
896
- * name), `options` supplies per-send choice lists (keyed by property name). Both
897
- * default to empty. The form preview safeParses this off `format_payload.data`, so a
898
- * malformed block is a loud notice rather than a whole-frame parse failure.
899
- */
900
- export const formData = z.object({
901
- values: z.record(z.string(), z.unknown()).default({}),
902
- options: z.record(z.string(), z.array(formOption)).default({}),
903
- });
904
- /**
905
- * One page of a stepped `form`: a `title` and the ordered top-level property names it
906
- * groups. `format_payload.pages` is the ordered list of these; absent = one page. The
907
- * form preview safeParses the list, so a malformed page is a loud notice, never silent.
908
- */
909
- export const formPage = z.object({
910
- title: z.string(),
911
- fields: z.array(z.string()),
912
- });
913
- /** The `form` pages list (`format_payload.pages`), safeParsed as a whole by the preview. */
914
- export const formPages = z.array(formPage);
915
- export const interaction = z.object({
916
- interaction_id: z.string(),
917
- group_id: z.string(),
918
- question: z.string().default(''),
919
- answer_format: answerFormat,
920
- // Format-specific payload: select options, form JSON schema (with optional per-send
921
- // `data` and `pages`), external url. The wire sends `null` for formats that carry
922
- // none (text/confirm); it is normalized to `{}` so consumers always see an object.
923
- format_payload: z
924
- .record(z.string(), z.unknown())
925
- .nullish()
926
- .transform((value) => value ?? {}),
927
- created_at: z.string(),
928
- timeout_at: z.string(),
929
- // Whether the answer body is sensitive and never persisted server-side. Rides
930
- // every add frame; an absent value (older frame) normalizes to false.
931
- sensitive: z.boolean().default(false),
932
- // True only for an external question answered by a signed server-to-server
933
- // callback (never by a human); the frame never carries the verifier object.
934
- // Absent/undefined for every other question.
935
- server_verified: z.boolean().optional(),
936
- // Name of the channel plugin the question was also delivered through (e.g.
937
- // "telegram"); absent when the question lives only in this inbox.
938
- channel: z.string().optional(),
939
- // Interaction attribution, each additive and absent when unset. `recipient` is
940
- // the channel delivery address the question went to; `origin` is the run id of
941
- // the background tool run that asked; `audience` is the user_id the question is
942
- // addressed to. Display/binding data only, never an authorization axis; a
943
- // present non-string is malformed.
944
- recipient: z.string().optional(),
945
- origin: z.string().optional(),
946
- audience: z.string().optional(),
947
- // Display-only media shown WITH the question (images and/or links). Absent when
948
- // the question carries none. Deliberately LOOSE (`z.array(z.unknown())`): each
949
- // item is `safeParse`d per item by the renderer against `interactionMediaItem`,
950
- // so one malformed/hostile item can never fail the whole frame parse and hide an
951
- // answerable question. A present non-array value is still malformed.
952
- media: z.array(z.unknown()).optional(),
953
- });
954
- /** The answer door's response — the blocked caller was woken. */
955
- export const interactionAnswered = z.object({
956
- interaction_id: z.string(),
957
- status: z.string(),
958
- });
959
- /**
960
- * The cancel door's response — a pending ask was WITHDRAWN without an answer (the
961
- * asking flow does not resume). `status` is `"cancelled"`; the shape mirrors the
962
- * answer door's terminal reply, but the door is distinct so it carries its own
963
- * schema.
964
- */
965
- export const interactionCancelled = z.object({
966
- interaction_id: z.string(),
967
- status: z.string(),
968
- });
969
- /**
970
- * A page of pending interactions from `GET /api/interactions?page=&pageSize=`. Each
971
- * item is the same record the stream's `interaction.add` frame carries. The pending
972
- * set is the paged base the tail-only stream applies live deltas over.
973
- */
974
- export const interactionsPage = z.object({
975
- items: z.array(interaction),
976
- ...pageWindow,
977
- });
978
- // -- channels ----------------------------------------------------------------
979
- // The installed channel-plugin catalog: names registered via the manifest's
980
- // channel modules. A deployment with none bound answers questions only in this
981
- // inbox (the Studio default).
982
- export const channels = z.object({
983
- channels: z.array(z.string()),
984
- });
985
- // -- conversations -----------------------------------------------------------
986
- // The cross-channel conversation monitor's READ side: the stored routing rows
987
- // (the route picker) from `GET /api/conversations`, a route's threads from
988
- // `GET /api/conversations/{route}/threads`, and one thread's transcript from
989
- // `GET /api/conversations/{route}/transcript?thread_id=`. Timestamps are float
990
- // epoch seconds, not ISO strings, and both paged doors carry the same window
991
- // fields; the transcript adds the direction it was read in.
992
- /** The inbound door a route is bound to: the authed API, or a channel medium. */
993
- export const conversationDoor = z.enum(['api', 'channel']);
994
- /** What a route's turn runs: a registered agent, or a tool dispatch. */
995
- export const conversationTargetKind = z.enum(['agent', 'tool']);
996
- /**
997
- * A route's default control mode (`initial_mode`) when a thread carries no
998
- * per-thread override: `agent` runs the target turn, `manual` suppresses it for an
999
- * operator to answer. The same value vocabulary as a per-thread mode override, but
1000
- * a distinct concept (the route default vs. one thread's override).
1001
- */
1002
- export const conversationMode = z.enum(['agent', 'manual']);
1003
- /**
1004
- * Where a record sits between intake and a terminal outcome. `failed` is the one
1005
- * the monitor surfaces loudly; `shed` and `silent` are terminal by design and
1006
- * never send.
1007
- */
1008
- export const conversationDeliveryStatus = z.enum([
1009
- 'accepted',
1010
- 'pending_delivery',
1011
- 'provisional',
1012
- 'delivered',
1013
- 'failed',
1014
- 'shed',
1015
- 'silent',
1016
- ]);
1017
- /** The nature of a turn's outcome, orthogonal to where its delivery stands. */
1018
- export const conversationAnswerStatus = z.enum(['answered', 'error', 'silent']);
1019
- /**
1020
- * Who authored a record's outgoing side. `client` is the ordinary turn the flow
1021
- * answered; `operator` is a message a human sent into the thread, whose text
1022
- * rides `answer` (its `inbound_text` is empty).
1023
- */
1024
- export const conversationRecordOrigin = z.enum(['client', 'operator']);
1025
- /**
1026
- * A stored routing row as a read door returns it, its `callback_secret` withheld.
1027
- *
1028
- * `initial_mode`, `turns_per_hour_override`, and `error_reply_text` carry defaults
1029
- * because a stored row always returns them, while a hand-built stub/fixture may
1030
- * omit them — the default keeps such a payload valid without inventing a field the
1031
- * server never sends.
1032
- */
1033
- export const conversationRoute = z.object({
1034
- route_name: z.string(),
1035
- door: conversationDoor,
1036
- target_kind: conversationTargetKind,
1037
- target_name: z.string(),
1038
- payload_expr: z.string().nullable(),
1039
- reply_expr: z.string().nullable(),
1040
- initial_mode: conversationMode.default('agent'),
1041
- execution_key: z.string(),
1042
- channel: z.string().nullable(),
1043
- our_identity: z.string().nullable(),
1044
- callback_url: z.string().nullable(),
1045
- turns_per_hour_override: z.number().int().nullable().default(null),
1046
- error_reply_text: z.string().nullable().default(null),
1047
- execution_key_fingerprint: z.string(),
1048
- // The door-layer state binding the backend stores on the route's target config and
1049
- // returns on read (`null` when the route touches no state) — the edit form prefills it.
1050
- state_binding: stateBinding.nullable().default(null),
1051
- });
1052
- export const conversationRoutes = z.object({
1053
- items: z.array(conversationRoute),
1054
- total: z.number(),
1055
- });
1056
- /**
1057
- * The client-facing create/edit body for a conversation route — the wire model of
1058
- * `tai42_contract.conversations.ConversationRouteCreate`. `POST
1059
- * /api/conversations/{route_name}` is an UPSERT, so this one shape is both the
1060
- * create AND the edit body. `route_name` rides the URL path; it is carried here too
1061
- * (the door rejects a body whose `route_name` disagrees with the path).
1062
- *
1063
- * The contract's cross-field validators are mirrored where the form authors this
1064
- * body: `payload_expr`/`reply_expr` are tool-only; an `api` door carries a
1065
- * `callback_url` (and no channel identity); a `channel` door carries `channel` +
1066
- * `our_identity` (and no callback). Sent explicitly (including `null`) so an edit
1067
- * clears a field the operator emptied rather than leaving the stored value.
1068
- */
1069
- export const conversationRouteCreate = z.object({
1070
- route_name: z.string(),
1071
- door: conversationDoor,
1072
- target_kind: conversationTargetKind,
1073
- target_name: z.string().min(1),
1074
- payload_expr: z.string().nullable().default(null),
1075
- reply_expr: z.string().nullable().default(null),
1076
- initial_mode: conversationMode.default('agent'),
1077
- execution_key: z.string().min(1),
1078
- channel: z.string().nullable().default(null),
1079
- our_identity: z.string().nullable().default(null),
1080
- callback_url: z.string().nullable().default(null),
1081
- turns_per_hour_override: z.number().int().positive().nullable().default(null),
1082
- error_reply_text: z.string().min(1).max(2000).nullable().default(null),
1083
- // The optional door-layer state binding this route applies around every turn's
1084
- // tool run (`null` when the route touches no state).
1085
- state_binding: stateBinding.nullable().default(null),
1086
- });
1087
- /**
1088
- * The create/replace reply: whether THIS call created the row (vs. replaced an
1089
- * existing one), the row itself (its `callback_secret` withheld), and the
1090
- * `callback_secret` returned ONCE for an `api`-door row (`null` for a `channel`
1091
- * row). The secret signs delivery callbacks and is never re-read from any door, so
1092
- * a caller that needs it must capture it from this reply.
1093
- */
1094
- export const conversationRouteWritten = z.object({
1095
- created: z.boolean(),
1096
- route_name: z.string(),
1097
- route: conversationRoute,
1098
- callback_secret: z.string().nullable(),
1099
- });
1100
- /**
1101
- * The delete reply: `removed` says whether THIS call removed the routing row (a
1102
- * retryable reclamation of orphaned thread indexes answers `false` rather than a
1103
- * 404).
1104
- */
1105
- export const conversationRouteDeleted = z.object({
1106
- removed: z.boolean(),
1107
- route_name: z.string(),
1108
- });
1109
- /**
1110
- * One thread of a route, summarized from its newest record. A `thread_id` holds
1111
- * an api-door address (`{principal}/{end user}`, percent-encoded), so it never
1112
- * goes back into a URL as a path segment — the transcript door takes it as a
1113
- * query value.
1114
- */
1115
- export const conversationThread = z.object({
1116
- thread_id: z.string(),
1117
- client_address: z.string(),
1118
- last_activity_at: z.number(),
1119
- message_count: z.number(),
1120
- last_delivery_status: conversationDeliveryStatus,
1121
- });
1122
- export const conversationThreadsPage = z.object({
1123
- items: z.array(conversationThread),
1124
- ...pageWindow,
1125
- });
1126
- /**
1127
- * One exchange: the visitor's `inbound_text` — every record answers a message, so
1128
- * both projections always carry it — and the agent's `answer`, plus where that
1129
- * answer's delivery stands. The fields after `updated_at` are ADMIN ONLY —
1130
- * the same door serves a caller-scoped projection that withholds them — so they
1131
- * are optional here and absence means "not published to this reader".
1132
- */
1133
- export const conversationMessage = z.object({
1134
- message_id: z.string(),
1135
- route_name: z.string(),
1136
- door: conversationDoor,
1137
- thread_id: z.string(),
1138
- client_address: z.string(),
1139
- caller_principal: z.string().nullable(),
1140
- inbound_text: z.string(),
1141
- answer_status: conversationAnswerStatus.nullable(),
1142
- answer: z.string().nullable(),
1143
- origin: conversationRecordOrigin,
1144
- delivery_status: conversationDeliveryStatus,
1145
- created_at: z.number(),
1146
- updated_at: z.number(),
1147
- channel: z.string().nullable().optional(),
1148
- our_identity: z.string().nullable().optional(),
1149
- provider_message_id: z.string().nullable().optional(),
1150
- callback_url: z.string().nullable().optional(),
1151
- error: z.string().nullable().optional(),
1152
- outbound_message_ids: z.array(z.string()).optional(),
1153
- attempts: z.number().optional(),
1154
- });
1155
- /**
1156
- * The direction a transcript page reads in. `asc` is the reading order (oldest
1157
- * first); `desc` is the live-tail order, where page 1 is always the newest page
1158
- * and paging forward walks BACKWARDS through the thread's history.
1159
- */
1160
- export const transcriptOrder = z.enum(['asc', 'desc']);
1161
- /** A transcript page, echoing the order it was read in. */
1162
- export const conversationTranscriptPage = z.object({
1163
- items: z.array(conversationMessage),
1164
- ...pageWindow,
1165
- order: transcriptOrder,
1166
- });
1167
- /**
1168
- * A route-scoped message search (`GET /api/conversations/{route}/messages/search?q`):
1169
- * every record on the route whose text matches `q`, across threads, newest first.
1170
- * The record shape is the transcript's exactly, so a hit renders with the same
1171
- * projection. `truncated` says the match set outran the door's scan cap — surfaced
1172
- * loudly, never a silent cut.
1173
- */
1174
- export const conversationMessageSearchPage = z.object({
1175
- items: z.array(conversationMessage),
1176
- ...pageWindow,
1177
- });
1178
- /**
1179
- * Who answers a thread's incoming messages: `agent` lets the flow reply on its
1180
- * own; `manual` holds the flow back so a human answers. `source` says whether the
1181
- * setting is the thread's own (`thread`) or inherited from its route (`route`).
1182
- */
1183
- export const conversationThreadMode = z.enum(['agent', 'manual']);
1184
- export const conversationThreadModeState = z.object({
1185
- mode: conversationThreadMode,
1186
- source: z.enum(['thread', 'route']),
1187
- });
1188
- /** The receipt a thread-message write returns: the stored id and its thread. */
1189
- export const conversationThreadMessageSent = z.object({
1190
- message_id: z.string(),
1191
- thread_id: z.string(),
1192
- });
1193
- /**
1194
- * Per-target presentation config for the conversation bridge, keyed by
1195
- * `(target_kind, target_name)` — the wire model of
1196
- * `tai42_contract.conversations.TargetConversationConfig`. The row carries no
1197
- * server-derived fields, so this ONE shape is the stored row AND the upsert body
1198
- * (`PUT /api/conversation-configs/{target_kind}/{target_name}` is the create AND
1199
- * the edit path).
1200
- *
1201
- * `multichannel` opts the target into person linking; `greeting_template` is the
1202
- * first-contact greeting. `greeting_template` is a `str.format` template (NOT a jq
1203
- * expression) that may reference at most the `{pairing_code}` placeholder — it
1204
- * carries no `x-tai42-expression`. `null` is the explicit spelling for "no
1205
- * greeting"; a blank string is refused server-side.
1206
- */
1207
- export const targetConversationConfig = z.object({
1208
- target_kind: conversationTargetKind,
1209
- target_name: z.string().min(1),
1210
- multichannel: z.boolean().default(false),
1211
- greeting_template: z.string().nullable().default(null),
1212
- });
1213
- /** Every stored per-target config (`GET /api/conversation-configs`). */
1214
- export const conversationConfigs = z.object({
1215
- items: z.array(targetConversationConfig),
1216
- total: z.number(),
1217
- });
1218
- /**
1219
- * The upsert reply: whether THIS call created the config (vs. replaced an existing
1220
- * one), its key, and the stored row.
1221
- */
1222
- export const conversationConfigWritten = z.object({
1223
- created: z.boolean(),
1224
- target_kind: conversationTargetKind,
1225
- target_name: z.string(),
1226
- config: targetConversationConfig,
1227
- });
1228
- /** The config-delete reply: `removed` is always `true` on success (an unknown key is a 404). */
1229
- export const conversationConfigDeleted = z.object({
1230
- removed: z.boolean(),
1231
- target_kind: conversationTargetKind,
1232
- target_name: z.string(),
1233
- });
1234
- /**
1235
- * The admin-only failed-delivery listing (`GET /api/conversations/messages/failed`):
1236
- * every answer record whose delivery ended `failed`, across every route and caller.
1237
- * The records are the ADMIN projection (delivery bookkeeping and `error` included),
1238
- * so the row shape is {@link conversationMessage} exactly. It is NOT a paged window —
1239
- * the door returns `{items, total}` and nothing more.
1240
- */
1241
- export const conversationFailedMessages = z.object({
1242
- items: z.array(conversationMessage),
1243
- total: z.number(),
1244
- });
1245
- /**
1246
- * The thread-delete reply (`DELETE /api/conversations/{route_name}/thread?thread_id=`).
1247
- * `removed` is a COUNT of the answer records this call deleted — `0` when a prior
1248
- * run already cleared them, when they had aged out under retention, or when the id
1249
- * was never stored (forgetting is absolute, never a 404 on a valid id).
1250
- */
1251
- export const conversationThreadDeleted = z.object({
1252
- removed: z.number(),
1253
- route_name: z.string(),
1254
- thread_id: z.string(),
1255
- });
1256
- /**
1257
- * The person-erase reply (`DELETE /api/conversations/persons/{person_id}`).
1258
- * `removed` counts the answer records deleted across the person's routes; `erased`
1259
- * says whether THIS call removed the person row (`false` on a retry or an
1260
- * already-gone person — never a 404).
1261
- */
1262
- export const conversationPersonDeleted = z.object({
1263
- person_id: z.string(),
1264
- removed: z.number(),
1265
- erased: z.boolean(),
1266
- });
1267
- // -- channel-web entry gate --------------------------------------------------
1268
- // The web channel's entry gate: a web route can be gated so its chat page is
1269
- // served only to a navigation carrying a live entry code. These four doors are
1270
- // keyed by the route's `our_identity`. A raw code exists only in flight (the
1271
- // mint reply); a listed code carries its hash id, never a raw value (none is
1272
- // stored).
1273
- /** One minted entry code as the gate read door lists it — its hash id, its
1274
- * optional label, and when it was created / dies. Never a raw code. */
1275
- export const webEntryCode = z.object({
1276
- code_id: z.string(),
1277
- label: z.string().nullable(),
1278
- created_at: z.string(),
1279
- expires_at: z.string().nullable(),
1280
- });
1281
- /** `GET /api/channels/web/gates/{identity}` — the gate flag and its live codes. */
1282
- export const webEntryGate = z.object({
1283
- enabled: z.boolean(),
1284
- codes: z.array(webEntryCode),
1285
- });
1286
- /** `PUT /api/channels/web/gates/{identity}` — the gate flag after the write. */
1287
- export const webEntryGateState = z.object({ enabled: z.boolean() });
1288
- /**
1289
- * `POST /api/channels/web/gates/{identity}/codes` — a freshly minted code. The
1290
- * raw `code` rides this reply ONCE (nothing else stores or lists it — a listed
1291
- * code's raw value is unrecoverable by design); `code_id` is its hash id and
1292
- * `expires_at` an ISO-8601 instant, or `null` for a code that never expires.
1293
- */
1294
- export const webEntryCodeMinted = z.object({
1295
- code: z.string(),
1296
- code_id: z.string(),
1297
- expires_at: z.string().nullable(),
1298
- });
1299
- /** `DELETE /api/channels/web/gates/{identity}/codes/{code_id}` — the revoked code. */
1300
- export const webEntryCodeRevoked = z.object({ status: z.literal('revoked') });
1301
- // -- notifications -----------------------------------------------------------
1302
- // The internal notifications sink: the messages the `notify_user` operation records
1303
- // with no channel, so they land only in the Studio inbox. Each record carries the
1304
- // server-minted id + timestamp; `recipient` is null when the message names none.
1305
- // The sink also stores the richer-send forms a notify carried — `media`
1306
- // (images/links shown WITH the message), `template` (a pre-approved out-of-window
1307
- // send), `options` (tappable option labels) — plus the `audience` identity the
1308
- // record is addressed to; the inbox renders them.
1309
- /**
1310
- * A pre-approved channel template a notification carried, stored raw on the sink
1311
- * record when the notify supplied one (server-side it is mutually exclusive with
1312
- * `media`/`options`). `parameters` is the POSITIONAL list of body-text values
1313
- * substituted into the template's placeholders in order; empty when it has none.
1314
- */
1315
- export const channelTemplate = z.object({
1316
- name: z.string(),
1317
- language: z.string(),
1318
- parameters: z.array(z.string()).default([]),
1319
- });
1320
- export const notification = z.object({
1321
- id: z.string(),
1322
- message: z.string(),
1323
- // A channel DELIVERY address the message went to; null when the send named none.
1324
- recipient: z.string().nullable(),
1325
- // The IDENTITY (a user_id) whose in-app inbox the record is addressed to, distinct
1326
- // from `recipient`. `.nullish()` accepts both an older record that predates the
1327
- // field (key omitted) and a broadcast send (explicit null) — both parse to "none".
1328
- audience: z.string().nullish(),
1329
- // Display-only media stored WITH the message (images and/or links). Deliberately
1330
- // LOOSE (`z.array(z.unknown())`), like the interactions frame: each item is
1331
- // `safeParse`d per item by the renderer against `interactionMediaItem`, so one
1332
- // malformed/hostile item is a loud per-item notice, never a whole-feed parse
1333
- // failure that would silently vanish every other notification. `.nullish()` covers
1334
- // an older record (key omitted) and a plain send (explicit null).
1335
- media: z.array(z.unknown()).nullish(),
1336
- // A pre-approved template the notify carried, parsed strictly: a drifting one is a
1337
- // loud feed-level `ApiSchemaError`, consistent with the record contract.
1338
- template: channelTemplate.nullish(),
1339
- // Tappable option labels the notify offered; display-only in the operator inbox
1340
- // (there is no conversation for the sink to enter). Strictly a string list.
1341
- options: z.array(z.string()).nullish(),
1342
- created_at: z.string(),
1343
- });
1344
- export const notifications = z.object({
1345
- notifications: z.array(notification),
1346
- });
1347
- // -- hooks -------------------------------------------------------------------
1348
- // Shapes match tai42_contract.hooks.HookParams: a hook fires a `tool` on a `topic`,
1349
- // optionally gated by a `condition` and shaped by an `expr` (each an authored
1350
- // templated-text value: inline content or a stored template id, with render kwargs).
1351
- /** A fire door — who may trigger a path, not what the fire may do. A string enum. */
1352
- export const triggerAuth = z.enum([
1353
- 'public',
1354
- 'verifier',
1355
- 'token',
1356
- 'token+api_key',
1357
- 'out-of-service',
1358
- ]);
1359
- /**
1360
- * The optional state subject a hook fire targets (mirrors
1361
- * `tai42_contract.hooks.HookSubject`): the conversation-target scope plus the subject
1362
- * `kind` and a `key_expr` jq evaluated over the event payload at fire. `null` leaves the
1363
- * fire with no ambient state context.
1364
- */
1365
- export const hookSubject = z.object({
1366
- target_kind: conversationTargetKind,
1367
- target_name: z.string(),
1368
- kind: z.string(),
1369
- key_expr: z.string(),
1370
- });
1371
- export const hookParams = z.object({
1372
- name: z.string(),
1373
- topic: z.string(),
1374
- tool: z.string(),
1375
- // The api-key `user_id` the fire runs AS.
1376
- execution_key: z.string().min(1),
1377
- tool_kwargs: z.record(z.string(), z.unknown()).default({}),
1378
- // The optional state subject the fire targets; `null` leaves it with no ambient
1379
- // state context (a state tool it calls must then carry an explicit subject).
1380
- subject: hookSubject.nullable().default(null),
1381
- condition: templatedText.nullable().default(null),
1382
- expr: templatedText.nullable().default(null),
1383
- // The optional door-layer state binding this hook applies around its fire's tool
1384
- // run (`null` when the hook touches no state).
1385
- state_binding: stateBinding.nullable().default(null),
1386
- });
1387
- export const hookList = z.object({
1388
- items: z.array(hookParams),
1389
- total: z.number(),
1390
- // Per-topic verifier bindings: the topic name maps to the verifier bound to it
1391
- // and that verifier's config (which carries a `secret_env` name, never a secret
1392
- // value). Tolerant of an older/empty response, which omits the key entirely.
1393
- topic_verifiers: z
1394
- .record(z.string(), z.object({ verifier: z.string(), config: z.record(z.string(), z.unknown()) }))
1395
- .default({}),
1396
- // Per-topic fire door (topic → door). Tolerant of an older response omitting it.
1397
- trigger_auth: z.record(z.string(), triggerAuth).default({}),
1398
- });
1399
- export const hookRegistered = z.object({
1400
- registered: z.boolean(),
1401
- name: z.string(),
1402
- });
1403
- export const hookRemoved = z.object({
1404
- removed: z.boolean(),
1405
- name: z.string(),
1406
- });
1407
- // The registered webhook-verifier catalog (`GET /api/hooks/verifiers`) — the
1408
- // sorted verifier NAMES only, never any verifier's config. An empty registry is a
1409
- // valid empty array. This is the ONLY source the topic-verifier bind picker reads.
1410
- export const hookVerifiers = z.array(z.string());
1411
- /** `PUT /api/hooks/topics/{topic}/verifier` — the bound topic + verifier name. */
1412
- export const topicVerifierSet = z.object({ topic: z.string(), verifier: z.string() });
1413
- /** `DELETE /api/hooks/topics/{topic}/verifier` — the unbound topic. */
1414
- export const topicVerifierRemoved = z.object({ removed: z.boolean(), topic: z.string() });
1415
- // -- trigger links -----------------------------------------------------------
1416
- // A trigger link is a minted, token-bearing PUBLIC URL (`GET|POST /trigger/{token}`)
1417
- // that resolves to a hook TOPIC and fires it. Studio mints, lists, and revokes them
1418
- // on the hooks page; the QR encodes the composed absolute URL.
1419
- /**
1420
- * `POST /api/hooks/trigger-links` — a freshly minted trigger link. `trigger_path`
1421
- * is a relative path (`/trigger/<token>`, never an absolute URL — Studio composes
1422
- * the API origin); `token` is the raw token returned exactly ONCE, in this response
1423
- * (nothing else stores or lists it — a listed link's QR is unrecoverable by
1424
- * design). `expires_at` is an ISO-8601 instant, or `null` for a permanent link.
1425
- */
1426
- export const triggerLinkCreated = z.object({
1427
- name: z.string(),
1428
- trigger_path: z.string(),
1429
- token: z.string(),
1430
- topic: z.string(),
1431
- expires_at: z.string().nullable(),
1432
- });
1433
- /**
1434
- * One listed trigger-link record (`GET /api/hooks/trigger-links` item). `tool_kwargs`
1435
- * is the per-link params merged last into every fire (`null` or `{}` = none);
1436
- * `created_by` is the ambient creator identity (`null` on an identity-less,
1437
- * gate-off deployment); `token_hash_prefix` is a short hash prefix for log
1438
- * correlation — never a raw token (none is stored).
1439
- */
1440
- export const triggerLinkRecord = z.object({
1441
- name: z.string(),
1442
- topic: z.string(),
1443
- execution_key: z.string().min(1),
1444
- trigger_auth: triggerAuth,
1445
- tool_kwargs: z.record(z.string(), z.unknown()).nullable(),
1446
- created_by: z.string().nullable(),
1447
- created_at: z.string(),
1448
- expires_at: z.string().nullable(),
1449
- token_hash_prefix: z.string(),
1450
- });
1451
- /** `GET /api/hooks/trigger-links` — every live trigger-link record. */
1452
- export const triggerLinkList = z.object({
1453
- items: z.array(triggerLinkRecord),
1454
- total: z.number(),
1455
- });
1456
- /** `DELETE /api/hooks/trigger-links/{name}` — the revoked link. */
1457
- export const triggerLinkDeleted = z.object({ removed: z.boolean(), name: z.string() });
1458
- // -- settings schema ---------------------------------------------------------
1459
- // The settings form is auto-rendered from a declarative group/field description
1460
- // the skeleton emits from its typed settings models.
1461
- export const settingsSchema = z.object({
1462
- groups: z.array(z.object({
1463
- name: z.string(),
1464
- module: z.string(),
1465
- qualname: z.string(),
1466
- fields: z.array(z.object({
1467
- name: z.string(),
1468
- env_var: z.string(),
1469
- type: z.string(),
1470
- default: jsonValue.nullable(),
1471
- required: z.boolean(),
1472
- secret: z.boolean(),
1473
- description: z.string().nullable(),
1474
- nested_group: z.string().nullable(),
1475
- default_namespace_var: z.string().nullable(),
1476
- value: jsonValue.nullable(),
1477
- })),
1478
- })),
1479
- });
1480
- // -- auth: scopes + api keys -------------------------------------------------
1481
- /** `{ url: scope_id }` — every URL currently mapped to a scope. */
1482
- export const authScopes = z.record(z.string(), z.string());
1483
- export const addUrlToScopeResult = z.object({ scope_id: z.string(), url: z.string() });
1484
- export const removeUrlFromScopeResult = z.object({ url: z.string() });
1485
- export const removeScopeResult = z.object({ scope_id: z.string(), deleted_keys: z.number() });
1486
- /**
1487
- * A route's authorization ACTION class, joined onto the catalog from the route
1488
- * registry. `read`/`write` routes are GRANTABLE — a role's per-tag level opens
1489
- * them; `fenced`/`secret` routes are ADMIN-ONLY and never grantable (no per-tag
1490
- * level reaches them). `null` marks a path the registry carries no metadata for
1491
- * (an unregistered/ungated path — no feature tags, no action).
1492
- */
1493
- export const routeAction = z.enum(['read', 'write', 'fenced', 'secret']);
1494
- /**
1495
- * One row of the route catalog (`GET /api/auth/routes`) — a plain HTTP route with
1496
- * its current mapping, JOINED with its route-registry metadata. `mapped` is the
1497
- * scope id, the public marker, or `null` when the path is unmapped (the mapper's
1498
- * "unassigned" bucket source). `methods` is the route's method set with `HEAD`
1499
- * removed. `tags` are the route's feature-group labels, `summary` its one-line
1500
- * description, and `action` its authorization class — the join fields the Roles
1501
- * grant editor reads to derive grantable feature groups and mark the admin-only
1502
- * (fenced/secret) routes it must never offer a grant for. An unregistered path
1503
- * carries `tags: []`, `summary: ''`, `action: null`.
1504
- */
1505
- export const authRoute = z.object({
1506
- path: z.string(),
1507
- methods: z.array(z.string()),
1508
- mapped: z.string().nullable(),
1509
- tags: z.array(z.string()),
1510
- summary: z.string(),
1511
- action: routeAction.nullable(),
1512
- });
1513
- export const authRoutes = z.array(authRoute);
1514
- // -- auth: roles (editable per-tag RBAC) -------------------------------------
1515
- /** A role's per-tag ACCESS LEVEL: `none` (no access), `read`, or `write`. */
1516
- export const grantLevel = z.enum(['none', 'read', 'write']);
1517
- /** A role's editable grant map: feature-group TAG name → the role's access level. */
1518
- export const roleGrants = z.record(z.string(), grantLevel);
1519
- /**
1520
- * A role body (`GET /api/auth/roles`, and the write routes' echo). Layer 1 is the
1521
- * read-only base-tier ceiling — the seeded jq `condition` plus `base_tier`
1522
- * (`editor`/`viewer`, or `null` for the reserved `allow_all` admin). Layer 2 is the
1523
- * editable per-tag `grants` map. `allow_all` (admin) carries an empty `grants` map
1524
- * and reaches everything, un-lockable. `condition` is the base-tier jq (an authored
1525
- * templated-text value) — READ-ONLY here (no raw-jq authoring surface).
1526
- */
1527
- export const roleBody = z.object({
1528
- name: z.string(),
1529
- description: z.string(),
1530
- scopes: z.array(z.string()),
1531
- condition: templatedText.nullable(),
1532
- base_tier: z.string().nullable(),
1533
- allow_all: z.boolean(),
1534
- grants: roleGrants,
1535
- });
1536
- export const roleList = z.array(roleBody);
1537
- /** `DELETE /api/auth/roles/{name}` — the deleted role's name. */
1538
- export const roleDeleted = z.object({ name: z.string(), deleted: z.boolean() });
1539
- /**
1540
- * One immutable role-version row (`GET /api/auth/roles/{name}/versions` →
1541
- * `versions`). `body` is the role body at that version; `is_current` flags the
1542
- * active pointer. Mirrors the generic versioned-store row shape.
1543
- */
1544
- export const roleVersion = z.object({
1545
- version: z.number(),
1546
- body: roleBody,
1547
- tags: z.array(z.string()),
1548
- created_at: z.string(),
1549
- is_current: z.boolean(),
1550
- });
1551
- /**
1552
- * One who/action/before→after audit row (`GET /api/auth/roles/{name}/versions` →
1553
- * `audit`). Its `body` carries the mutation kind, the actor, and the before/after
1554
- * role bodies (both `null` for a create's `before` / a delete's `after`).
1555
- */
1556
- export const roleAuditEvent = z.object({
1557
- version: z.number(),
1558
- body: z.object({
1559
- action: z.string(),
1560
- actor: z.string().nullable(),
1561
- before: jsonValue.nullable(),
1562
- after: jsonValue.nullable(),
1563
- }),
1564
- tags: z.array(z.string()),
1565
- created_at: z.string(),
1566
- is_current: z.boolean(),
1567
- });
1568
- /** `GET /api/auth/roles/{name}/versions` — the append-only history + audit trail. */
1569
- export const roleVersions = z.object({
1570
- versions: z.array(roleVersion),
1571
- audit: z.array(roleAuditEvent),
1572
- });
1573
- /** `GET /api/auth/public-routes` — the sorted urls pinned to the public marker. */
1574
- export const publicRoutes = z.array(z.string());
1575
- /** `POST /api/auth/public-routes` — the pinned url. */
1576
- export const pinPublicResult = z.object({ url: z.string() });
1577
- /** `DELETE /api/auth/public-routes` — the unpinned url (404 when not pinned). */
1578
- export const unpinPublicResult = z.object({ url: z.string() });
1579
- /** The per-key payloads (`GET /api/auth/tokens-payload`). The stable per-mint
1580
- * `key_fingerprint` is nested under `policy_data`; the picker reads it there. */
1581
- export const tokensPayload = z.array(z.object({
1582
- // The value an `execution_key` binding names.
1583
- user_id: z.string().min(1),
1584
- description: z.string(),
1585
- scopes: z.array(z.string()),
1586
- policy_data: jsonValue,
1587
- condition: templatedText.nullable().optional(),
1588
- }));
1589
- /** Mint reply `{ api_key, key_fingerprint }` narrowed to the raw `sk-…` key,
1590
- * returned once at creation and never again. */
1591
- export const createdApiKey = z
1592
- .object({ api_key: z.string(), key_fingerprint: z.string() })
1593
- .transform((minted) => minted.api_key);
1594
- export const editApiKeyResult = z.object({ user_id: z.string(), updated: z.boolean() });
1595
- export const revokeApiKeyResult = z.object({ user_id: z.string(), revoked: z.boolean() });
1596
- // -- login methods (the public sign-in aggregator) ---------------------------
1597
- /**
1598
- * One field of a `form` login method. `secret` fields render as password
1599
- * inputs; `autocomplete` passes through to the input (e.g. "email",
1600
- * "current-password", "new-password").
1601
- */
1602
- export const loginFormField = z.object({
1603
- name: z.string().min(1),
1604
- label: z.string().min(1),
1605
- secret: z.boolean(),
1606
- autocomplete: z.string().optional(),
1607
- });
1608
- /**
1609
- * The two — and only two — login-method shapes. Discriminated on `shape`; an
1610
- * unknown shape fails validation loudly (contract drift, not a soft skip).
1611
- * Optional fields are `.optional()` (absent), never `.nullable()`: the server
1612
- * serializes with `exclude_none`, so an absent field is OMITTED, never `null`.
1613
- */
1614
- export const loginMethod = z.discriminatedUnion('shape', [
1615
- z.object({
1616
- shape: z.literal('form'),
1617
- id: z.string().min(1),
1618
- title: z.string().min(1),
1619
- purpose: z.enum(['login', 'bootstrap', 'invite']).default('login'),
1620
- fields: z.array(loginFormField).min(1),
1621
- submit_path: z.string().min(1),
1622
- }),
1623
- z.object({
1624
- shape: z.literal('button'),
1625
- id: z.string().min(1),
1626
- label: z.string().min(1),
1627
- icon: z.string().optional(),
1628
- href: z.string().min(1),
1629
- }),
1630
- ]);
1631
- /**
1632
- * `GET /api/login/methods` (PUBLIC). `bootstrap: true` ⇒ the deployment has zero
1633
- * accounts and `methods` contains the owner-creation form. Non-strict — the
1634
- * skeleton may grow additive fields.
1635
- */
1636
- export const loginMethods = z.object({
1637
- methods: z.array(loginMethod),
1638
- bootstrap: z.boolean(),
1639
- });
1640
- /**
1641
- * A successful login/exchange: the minted session token (opaque, `tai-sess-`
1642
- * prefixed — the client treats it as an opaque string) + its user.
1643
- */
1644
- export const loginResult = z.object({
1645
- token: z.string().min(1),
1646
- user_id: z.string().min(1),
1647
- });
1648
- // -- auth: the caller's capability projection (GET /api/auth/me) --------------
1649
- /** A concrete route the caller can reach, with the methods that pass its jq fence. */
1650
- export const routeEntry = z.object({
1651
- path: z.string(),
1652
- methods: z.array(z.string()),
1653
- });
1654
- /**
1655
- * A dynamic route pattern the caller can reach — a mount/pattern surface not
1656
- * enumerable into concrete paths, projected only when its scope AND jq admit it.
1657
- */
1658
- export const patternEntry = z.object({
1659
- pattern: z.string(),
1660
- scope_id: z.string(),
1661
- });
1662
- /**
1663
- * A sub-MCP mount the caller can reach, with the tools it exposes and its
1664
- * transport. Shares the {@link subMcpMount} shape with the registry list, adding
1665
- * the `slug` the projection carries inline (the list keys mounts by slug instead).
1666
- */
1667
- export const subMcpEntry = subMcpMount.extend({ slug: z.string() });
1668
- /**
1669
- * `GET /api/auth/me` — the caller's derived capability projection: the concrete
1670
- * (path, method) surface, dynamic patterns, sub-MCP mounts, tools, and agents it
1671
- * can reach RIGHT NOW (derived server-side, never stored). `admin` is the
1672
- * condition-free ownerless `"*"` discriminator (a TOTAL projection); a scoped
1673
- * session carries `admin: false` and a jq-exact `routes` list. `owner_user_id` is
1674
- * the key's owner claim, `null` for an ownerless key. The invariant is
1675
- * projection ⊆ gate: every projected surface is one the server would admit, so
1676
- * the UI can filter on it without ever advertising a door the gate denies.
1677
- */
1678
- export const meProjection = z.object({
1679
- user_id: z.string(),
1680
- owner_user_id: z.string().nullable(),
1681
- admin: z.boolean(),
1682
- scopes: z.array(z.string()),
1683
- routes: z.array(routeEntry),
1684
- route_patterns: z.array(patternEntry),
1685
- sub_mcp: z.array(subMcpEntry),
1686
- tools: z.array(z.string()),
1687
- agents: z.array(z.string()),
1688
- mintable: z.boolean(),
1689
- });
1690
- /**
1691
- * `POST /api/auth/claim-links` — a one-time claim link minted in the key-create
1692
- * flow. `claim_path` is a fragment-carrier path (`/login#claim=<token>`, never an
1693
- * absolute URL — the Studio composes the origin); `token` is the raw claim token
1694
- * returned exactly ONCE; `expires_at` is an ISO-8601 instant.
1695
- */
1696
- export const claimLinkCreated = z.object({
1697
- claim_path: z.string(),
1698
- token: z.string(),
1699
- expires_at: z.string(),
1700
- });
1701
- /**
1702
- * `POST /api/auth/logout` — the single authed logout dispatcher's result.
1703
- * `revoked` is `true` when a live accounts session was revoked; a plain `sk-` key
1704
- * has nothing to revoke and answers `404` (a loud absent-session signal), never a
1705
- * `revoked: false` body. Non-strict, so a SUCCESSFUL logout never throws
1706
- * `ApiSchemaError` on an additive field.
1707
- */
1708
- export const logoutResult = z.object({ revoked: z.boolean() });
1709
- /**
1710
- * `GET /api/auth/capabilities` — whether this deployment can MINT API keys
1711
- * locally. `mintable: false` means every configured identity provider is
1712
- * validator-only (keys are issued at an external issuer), so the key-creation
1713
- * UI disables up front instead of surfacing a raw mint failure. Non-strict.
1714
- */
1715
- export const authCapabilities = z.object({
1716
- mintable: z.boolean(),
1717
- providers: z.array(z.object({ name: z.string(), mintable: z.boolean() })),
1718
- });
1719
- // -- access-control policy (authoring + versioning) --------------------------
1720
- /**
1721
- * `POST /api/auth/validate-condition` — the fail-closed jq guard. `ok` is the
1722
- * compile/render result; `result` is the boolean of a sample evaluation, or
1723
- * `null` when no `sample_context` was supplied (compile-only). A broken condition
1724
- * is a `400 {error}` surfaced by the transport, NEVER a false `ok` — so a
1725
- * syntactically-broken condition can be caught before it locks a key out. A drift
1726
- * throws `ApiSchemaError`.
1727
- */
1728
- export const validateConditionResult = z.object({
1729
- ok: z.boolean(),
1730
- result: z.boolean().nullable(),
1731
- });
1732
- /**
1733
- * The enforced policy shape a version body carries. SECRET-ADJACENT — `condition`
1734
- * can reveal auth logic, so its fixtures are hand-authored, never captured.
1735
- * `condition` is an authored templated-text value (inline content or a stored
1736
- * template id); `null` when the policy carries no condition.
1737
- */
1738
- export const policyBody = z.object({
1739
- scopes: z.array(z.string()),
1740
- policy_data: jsonValue,
1741
- condition: templatedText.nullable(),
1742
- });
1743
- /**
1744
- * One immutable policy version row
1745
- * (`GET /api/auth/api-keys/{user_id}/policy/versions`). `is_current` flags the
1746
- * active pointer; `tags` is the generic per-version label list (kind-agnostic),
1747
- * mirroring the preset version shape.
1748
- */
1749
- export const policyVersion = z.object({
1750
- version: z.number(),
1751
- body: policyBody,
1752
- tags: z.array(z.string()),
1753
- created_at: z.string(),
1754
- is_current: z.boolean(),
1755
- });
1756
- export const policyVersionList = z.array(policyVersion);
1757
- /** `POST /api/auth/api-keys/{user_id}/policy/rollback` — the re-pointed version. */
1758
- export const policyRollback = z.object({ user_id: z.string(), active_version: z.number() });
1759
- // -- backup ------------------------------------------------------------------
1760
- export const backupSections = z.array(z.object({ name: z.string(), secret: z.boolean() }));
1761
- /** The exported backup document — validated when the UI reads a picked file. */
1762
- export const backupDocument = z.object({
1763
- version: z.literal(1),
1764
- created_at: z.string(),
1765
- sections: z.record(z.string(), z.unknown()),
1766
- errors: z.record(z.string(), z.string()).optional(),
1767
- });
1768
- export const backupImportReport = z.object({
1769
- ok: z.boolean(),
1770
- sections: z.record(z.string(), z.object({
1771
- created: z.number(),
1772
- updated: z.number(),
1773
- skipped: z.number(),
1774
- errors: z.array(z.string()),
1775
- new_api_keys: z
1776
- .array(z.object({
1777
- user_id: z.string(),
1778
- description: z.string(),
1779
- api_key: z.string(),
1780
- }))
1781
- .optional(),
1782
- // The `manifest` and `env` sections apply through the config pipeline, so their
1783
- // report carries the mode-wrapped fleet `fanout` of the restore's reload; other
1784
- // sections omit it. Parsed EXPLICITLY so the shared fleet-report handler can
1785
- // surface a failed restore propagation — otherwise zod would strip it silently.
1786
- fanout: fleetReportFanout.optional(),
1787
- })),
1788
- });
1789
- // -- schedules ---------------------------------------------------------------
1790
- // The skeleton returns opaque backend-tool results; the table reads a few known
1791
- // fields and passes the rest through untouched.
1792
- export const scheduleItem = z
1793
- .object({
1794
- name: z.string(),
1795
- enabled: z.boolean().optional(),
1796
- schedule: jsonValue.optional(),
1797
- target: jsonValue.optional(),
1798
- args: z.array(jsonValue).optional(),
1799
- kwargs: z.record(z.string(), jsonValue).optional(),
1800
- })
1801
- .loose();
1802
- export const scheduleList = z.array(scheduleItem);
1803
- export const serverDateTime = z
1804
- .object({
1805
- utc: jsonValue,
1806
- local: jsonValue.optional(),
1807
- system: jsonValue.optional(),
1808
- })
1809
- .loose();
1810
- // -- observability -----------------------------------------------------------
1811
- export const dashboardMetrics = z.object({
1812
- summary: z.object({
1813
- totalRuns: z.number(),
1814
- totalCost: z.number(),
1815
- totalTokens: z.number(),
1816
- averageLatencyMs: z.number(),
1817
- avgCostPerRun: z.number(),
1818
- avgTokensPerRun: z.number(),
1819
- timeToFirstTokenMs: z.number().nullable(),
1820
- }),
1821
- timeSeries: z.array(z.object({
1822
- bucket: z.string().nullable(),
1823
- runs: z.number(),
1824
- cost: z.number(),
1825
- avgLatencyMs: z.number(),
1826
- totalTokens: z.number(),
1827
- })),
1828
- byModel: z.array(z.object({
1829
- model: z.string(),
1830
- calls: z.number(),
1831
- cost: z.number(),
1832
- totalTokens: z.number(),
1833
- avgLatencyMs: z.number(),
1834
- })),
1835
- granularity: z.enum(['hour', 'day', 'week']),
1836
- });
1837
- export const run = z.object({
1838
- id: z.string(),
1839
- traceId: z.string(),
1840
- createdAt: z.string().nullable(),
1841
- tags: z.array(z.string()),
1842
- status: z.enum(['error', 'success']),
1843
- cost: z.number().nullable(),
1844
- latencyMs: z.number().nullable(),
1845
- totalTokens: z.number().nullable(),
1846
- inputPreview: jsonValue,
1847
- outputPreview: jsonValue,
1848
- });
1849
- export const runsPage = z.object({
1850
- items: z.array(run),
1851
- page: z.number(),
1852
- nextPage: z.number().nullable(),
1853
- });
1854
- export const runSpan = z.object({
1855
- id: z.string(),
1856
- parentId: z.string().nullable(),
1857
- traceId: z.string().nullable(),
1858
- name: z.string().nullable(),
1859
- type: z.string().nullable(),
1860
- level: z.string().nullable(),
1861
- statusMessage: z.string().nullable(),
1862
- start: z.string().nullable(),
1863
- end: z.string().nullable(),
1864
- model: z.string().nullable(),
1865
- usage: jsonValue.nullable(),
1866
- metadata: jsonValue.nullable(),
1867
- input: jsonValue,
1868
- output: jsonValue,
1869
- nodeId: z.string().nullable(),
1870
- });
1871
- export const runTrace = z.object({
1872
- traceId: z.string(),
1873
- timestamp: z.string().nullable(),
1874
- tags: z.array(z.string()),
1875
- totalCost: z.number().nullable(),
1876
- input: jsonValue,
1877
- output: jsonValue,
1878
- metadata: jsonValue.nullable(),
1879
- spans: z.array(runSpan),
1880
- });
1881
- // -- marketplace -------------------------------------------------------------
1882
- /**
1883
- * The CLOSED HTTP-method enum fixed by the contract's RouteMethod, the one source
1884
- * for every route-method field (declaration and install/preview responses alike):
1885
- * a wire method the contract does not define fails loudly as drift rather than
1886
- * passing through silently.
1887
- */
1888
- export const routeMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
1889
- /**
1890
- * How the plugin is delivered, derived server-side from whether its spec names a
1891
- * package: `package` is pip-installed, `descriptor` (marketplace `source` `spec`)
1892
- * installs nothing but the manifest entry. A CLOSED enum, so a wire value the
1893
- * server does not derive fails loudly as drift.
1894
- */
1895
- export const marketplaceDelivery = z.enum(['package', 'descriptor']);
1896
- /**
1897
- * The install-provenance vocabulary a listing/version resolves from: a pip index
1898
- * (`pypi`), a git tag (`github`), or a descriptor yml (`spec`). CLOSED, so a wire
1899
- * value outside it fails as drift.
1900
- */
1901
- export const marketplaceSource = z.enum(['pypi', 'github', 'spec']);
1902
- /**
1903
- * One env var an item requires before install: its `name` and whether its value is
1904
- * a `secret` (an OAuth client secret is; a client id or a plain marker var is not).
1905
- * The install dialog seeds each toggle from `secret` and collects the missing ones.
1906
- */
1907
- export const marketplaceRequiredEnvVar = z.object({ name: z.string(), secret: z.boolean() });
1908
- /**
1909
- * One HTTP route an item declares in its `tai-plugin.yml`: a `path` relative to
1910
- * the item's mount base, the `methods` it answers, and whether it is served
1911
- * `public` (without authentication). The absolute path an operator sees is
1912
- * resolved from the base at install time.
1913
- */
1914
- export const marketplaceRouteDecl = z.object({
1915
- path: z.string(),
1916
- methods: z.array(routeMethod),
1917
- public: z.boolean(),
1918
- });
1919
- /**
1920
- * A route-carrying item's declared mount: the default `base` prefix the routes
1921
- * hang off (an operator may remap it at install) and the `paths` it registers.
1922
- */
1923
- export const marketplaceRoutesDecl = z.object({
1924
- base: z.string(),
1925
- paths: z.array(marketplaceRouteDecl),
1926
- });
1927
- /**
1928
- * One contained item of a marketplace plugin's published version: its kind
1929
- * (tool / agent / extension / …), name, description, free-form tags, and the
1930
- * logical group it belongs to (`null` when ungrouped). `routes` is present only
1931
- * on a route-carrying item (a router or channel); absent/null on every other.
1932
- * `required_env` is the env vars the item needs before install — but the REGISTRY's
1933
- * plugin-detail body does NOT carry it per item: required-env is server-computed
1934
- * (`required_env_for_spec`) and rides the install PREVIEW, never the listing detail,
1935
- * so the registry OMITS it here and it parses as `undefined`. Optional (not
1936
- * defaulted) so the type mirrors the wire; the authority the install dialog collects
1937
- * from is the preview's `required_env`/`missing_env`, never this field.
1938
- */
1939
- export const marketplaceItem = z.object({
1940
- kind: z.string(),
1941
- name: z.string(),
1942
- description: z.string(),
1943
- tags: z.array(z.string()),
1944
- group: z.string().nullable(),
1945
- routes: marketplaceRoutesDecl.nullish(),
1946
- required_env: z.array(marketplaceRequiredEnvVar).optional(),
1947
- });
1948
- /**
1949
- * One kind summary of a search row: an item kind, how many UNGROUPED items of
1950
- * that kind the listing's latest published version carries, and their names.
1951
- * The registry orders the array count DESC then kind ASC, and each entry's
1952
- * `names` ASC; the UI renders in that order.
1953
- */
1954
- export const marketplaceSearchKind = z.object({
1955
- kind: z.string(),
1956
- count: z.number(),
1957
- names: z.array(z.string()),
1958
- });
1959
- /**
1960
- * One logical group of a search row: the group name and how many items the
1961
- * listing's latest published version places in it. The registry orders the
1962
- * array count DESC then name ASC; the UI renders in that order.
1963
- */
1964
- export const marketplaceSearchGroup = z.object({
1965
- name: z.string(),
1966
- count: z.number(),
1967
- });
1968
- /**
1969
- * One listing-level search row (`GET /api/marketplace/search`). `ref` is the
1970
- * "namespace/name" install target, so every row carries enough to link to its
1971
- * plugin. `latest_version` is nullable defensively; the search relation only
1972
- * emits listings with a published version. `kinds` summarizes that latest
1973
- * version's UNGROUPED items; `groups` summarizes its logical groups (`[]` when
1974
- * none). Both keys are always present.
1975
- */
1976
- export const marketplaceSearchRow = z.object({
1977
- ref: z.string(),
1978
- namespace: z.string(),
1979
- name: z.string(),
1980
- display_name: z.string().nullable(),
1981
- icon_url: z.string().nullable(),
1982
- // Null for a descriptor listing (`source` `spec`): it names no package.
1983
- package: z.string().nullable(),
1984
- description: z.string(),
1985
- categories: z.array(z.string()),
1986
- tags: z.array(z.string()),
1987
- trust_tier: z.string(),
1988
- pricing: z.string(),
1989
- // A display-only premium flag: a badge, never a payment surface. Nullable
1990
- // + optional: an older registry omits it, and it is absent on non-premium rows.
1991
- premium: z.boolean().nullish(),
1992
- latest_version: z.string().nullable(),
1993
- downloads: z.number(),
1994
- updated_at: z.string(),
1995
- kinds: z.array(marketplaceSearchKind),
1996
- groups: z.array(marketplaceSearchGroup),
1997
- });
1998
- /**
1999
- * One page of listing rows. The wire carries no next-page field — has-next is
2000
- * COMPUTED: `page * page_size < total`.
2001
- */
2002
- export const marketplaceSearchPage = z.object({
2003
- listings: z.array(marketplaceSearchRow),
2004
- total: z.number(),
2005
- page: z.number(),
2006
- page_size: z.number(),
2007
- });
2008
- /**
2009
- * One version of a listing as the versions list carries it (`version`, its
2010
- * publication `status`, the `contract_range` it was validated against, and when
2011
- * it was `published_at`). `contract_range` and `published_at` are null until a
2012
- * version reaches the published state, so both are nullable.
2013
- */
2014
- export const marketplaceVersion = z.object({
2015
- version: z.string(),
2016
- contract_range: z.string().nullable(),
2017
- status: z.string(),
2018
- published_at: z.string().nullable(),
2019
- });
2020
- /**
2021
- * The listing's latest published version as embedded in its detail: the version
2022
- * (with the `marketplaceVersion` lifecycle fields) and the items it contains. The
2023
- * registry's detail body carries NO per-version `delivery` field — delivery is
2024
- * server-computed from whether the spec names a package, and the detail surface
2025
- * derives it from the listing's `package` (null ⇒ `descriptor`, else `package`).
2026
- * Env to collect comes from the install preview (`required_env` / `missing_env`),
2027
- * never from these items.
2028
- */
2029
- export const marketplaceLatestVersion = marketplaceVersion.extend({
2030
- items: z.array(marketplaceItem),
2031
- });
2032
- /** One advisory row; `withdrawn_at` non-null means it no longer applies. */
2033
- export const marketplaceAdvisory = z.object({
2034
- id: z.number(),
2035
- // The "namespace/name" listing ref exactly as the registry serves it — the
2036
- // wire field consumers match installed plugins against.
2037
- listing: z.string(),
2038
- affected_versions: z.string(),
2039
- severity: z.string(),
2040
- summary: z.string(),
2041
- created_at: z.string(),
2042
- withdrawn_at: z.string().nullable(),
2043
- });
2044
- /**
2045
- * A plugin's full detail (`GET /api/marketplace/plugins/{ns}/{name}`): the
2046
- * listing, its latest published version (`null` when nothing is published yet,
2047
- * carrying the contained items), and the known version history.
2048
- */
2049
- export const marketplacePluginDetail = z.object({
2050
- namespace: z.string(),
2051
- name: z.string(),
2052
- display_name: z.string().nullable(),
2053
- icon_url: z.string().nullable(),
2054
- // Null for a descriptor listing (`source` `spec`): it names no package.
2055
- package: z.string().nullable(),
2056
- // The install-provenance vocabulary (`pypi` / `github` / `spec`). Nullish: an
2057
- // older registry omits it, and a listing with nothing published carries none.
2058
- source: marketplaceSource.nullish(),
2059
- description: z.string(),
2060
- readme_md: z.string().nullable(),
2061
- license: z.string().nullable(),
2062
- homepage_url: z.string().nullable(),
2063
- repository_url: z.string().nullable(),
2064
- // The marketplace-stored docs-site URL: the store is the source, the UI a
2065
- // link. Nullable + optional: absent on a listing with no published docs.
2066
- docs_url: z.string().nullish(),
2067
- categories: z.array(z.string()),
2068
- tags: z.array(z.string()),
2069
- trust_tier: z.string(),
2070
- pricing: z.string(),
2071
- // A display-only premium flag: a badge, never a payment surface.
2072
- premium: z.boolean().nullish(),
2073
- downloads: z.number(),
2074
- latest: marketplaceLatestVersion.nullable(),
2075
- versions: z.array(marketplaceVersion),
2076
- });
2077
- /**
2078
- * One installed plugin's compat verdict against the RUNNING tai42-contract:
2079
- * `compatible` / `incompatible` per its declared contract range, `unknown` when
2080
- * no verdict could be computed. `reason` is the human-readable explanation of a
2081
- * non-`compatible` verdict, `null` when compatible. CLOSED (`.strict()`): the
2082
- * server is built against exactly this shape, so an unknown key is drift and
2083
- * throws instead of being stripped.
2084
- */
2085
- export const marketplaceInstalledCompat = z
2086
- .object({
2087
- status: z.enum(['compatible', 'incompatible', 'unknown']),
2088
- reason: z.string().nullable(),
2089
- })
2090
- .strict();
2091
- /**
2092
- * One installed marketplace plugin as the local attribution store records it.
2093
- * `latest` is the newest registry version (`null` when unknown);
2094
- * `update_available` advertises only a newer COMPATIBLE version, and
2095
- * `incompatible_newer` names the newest version blocked by the running
2096
- * contract (`null` when none), so "an update exists but needs a newer core" is
2097
- * visible. `missing_upstream` flags a plugin the registry no longer lists.
2098
- * `compat` is the row's verdict against the running contract. CLOSED, like
2099
- * every shape of the installed listing.
2100
- */
2101
- export const marketplaceInstalledPlugin = z
2102
- .object({
2103
- ref: z.string(),
2104
- version: z.string(),
2105
- source: z.string(),
2106
- // How the row was delivered: `package` for a pip-installed plugin, `descriptor`
2107
- // for a descriptor-only plugin whose stored spec names no package.
2108
- delivery: marketplaceDelivery,
2109
- installed_at: z.string(),
2110
- latest: z.string().nullable(),
2111
- update_available: z.boolean(),
2112
- incompatible_newer: z.string().nullable(),
2113
- missing_upstream: z.boolean(),
2114
- compat: marketplaceInstalledCompat,
2115
- // The `{kind, name}` of every item this plugin's stored spec provides
2116
- // (local truth). The connectors page's McpServersSection joins the mcp-server
2117
- // item names against the manifest's mcp-entry titles to render an
2118
- // installer-written entry read-only.
2119
- items: z.array(marketplaceItem.pick({ kind: true, name: true })),
2120
- // The persisted `{item_name: base}` mount this plugin is installed at (local
2121
- // truth; `{}` means every item at its declared base). Studio seeds the update
2122
- // flow and renders routes at the ACTUAL mounted base from this.
2123
- route_mounts: z.record(z.string(), z.string()),
2124
- })
2125
- .strict();
2126
- /** One plugin the boot pass quarantined instead of loading, with why. CLOSED. */
2127
- export const marketplaceQuarantinedPlugin = z
2128
- .object({
2129
- name: z.string(),
2130
- reason: z.string(),
2131
- })
2132
- .strict();
2133
- /**
2134
- * The installed inventory (`GET /api/marketplace/installed`): the attributed
2135
- * rows plus every plugin the boot pass quarantined instead of loading. CLOSED.
2136
- */
2137
- export const marketplaceInstalled = z
2138
- .object({
2139
- installed: z.array(marketplaceInstalledPlugin),
2140
- quarantined: z.array(marketplaceQuarantinedPlugin),
2141
- })
2142
- .strict();
2143
- /**
2144
- * One per-plugin outcome of `POST /api/marketplace/upgrade-all`: `upgraded`,
2145
- * already `up-to-date`, `no-compatible-version` in the registry, or `failed`;
2146
- * `detail` always carries the human-readable specifics. CLOSED.
2147
- */
2148
- export const marketplaceUpgradeAllRow = z
2149
- .object({
2150
- ref: z.string(),
2151
- outcome: z.enum(['upgraded', 'up-to-date', 'no-compatible-version', 'failed']),
2152
- detail: z.string(),
2153
- })
2154
- .strict();
2155
- /** The whole upgrade-all readout: one outcome row per installed plugin. CLOSED. */
2156
- export const marketplaceUpgradeAllResult = z
2157
- .object({
2158
- results: z.array(marketplaceUpgradeAllRow),
2159
- })
2160
- .strict();
2161
- /** The advisory state for installed plugins, with when it was last fetched. */
2162
- export const marketplaceAdvisories = z.object({
2163
- advisories: z.array(marketplaceAdvisory),
2164
- fetched_at: z.string(),
2165
- });
2166
- /** The registry's controlled category list, proxied through the skeleton. */
2167
- export const marketplaceCategories = z.array(z.string());
2168
- /** The registry's controlled item-kind list, proxied through the skeleton. */
2169
- export const marketplaceKinds = z.array(z.string());
2170
- /**
2171
- * One route the install / update actually mounted: the item that owns it, the
2172
- * absolute `full_path` it now answers, the `methods`, and whether it is served
2173
- * `public`. The receipt lists these so the operator sees what was opened where.
2174
- */
2175
- export const marketplaceMountedRoute = z.object({
2176
- item: z.string(),
2177
- full_path: z.string(),
2178
- methods: z.array(routeMethod),
2179
- public: z.boolean(),
2180
- });
2181
- /**
2182
- * The receipt of an install / update: what landed and at which version.
2183
- * `notes` (env-selected items naming their activating env var) and any
2184
- * non-critical `advisories` ride BOTH the install and update receipts.
2185
- * `routes` is the mounted-route list — empty for a plugin that registers none.
2186
- * The wire also carries `package`, `reload`, and `pip_output`, which the receipt
2187
- * does not model — zod's default strip drops them; the UI renders the receipt,
2188
- * not the raw install log.
2189
- */
2190
- export const marketplaceInstallResult = z.object({
2191
- ref: z.string(),
2192
- version: z.string(),
2193
- notes: z.array(z.string()),
2194
- advisories: z.array(marketplaceAdvisory),
2195
- routes: z.array(marketplaceMountedRoute),
2196
- });
2197
- /** One resolved route in an install preview: the declared `path`, its resolved
2198
- * absolute `full_path`, the `methods`, and whether it is `public`. */
2199
- export const marketplacePreviewRoute = z.object({
2200
- path: z.string(),
2201
- full_path: z.string(),
2202
- methods: z.array(routeMethod),
2203
- public: z.boolean(),
2204
- });
2205
- /** One route-carrying item in an install preview: its resolved `base` (the
2206
- * override or the default), the `default_base` it declares, and the routes that
2207
- * mount under it. */
2208
- export const marketplacePreviewItem = z.object({
2209
- item: z.string(),
2210
- kind: z.string(),
2211
- base: z.string(),
2212
- default_base: z.string(),
2213
- routes: z.array(marketplacePreviewRoute),
2214
- });
2215
- /** One collision the preview found: a route whose resolved shape+method clashes
2216
- * with an already-owned route. `conflict_owner` is `core` or `plugin:<ref>`; the
2217
- * remedy is to remap the item's base. */
2218
- export const marketplacePreviewCollision = z.object({
2219
- item: z.string(),
2220
- full_path: z.string(),
2221
- methods: z.array(routeMethod),
2222
- conflict_owner: z.string(),
2223
- conflict_path: z.string(),
2224
- });
2225
- /** One public route the operator is asked to accept: served WITHOUT
2226
- * authentication once installed. */
2227
- export const marketplacePreviewPublicRoute = z.object({
2228
- item: z.string(),
2229
- full_path: z.string(),
2230
- methods: z.array(routeMethod),
2231
- });
2232
- /**
2233
- * The install preview (`POST /api/marketplace/install/preview`): the resolved
2234
- * routes per item with any base overrides applied, the collisions against the
2235
- * live registry, the public routes requiring acceptance, and whether acceptance
2236
- * is required at all. `new_public_routes` narrows to rows not already approved
2237
- * (the update case); on a fresh install it equals `public_routes`.
2238
- *
2239
- * `required_env` is every env var the spec requires with its derived secret-ness;
2240
- * `missing_env` is the bare names not already present in the env store union
2241
- * process env (server-computed — the ONE authority the dialog collects), and
2242
- * `delivery` is the one word the surface shows (`package` / `descriptor`).
2243
- */
2244
- export const marketplaceInstallPreview = z.object({
2245
- ref: z.string(),
2246
- version: z.string(),
2247
- items: z.array(marketplacePreviewItem),
2248
- collisions: z.array(marketplacePreviewCollision),
2249
- public_routes: z.array(marketplacePreviewPublicRoute),
2250
- new_public_routes: z.array(marketplacePreviewPublicRoute),
2251
- requires_public_acceptance: z.boolean(),
2252
- required_env: z.array(marketplaceRequiredEnvVar),
2253
- missing_env: z.array(z.string()),
2254
- delivery: marketplaceDelivery,
2255
- });
2256
- /**
2257
- * The receipt of an uninstall. `notes` carries operator warnings (e.g. a config
2258
- * provider still selected by its env var); render them like the install
2259
- * receipt's notes.
2260
- */
2261
- export const marketplaceUninstallResult = z.object({
2262
- ref: z.string(),
2263
- uninstalled: z.literal(true),
2264
- notes: z.array(z.string()),
2265
- });
2266
- // Background tool-run schemas live in their own module; re-exported here so the
2267
- // shared fixture-validation contract resolves them by name alongside the rest.
2268
- export { toolRunSubmitResult, toolRunRecord, toolRunList } from './tool-runs';
2269
- // -- states ------------------------------------------------------------------
2270
- // Shapes mirror tai42_contract.states.models: a declared JSON document, one per
2271
- // subject. The base document, template fragments, write regimes and effective schema
2272
- // are permissive JSON records — the auto-form and JsonTree interpret them at runtime;
2273
- // a drift throws ApiSchemaError. Server-composed fields (`effective_schema`, `regimes`,
2274
- // list/stats derived columns) are modelled optional so a response that omits them still
2275
- // parses.
2276
- /** One addressed subject: the conversation-target scope plus the (kind, key) within it. */
2277
- export const stateSubject = z.object({
2278
- target_kind: conversationTargetKind,
2279
- target_name: z.string(),
2280
- kind: z.string(),
2281
- key: z.string(),
2282
- });
2283
- /** One absolute write-regime rule composed over the attachments: `{path, regime}`, permissive. */
2284
- export const stateRegime = z.record(z.string(), z.unknown());
2285
- /**
2286
- * A declared state served on every read. `schema` is the base JSON Schema;
2287
- * `effective_schema` (the base composed with every attached template's fragment) and
2288
- * `regimes` (the absolute write-regime rules) are platform-COMPUTED and served on every
2289
- * read — modelled optional so a write body that omits them, or a server yet to compose
2290
- * them, still parses. A drift throws ApiSchemaError.
2291
- */
2292
- export const stateDeclaration = z.object({
2293
- name: z.string(),
2294
- description: z.string().default(''),
2295
- schema: jsonSchema,
2296
- subject_kinds: z.array(z.string()),
2297
- default_subject_kind: z.string(),
2298
- retention_days: z.number().nullable().default(null),
2299
- effective_schema: jsonSchema.nullable().optional(),
2300
- regimes: z.array(stateRegime).nullable().optional(),
2301
- });
2302
- /**
2303
- * One row of `GET /api/states` — a served declaration (`list_states` dumps every
2304
- * `StateDeclaration`) plus `updated_at` (the row's last-write timestamp, ISO or
2305
- * `null`). The list carries no record count; the `Records` column reads
2306
- * `getStateStats` lazily per row.
2307
- */
2308
- export const stateListItem = stateDeclaration.extend({
2309
- updated_at: z.string().nullable().default(null),
2310
- });
2311
- export const stateList = z.array(stateListItem);
2312
- /** One template attachment on a state: where the fragment lands + its param/declaration values. */
2313
- export const stateAttachment = z.object({
2314
- template: z.string(),
2315
- path: z.array(z.string()),
2316
- parameters: z.record(z.string(), z.unknown()).default({}),
2317
- declarations: z.record(z.string(), z.unknown()).default({}),
2318
- });
2319
- export const stateAttachmentList = z.array(stateAttachment);
2320
- /** `GET /api/states/{name}` — the declaration (with effective schema + regimes) plus attachments. */
2321
- export const stateDetail = stateDeclaration.extend({
2322
- attachments: z.array(stateAttachment).default([]),
2323
- });
2324
- /**
2325
- * `GET /api/states/{name}/stats` — `records` (total documents), `per_field` (the count
2326
- * of records carrying each base-schema property), `per_kind` (records per subject kind)
2327
- * and `consumers` (the count of listable consumers). The Records tab shows `records`;
2328
- * the states list reads it lazily for its Records column.
2329
- */
2330
- export const stateStats = z.object({
2331
- records: z.number(),
2332
- per_field: z.record(z.string(), z.number()).default({}),
2333
- per_kind: z.record(z.string(), z.number()).default({}),
2334
- consumers: z.number().default(0),
2335
- });
2336
- /**
2337
- * One template jq: `jq` is a program over the subject's attached subtree, authored as a
2338
- * templated text (inline `content` or a stored template `id`); its `purpose` is either
2339
- * `input` (a read-only program feeding a tool argument) or `update` (a program returning
2340
- * an op batch that mutates the record). `params` are its declared argument names;
2341
- * `reads`/`writes` are the template-relative record paths an `update` declares, each a
2342
- * list of key segments; `description` is the human label.
2343
- */
2344
- export const templateJq = z.object({
2345
- description: z.string().default(''),
2346
- purpose: z.enum(['input', 'update']),
2347
- params: z.array(z.string()).default([]),
2348
- reads: z.array(z.array(z.string())).default([]),
2349
- writes: z.array(z.array(z.string())).default([]),
2350
- jq: templatedText,
2351
- });
2352
- /**
2353
- * How a declarations edit settles open records: `orphans` returns the items a record
2354
- * orphans against the new declarations, `close` builds the op batch that closes one
2355
- * orphan, `resolutions` names the resolutions a close may name — each a templated-text
2356
- * jq program (inline `content` or a stored template `id`).
2357
- */
2358
- export const templateReconcile = z.object({
2359
- orphans: templatedText,
2360
- close: templatedText,
2361
- resolutions: templatedText,
2362
- });
2363
- /**
2364
- * A template's declarations section: `schema` is the JSON schema of the static values an
2365
- * attachment stores, and `check` is an optional attach-time predicate authored as a
2366
- * templated text (inline `content` or a stored template `id`), `null` when the template
2367
- * declares none.
2368
- */
2369
- export const templateDeclarations = z.object({
2370
- schema: jsonSchema,
2371
- check: templatedText.nullable().default(null),
2372
- });
2373
- /**
2374
- * The platform half of a state-template document (mirrors StateTemplateDocument).
2375
- * `template_jq` maps a name to its definition (each carrying its `purpose`);
2376
- * `reconcile` is the settle policy; `declarations` is the static-values section — all
2377
- * optional (`null` when the template declares none).
2378
- */
2379
- export const stateTemplateDocument = z.object({
2380
- kind: z.literal('state-template').default('state-template'),
2381
- name: z.string(),
2382
- description: z.string().default(''),
2383
- parameters: z.record(z.string(), z.unknown()).default({}),
2384
- schema: jsonSchema,
2385
- regimes: z.array(stateRegime).default([]),
2386
- declarations: templateDeclarations.nullable().default(null),
2387
- trace: z.record(z.string(), z.unknown()).default({}),
2388
- template_jq: z.record(z.string(), templateJq).nullable().default(null),
2389
- reconcile: templateReconcile.nullable().default(null),
2390
- });
2391
- /**
2392
- * One row of `GET /api/state-templates`. The template document plus the two derived columns
2393
- * the Templates tab shows: `attached_to` (how many states attach it — a delete is refused
2394
- * while > 0) and `shipped_default` (a platform-shipped template). Both are server-derived
2395
- * columns; the document fields are verbatim.
2396
- */
2397
- export const stateTemplateListItem = stateTemplateDocument.extend({
2398
- attached_to: z.number().default(0),
2399
- shipped_default: z.boolean().default(false),
2400
- });
2401
- export const stateTemplateList = z.array(stateTemplateListItem);
2402
- /** A read record: the state, its subject, the document + monotonic seq, and any fold. */
2403
- export const stateRecord = z.object({
2404
- state: z.string(),
2405
- subject: stateSubject,
2406
- data: z.record(z.string(), z.unknown()).default({}),
2407
- seq: z.number(),
2408
- canonical_subject: stateSubject,
2409
- folded_from: z.array(stateSubject).default([]),
2410
- });
2411
- /** The outcome of an `apply` (a delta batch): whether it applied + the resulting doc. */
2412
- export const applyResult = z.object({
2413
- applied: z.boolean(),
2414
- data: z.record(z.string(), z.unknown()).nullable().default(null),
2415
- seq: z.number().nullable().default(null),
2416
- skipped: z.array(z.record(z.string(), z.unknown())).default([]),
2417
- });
2418
- /**
2419
- * `GET …/template-jq/{name}` — the evaluated template jq's `name`, its `purpose`, and
2420
- * its `value` (the `input` jq's read). GET evaluates ONLY an `input`-purpose jq; a GET
2421
- * on an `update`-purpose name is refused with 422, so the result's purpose is always
2422
- * `input`.
2423
- */
2424
- export const templateJqResult = z.object({
2425
- name: z.string(),
2426
- purpose: z.literal('input'),
2427
- value: z.unknown(),
2428
- });
2429
- /**
2430
- * `POST …/template-jq/{name}` — the applied template jq's outcome. The jq lands an op
2431
- * batch through the same `apply` chokepoint as a delta, so `applied`/`data`/`seq`/`skipped`
2432
- * carry that outcome.
2433
- */
2434
- export const templateJqApplyResult = z.object({
2435
- name: z.string(),
2436
- applied: z.boolean(),
2437
- data: z.record(z.string(), z.unknown()).nullable().default(null),
2438
- seq: z.number().nullable().default(null),
2439
- skipped: z.array(z.record(z.string(), z.unknown())).default([]),
2440
- });
2441
- /**
2442
- * One paged subject row: the addressed `subject` plus its record's last-write
2443
- * `updated_at` — epoch seconds (the store's `extract(epoch FROM updated_at)`), served
2444
- * raw (no `mode="json"` dump), and `NOT NULL` on every record, so a plain number.
2445
- */
2446
- export const subjectRow = z.object({
2447
- subject: stateSubject,
2448
- updated_at: z.number(),
2449
- });
2450
- /**
2451
- * `GET /api/states/{name}/subjects` — a keyset page of subject rows (optionally of one
2452
- * `kind`): the matched `subjects` plus the `next_cursor` a "Load more" carries back
2453
- * (`null` at the end).
2454
- */
2455
- export const subjectPage = z.object({
2456
- subjects: z.array(subjectRow),
2457
- next_cursor: z.string().nullable().default(null),
2458
- });
2459
- /**
2460
- * `POST /api/states/{name}/records/search` — a keyset page of the subject rows whose
2461
- * document contains the filters (a JSONB containment match). Each hit's `subject` opens
2462
- * the record page; `next_cursor` pages the rest.
2463
- */
2464
- export const recordSearchPage = z.object({
2465
- matches: z.array(subjectRow),
2466
- next_cursor: z.string().nullable().default(null),
2467
- });
2468
- /** A WriteEntry's origin completed by the platform chokepoint (mirrors CompletedOrigin). */
2469
- export const completedOrigin = z.object({
2470
- consumer: z.string().nullable().default(null),
2471
- // Opaque consumer-supplied provenance JSON the platform stores as is.
2472
- meta: z.record(z.string(), z.unknown()).nullable().default(null),
2473
- run_id: z.string().nullable().default(null),
2474
- op_id: z.string().nullable().default(null),
2475
- door: z.string(),
2476
- actor: z.string().nullable().default(null),
2477
- turn_id: z.string().nullable().default(null),
2478
- inbound_id: z.string().nullable().default(null),
2479
- });
2480
- /** One row of a subject's audit trail (mirrors WriteEntry). `paths` are absolute. */
2481
- export const writeEntry = z.object({
2482
- seq: z.number(),
2483
- at: z.string(),
2484
- origin: completedOrigin,
2485
- paths: z.array(z.array(z.union([z.string(), z.number()]))).default([]),
2486
- });
2487
- /** `GET /api/states/{name}/records/.../writes` — a keyset page of the audit trail. */
2488
- export const writesPage = z.object({
2489
- items: z.array(writeEntry),
2490
- next_cursor: z.string().nullable().default(null),
2491
- });
2492
- /** Where the Studio opens a consumer (mirrors ConsumerLink): a token+search OR plugin path. */
2493
- export const consumerLink = z.object({
2494
- token: z.string().nullable().default(null),
2495
- plugin_path: z.string().nullable().default(null),
2496
- search: z.record(z.string(), z.unknown()).nullable().default(null),
2497
- });
2498
- /**
2499
- * One thing that binds a state (mirrors ConsumerRow): its `kind` (flow / hook / schedule
2500
- * / agent), `name`, human `detail` and optional `link`. `unavailable` marks a consumer
2501
- * family that cannot be listed on this deployment (e.g. no scheduling backend), surfaced
2502
- * as a muted line — never swallowed.
2503
- */
2504
- export const consumerRow = z.object({
2505
- kind: z.string(),
2506
- name: z.string().nullable().default(null),
2507
- detail: z.string().nullable().default(null),
2508
- link: consumerLink.nullable().default(null),
2509
- unavailable: z.string().nullable().default(null),
2510
- });
2511
- /** `GET /api/states/{name}/consumers` — the union of every registered lister's rows. */
2512
- export const stateConsumers = z.array(consumerRow);
2513
- /** `DELETE /api/states/{name}` — the removed state. */
2514
- export const stateDeleted = z.object({ name: z.string(), deleted: z.literal(true) });
2515
- /** `PUT /api/states/{name}/attachments/{template}` — the template now attached to the state. */
2516
- export const stateAttached = z.object({
2517
- attached: z.literal(true),
2518
- state: z.string(),
2519
- template: z.string(),
2520
- });
2521
- /** `PATCH /api/states/{name}/attachments/{template}` — the attachment whose declarations were rewritten. */
2522
- export const stateAttachmentUpdated = z.object({
2523
- updated: z.literal(true),
2524
- state: z.string(),
2525
- template: z.string(),
2526
- });
2527
- /** `DELETE /api/states/{name}/attachments/{template}` — the template detached from the state. */
2528
- export const stateDetached = z.object({
2529
- detached: z.literal(true),
2530
- state: z.string(),
2531
- template: z.string(),
2532
- });
2533
- /**
2534
- * `POST /api/states/{name}/records/.../fold` — the fold report: the `mode`, the
2535
- * `{kind, key}` of the folded `from` and surviving `into` subjects, whether the fold
2536
- * was `already` in place, and how many records were `flattened` into the survivor.
2537
- */
2538
- export const stateFoldReport = z.object({
2539
- mode: z.enum(['switch', 'merge']),
2540
- from: z.object({ kind: z.string(), key: z.string() }),
2541
- into: z.object({ kind: z.string(), key: z.string() }),
2542
- already: z.boolean(),
2543
- flattened: z.number().default(0),
2544
- });
2545
- /** `DELETE /api/state-templates/{name}` — the removed template document. */
2546
- export const stateTemplateDeleted = z.object({ name: z.string(), deleted: z.literal(true) });
2547
- /** `DELETE /api/states/{name}/records/...` — the erased record marker. */
2548
- export const recordErased = z.object({ erased: z.literal(true) });
2549
- /**
2550
- * `POST /api/state-retention/prune` — the retention sweep's per-state deleted counts
2551
- * (`{}` when nothing expired); the UI sums the values for its total.
2552
- */
2553
- export const stateRetentionPruned = z.object({
2554
- pruned: z.record(z.string(), z.number()).default({}),
2555
- });
2556
- //# sourceMappingURL=schemas.js.map