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