@tai42/api-client 16.0.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/dist/agents.d.ts.map +1 -1
  2. package/dist/agents.js +1 -1
  3. package/dist/agents.js.map +1 -1
  4. package/dist/client/agents-client.d.ts +26 -0
  5. package/dist/client/agents-client.d.ts.map +1 -0
  6. package/dist/client/agents-client.js +19 -0
  7. package/dist/client/agents-client.js.map +1 -0
  8. package/dist/client/auth-client.d.ts +247 -0
  9. package/dist/client/auth-client.d.ts.map +1 -0
  10. package/dist/client/auth-client.js +73 -0
  11. package/dist/client/auth-client.js.map +1 -0
  12. package/dist/client/backend-client.d.ts +39 -0
  13. package/dist/client/backend-client.d.ts.map +1 -0
  14. package/dist/client/backend-client.js +22 -0
  15. package/dist/client/backend-client.js.map +1 -0
  16. package/dist/client/backup-client.d.ts +56 -0
  17. package/dist/client/backup-client.d.ts.map +1 -0
  18. package/dist/client/backup-client.js +12 -0
  19. package/dist/client/backup-client.js.map +1 -0
  20. package/dist/client/capabilities-client.d.ts +40 -0
  21. package/dist/client/capabilities-client.d.ts.map +1 -0
  22. package/dist/client/capabilities-client.js +19 -0
  23. package/dist/client/capabilities-client.js.map +1 -0
  24. package/dist/client/channels-client.d.ts +7 -0
  25. package/dist/client/channels-client.d.ts.map +1 -0
  26. package/dist/client/channels-client.js +10 -0
  27. package/dist/client/channels-client.js.map +1 -0
  28. package/dist/client/config-client.d.ts +59 -0
  29. package/dist/client/config-client.d.ts.map +1 -0
  30. package/dist/client/config-client.js +18 -0
  31. package/dist/client/config-client.js.map +1 -0
  32. package/dist/client/connectors-client.d.ts +225 -0
  33. package/dist/client/connectors-client.d.ts.map +1 -0
  34. package/dist/client/connectors-client.js +31 -0
  35. package/dist/client/connectors-client.js.map +1 -0
  36. package/dist/client/conversations-client.d.ts +398 -0
  37. package/dist/client/conversations-client.d.ts.map +1 -0
  38. package/dist/client/conversations-client.js +120 -0
  39. package/dist/client/conversations-client.js.map +1 -0
  40. package/dist/client/hooks-client.d.ts +112 -0
  41. package/dist/client/hooks-client.d.ts.map +1 -0
  42. package/dist/client/hooks-client.js +25 -0
  43. package/dist/client/hooks-client.js.map +1 -0
  44. package/dist/{client.d.ts → client/index.d.ts} +2336 -2708
  45. package/dist/client/index.d.ts.map +1 -0
  46. package/dist/client/index.js +79 -0
  47. package/dist/client/index.js.map +1 -0
  48. package/dist/client/interactions-client.d.ts +52 -0
  49. package/dist/client/interactions-client.d.ts.map +1 -0
  50. package/dist/client/interactions-client.js +84 -0
  51. package/dist/client/interactions-client.js.map +1 -0
  52. package/dist/client/login-client.d.ts +69 -0
  53. package/dist/client/login-client.d.ts.map +1 -0
  54. package/dist/client/login-client.js +137 -0
  55. package/dist/client/login-client.js.map +1 -0
  56. package/dist/client/manifest-client.d.ts +354 -0
  57. package/dist/client/manifest-client.d.ts.map +1 -0
  58. package/dist/client/manifest-client.js +76 -0
  59. package/dist/client/manifest-client.js.map +1 -0
  60. package/dist/client/marketplace-client.d.ts +274 -0
  61. package/dist/client/marketplace-client.d.ts.map +1 -0
  62. package/dist/client/marketplace-client.js +46 -0
  63. package/dist/client/marketplace-client.js.map +1 -0
  64. package/dist/client/mcp-config-schema-client.d.ts +5 -0
  65. package/dist/client/mcp-config-schema-client.d.ts.map +1 -0
  66. package/dist/client/mcp-config-schema-client.js +9 -0
  67. package/dist/client/mcp-config-schema-client.js.map +1 -0
  68. package/dist/client/notifications-client.d.ts +27 -0
  69. package/dist/client/notifications-client.d.ts.map +1 -0
  70. package/dist/client/notifications-client.js +11 -0
  71. package/dist/client/notifications-client.js.map +1 -0
  72. package/dist/client/observability-client.d.ts +103 -0
  73. package/dist/client/observability-client.d.ts.map +1 -0
  74. package/dist/client/observability-client.js +43 -0
  75. package/dist/client/observability-client.js.map +1 -0
  76. package/dist/client/plugins-client.d.ts +17 -0
  77. package/dist/client/plugins-client.d.ts.map +1 -0
  78. package/dist/client/plugins-client.js +9 -0
  79. package/dist/client/plugins-client.js.map +1 -0
  80. package/dist/client/policy-client.d.ts +37 -0
  81. package/dist/client/policy-client.d.ts.map +1 -0
  82. package/dist/client/policy-client.js +26 -0
  83. package/dist/client/policy-client.js.map +1 -0
  84. package/dist/client/presets-client.d.ts +334 -0
  85. package/dist/client/presets-client.d.ts.map +1 -0
  86. package/dist/client/presets-client.js +38 -0
  87. package/dist/client/presets-client.js.map +1 -0
  88. package/dist/client/profiles-client.d.ts +101 -0
  89. package/dist/client/profiles-client.d.ts.map +1 -0
  90. package/dist/client/profiles-client.js +37 -0
  91. package/dist/client/profiles-client.js.map +1 -0
  92. package/dist/client/schedules-client.d.ts +27 -0
  93. package/dist/client/schedules-client.d.ts.map +1 -0
  94. package/dist/client/schedules-client.js +13 -0
  95. package/dist/client/schedules-client.js.map +1 -0
  96. package/dist/client/settings-schema-client.d.ts +23 -0
  97. package/dist/client/settings-schema-client.d.ts.map +1 -0
  98. package/dist/client/settings-schema-client.js +9 -0
  99. package/dist/client/settings-schema-client.js.map +1 -0
  100. package/dist/client/states-client.d.ts +470 -0
  101. package/dist/client/states-client.d.ts.map +1 -0
  102. package/dist/client/states-client.js +144 -0
  103. package/dist/client/states-client.js.map +1 -0
  104. package/dist/client/storage-client.d.ts +40 -0
  105. package/dist/client/storage-client.d.ts.map +1 -0
  106. package/dist/client/storage-client.js +43 -0
  107. package/dist/client/storage-client.js.map +1 -0
  108. package/dist/client/sub-mcp-client.d.ts +17 -0
  109. package/dist/client/sub-mcp-client.d.ts.map +1 -0
  110. package/dist/client/sub-mcp-client.js +14 -0
  111. package/dist/client/sub-mcp-client.js.map +1 -0
  112. package/dist/client/system-client.d.ts +11 -0
  113. package/dist/client/system-client.d.ts.map +1 -0
  114. package/dist/client/system-client.js +13 -0
  115. package/dist/client/system-client.js.map +1 -0
  116. package/dist/client/templates-client.d.ts +29 -0
  117. package/dist/client/templates-client.d.ts.map +1 -0
  118. package/dist/client/templates-client.js +21 -0
  119. package/dist/client/templates-client.js.map +1 -0
  120. package/dist/client/tool-extensions-client.d.ts +53 -0
  121. package/dist/client/tool-extensions-client.d.ts.map +1 -0
  122. package/dist/client/tool-extensions-client.js +21 -0
  123. package/dist/client/tool-extensions-client.js.map +1 -0
  124. package/dist/client/tool-meta-client.d.ts +69 -0
  125. package/dist/client/tool-meta-client.d.ts.map +1 -0
  126. package/dist/client/tool-meta-client.js +38 -0
  127. package/dist/client/tool-meta-client.js.map +1 -0
  128. package/dist/client/tools-client.d.ts +83 -0
  129. package/dist/client/tools-client.d.ts.map +1 -0
  130. package/dist/client/tools-client.js +38 -0
  131. package/dist/client/tools-client.js.map +1 -0
  132. package/dist/client/transport.d.ts +13 -0
  133. package/dist/client/transport.d.ts.map +1 -0
  134. package/dist/client/transport.js +10 -0
  135. package/dist/client/transport.js.map +1 -0
  136. package/dist/client/trigger-links-client.d.ts +44 -0
  137. package/dist/client/trigger-links-client.d.ts.map +1 -0
  138. package/dist/client/trigger-links-client.js +30 -0
  139. package/dist/client/trigger-links-client.js.map +1 -0
  140. package/dist/client/web-entry-gate-client.d.ts +34 -0
  141. package/dist/client/web-entry-gate-client.d.ts.map +1 -0
  142. package/dist/client/web-entry-gate-client.js +25 -0
  143. package/dist/client/web-entry-gate-client.js.map +1 -0
  144. package/dist/errors.d.ts +11 -0
  145. package/dist/errors.d.ts.map +1 -1
  146. package/dist/errors.js +15 -0
  147. package/dist/errors.js.map +1 -1
  148. package/dist/http.d.ts.map +1 -1
  149. package/dist/http.js +33 -17
  150. package/dist/http.js.map +1 -1
  151. package/dist/index.d.ts +12 -12
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +6 -13
  154. package/dist/index.js.map +1 -1
  155. package/dist/schemas/api-keys.d.ts +43 -0
  156. package/dist/schemas/api-keys.d.ts.map +1 -0
  157. package/dist/schemas/api-keys.js +29 -0
  158. package/dist/schemas/api-keys.js.map +1 -0
  159. package/dist/schemas/auth-routes.d.ts +78 -0
  160. package/dist/schemas/auth-routes.d.ts.map +1 -0
  161. package/dist/schemas/auth-routes.js +42 -0
  162. package/dist/schemas/auth-routes.js.map +1 -0
  163. package/dist/schemas/backup.d.ts +78 -0
  164. package/dist/schemas/backup.d.ts.map +1 -0
  165. package/dist/schemas/backup.js +33 -0
  166. package/dist/schemas/backup.js.map +1 -0
  167. package/dist/schemas/capabilities.d.ts +109 -0
  168. package/dist/schemas/capabilities.d.ts.map +1 -0
  169. package/dist/schemas/capabilities.js +78 -0
  170. package/dist/schemas/capabilities.js.map +1 -0
  171. package/dist/schemas/channels.d.ts +7 -0
  172. package/dist/schemas/channels.d.ts.map +1 -0
  173. package/dist/schemas/channels.js +9 -0
  174. package/dist/schemas/channels.js.map +1 -0
  175. package/dist/schemas/config.d.ts +17 -0
  176. package/dist/schemas/config.d.ts.map +1 -0
  177. package/dist/schemas/config.js +17 -0
  178. package/dist/schemas/config.js.map +1 -0
  179. package/dist/schemas/connectors.d.ts +383 -0
  180. package/dist/schemas/connectors.d.ts.map +1 -0
  181. package/dist/schemas/connectors.js +122 -0
  182. package/dist/schemas/connectors.js.map +1 -0
  183. package/dist/schemas/conversations.d.ts +600 -0
  184. package/dist/schemas/conversations.d.ts.map +1 -0
  185. package/dist/schemas/conversations.js +208 -0
  186. package/dist/schemas/conversations.js.map +1 -0
  187. package/dist/schemas/extensions.d.ts +44 -0
  188. package/dist/schemas/extensions.d.ts.map +1 -0
  189. package/dist/schemas/extensions.js +29 -0
  190. package/dist/schemas/extensions.js.map +1 -0
  191. package/dist/schemas/fleet.d.ts +271 -0
  192. package/dist/schemas/fleet.d.ts.map +1 -0
  193. package/dist/schemas/fleet.js +132 -0
  194. package/dist/schemas/fleet.js.map +1 -0
  195. package/dist/schemas/hooks.d.ts +120 -0
  196. package/dist/schemas/hooks.d.ts.map +1 -0
  197. package/dist/schemas/hooks.js +44 -0
  198. package/dist/schemas/hooks.js.map +1 -0
  199. package/dist/schemas/index.d.ts +40 -0
  200. package/dist/schemas/index.d.ts.map +1 -0
  201. package/dist/schemas/index.js +40 -0
  202. package/dist/schemas/index.js.map +1 -0
  203. package/dist/schemas/interactions.d.ts +156 -0
  204. package/dist/schemas/interactions.d.ts.map +1 -0
  205. package/dist/schemas/interactions.js +124 -0
  206. package/dist/schemas/interactions.js.map +1 -0
  207. package/dist/schemas/login.d.ts +93 -0
  208. package/dist/schemas/login.d.ts.map +1 -0
  209. package/dist/schemas/login.js +61 -0
  210. package/dist/schemas/login.js.map +1 -0
  211. package/dist/schemas/manifest-mcp.d.ts +125 -0
  212. package/dist/schemas/manifest-mcp.d.ts.map +1 -0
  213. package/dist/schemas/manifest-mcp.js +61 -0
  214. package/dist/schemas/manifest-mcp.js.map +1 -0
  215. package/dist/schemas/marketplace.d.ts +708 -0
  216. package/dist/schemas/marketplace.d.ts.map +1 -0
  217. package/dist/schemas/marketplace.js +387 -0
  218. package/dist/schemas/marketplace.js.map +1 -0
  219. package/dist/schemas/notifications.d.ts +61 -0
  220. package/dist/schemas/notifications.d.ts.map +1 -0
  221. package/dist/schemas/notifications.js +38 -0
  222. package/dist/schemas/notifications.js.map +1 -0
  223. package/dist/schemas/observability.d.ts +114 -0
  224. package/dist/schemas/observability.d.ts.map +1 -0
  225. package/dist/schemas/observability.js +74 -0
  226. package/dist/schemas/observability.js.map +1 -0
  227. package/dist/schemas/policy.d.ts +58 -0
  228. package/dist/schemas/policy.d.ts.map +1 -0
  229. package/dist/schemas/policy.js +32 -0
  230. package/dist/schemas/policy.js.map +1 -0
  231. package/dist/schemas/presets.d.ts +258 -0
  232. package/dist/schemas/presets.d.ts.map +1 -0
  233. package/dist/schemas/presets.js +95 -0
  234. package/dist/schemas/presets.js.map +1 -0
  235. package/dist/schemas/principals.d.ts +54 -0
  236. package/dist/schemas/principals.d.ts.map +1 -0
  237. package/dist/schemas/principals.js +34 -0
  238. package/dist/schemas/principals.js.map +1 -0
  239. package/dist/schemas/profiles.d.ts +180 -0
  240. package/dist/schemas/profiles.d.ts.map +1 -0
  241. package/dist/schemas/profiles.js +100 -0
  242. package/dist/schemas/profiles.js.map +1 -0
  243. package/dist/schemas/roles.d.ts +125 -0
  244. package/dist/schemas/roles.d.ts.map +1 -0
  245. package/dist/schemas/roles.js +46 -0
  246. package/dist/schemas/roles.js.map +1 -0
  247. package/dist/schemas/schedules.d.ts +25 -0
  248. package/dist/schemas/schedules.d.ts.map +1 -0
  249. package/dist/schemas/schedules.js +24 -0
  250. package/dist/schemas/schedules.js.map +1 -0
  251. package/dist/schemas/served.d.ts +626 -0
  252. package/dist/schemas/served.d.ts.map +1 -0
  253. package/dist/schemas/served.js +28 -0
  254. package/dist/schemas/served.js.map +1 -0
  255. package/dist/schemas/settings-schema.d.ts +23 -0
  256. package/dist/schemas/settings-schema.d.ts.map +1 -0
  257. package/dist/schemas/settings-schema.js +25 -0
  258. package/dist/schemas/settings-schema.js.map +1 -0
  259. package/dist/schemas/setup.d.ts +43 -0
  260. package/dist/schemas/setup.d.ts.map +1 -0
  261. package/dist/schemas/setup.js +19 -0
  262. package/dist/schemas/setup.js.map +1 -0
  263. package/dist/schemas/shared.d.ts +20 -0
  264. package/dist/schemas/shared.d.ts.map +1 -0
  265. package/dist/schemas/shared.js +20 -0
  266. package/dist/schemas/shared.js.map +1 -0
  267. package/dist/schemas/states.d.ts +499 -0
  268. package/dist/schemas/states.d.ts.map +1 -0
  269. package/dist/schemas/states.js +220 -0
  270. package/dist/schemas/states.js.map +1 -0
  271. package/dist/schemas/storage.d.ts +37 -0
  272. package/dist/schemas/storage.d.ts.map +1 -0
  273. package/dist/schemas/storage.js +24 -0
  274. package/dist/schemas/storage.js.map +1 -0
  275. package/dist/schemas/studio-plugins.d.ts +30 -0
  276. package/dist/schemas/studio-plugins.d.ts.map +1 -0
  277. package/dist/schemas/studio-plugins.js +20 -0
  278. package/dist/schemas/studio-plugins.js.map +1 -0
  279. package/dist/schemas/sub-mcp.d.ts +29 -0
  280. package/dist/schemas/sub-mcp.d.ts.map +1 -0
  281. package/dist/schemas/sub-mcp.js +22 -0
  282. package/dist/schemas/sub-mcp.js.map +1 -0
  283. package/dist/schemas/system-kinds.d.ts +122 -0
  284. package/dist/schemas/system-kinds.d.ts.map +1 -0
  285. package/dist/schemas/system-kinds.js +40 -0
  286. package/dist/schemas/system-kinds.js.map +1 -0
  287. package/dist/schemas/templates.d.ts +26 -0
  288. package/dist/schemas/templates.d.ts.map +1 -0
  289. package/dist/schemas/templates.js +11 -0
  290. package/dist/schemas/templates.js.map +1 -0
  291. package/dist/schemas/tool-meta.d.ts +64 -0
  292. package/dist/schemas/tool-meta.d.ts.map +1 -0
  293. package/dist/schemas/tool-meta.js +44 -0
  294. package/dist/schemas/tool-meta.js.map +1 -0
  295. package/dist/schemas/tools.d.ts +62 -0
  296. package/dist/schemas/tools.d.ts.map +1 -0
  297. package/dist/schemas/tools.js +53 -0
  298. package/dist/schemas/tools.js.map +1 -0
  299. package/dist/schemas/trigger-links.d.ts +71 -0
  300. package/dist/schemas/trigger-links.d.ts.map +1 -0
  301. package/dist/schemas/trigger-links.js +46 -0
  302. package/dist/schemas/trigger-links.js.map +1 -0
  303. package/dist/schemas/web-entry.d.ts +45 -0
  304. package/dist/schemas/web-entry.d.ts.map +1 -0
  305. package/dist/schemas/web-entry.js +36 -0
  306. package/dist/schemas/web-entry.js.map +1 -0
  307. package/dist/tool-runs.d.ts.map +1 -1
  308. package/dist/tool-runs.js.map +1 -1
  309. package/package.json +1 -1
  310. package/src/agents.ts +2 -2
  311. package/src/client/agents-client.ts +23 -0
  312. package/src/client/auth-client.ts +170 -0
  313. package/src/client/backend-client.ts +25 -0
  314. package/src/client/backup-client.ts +16 -0
  315. package/src/client/capabilities-client.ts +22 -0
  316. package/src/client/channels-client.ts +11 -0
  317. package/src/client/config-client.ts +21 -0
  318. package/src/client/connectors-client.ts +52 -0
  319. package/src/client/conversations-client.ts +240 -0
  320. package/src/client/hooks-client.ts +44 -0
  321. package/src/client/index.ts +122 -0
  322. package/src/client/interactions-client.ts +89 -0
  323. package/src/client/login-client.ts +150 -0
  324. package/src/client/manifest-client.ts +118 -0
  325. package/src/client/marketplace-client.ts +122 -0
  326. package/src/client/mcp-config-schema-client.ts +11 -0
  327. package/src/client/notifications-client.ts +13 -0
  328. package/src/client/observability-client.ts +82 -0
  329. package/src/client/plugins-client.ts +11 -0
  330. package/src/client/policy-client.ts +41 -0
  331. package/src/client/presets-client.ts +124 -0
  332. package/src/client/profiles-client.ts +51 -0
  333. package/src/client/schedules-client.ts +22 -0
  334. package/src/client/settings-schema-client.ts +11 -0
  335. package/src/client/states-client.ts +292 -0
  336. package/src/client/storage-client.ts +63 -0
  337. package/src/client/sub-mcp-client.ts +17 -0
  338. package/src/client/system-client.ts +16 -0
  339. package/src/client/templates-client.ts +28 -0
  340. package/src/client/tool-extensions-client.ts +24 -0
  341. package/src/client/tool-meta-client.ts +65 -0
  342. package/src/client/tools-client.ts +66 -0
  343. package/src/client/transport.ts +18 -0
  344. package/src/client/trigger-links-client.ts +51 -0
  345. package/src/client/web-entry-gate-client.ts +45 -0
  346. package/src/errors.ts +16 -0
  347. package/src/http.ts +48 -24
  348. package/src/index.ts +231 -226
  349. package/src/schemas/api-keys.ts +34 -0
  350. package/src/schemas/auth-routes.ts +48 -0
  351. package/src/schemas/backup.ts +43 -0
  352. package/src/schemas/capabilities.ts +92 -0
  353. package/src/schemas/channels.ts +11 -0
  354. package/src/schemas/config.ts +17 -0
  355. package/src/schemas/connectors.ts +139 -0
  356. package/src/schemas/conversations.ts +257 -0
  357. package/src/schemas/extensions.ts +34 -0
  358. package/src/schemas/fleet.ts +154 -0
  359. package/src/schemas/hooks.ts +57 -0
  360. package/src/schemas/index.ts +39 -0
  361. package/src/schemas/interactions.ts +147 -0
  362. package/src/schemas/login.ts +69 -0
  363. package/src/schemas/manifest-mcp.ts +70 -0
  364. package/src/schemas/marketplace.ts +447 -0
  365. package/src/schemas/notifications.ts +43 -0
  366. package/src/schemas/observability.ts +87 -0
  367. package/src/schemas/policy.ts +38 -0
  368. package/src/schemas/presets.ts +114 -0
  369. package/src/schemas/principals.ts +41 -0
  370. package/src/schemas/profiles.ts +120 -0
  371. package/src/schemas/roles.ts +58 -0
  372. package/src/schemas/schedules.ts +28 -0
  373. package/src/schemas/served.ts +50 -0
  374. package/src/schemas/settings-schema.ts +32 -0
  375. package/src/schemas/setup.ts +39 -0
  376. package/src/schemas/shared.ts +21 -0
  377. package/src/schemas/states.ts +273 -0
  378. package/src/schemas/storage.ts +31 -0
  379. package/src/schemas/studio-plugins.ts +21 -0
  380. package/src/schemas/sub-mcp.ts +24 -0
  381. package/src/schemas/system-kinds.ts +47 -0
  382. package/src/schemas/templates.ts +12 -0
  383. package/src/schemas/tool-meta.ts +51 -0
  384. package/src/schemas/tools.ts +62 -0
  385. package/src/schemas/trigger-links.ts +55 -0
  386. package/src/schemas/web-entry.ts +46 -0
  387. package/src/tool-runs.ts +1 -1
  388. package/dist/client.d.ts.map +0 -1
  389. package/dist/client.js +0 -920
  390. package/dist/client.js.map +0 -1
  391. package/dist/schemas.d.ts +0 -5172
  392. package/dist/schemas.d.ts.map +0 -1
  393. package/dist/schemas.js +0 -2268
  394. package/dist/schemas.js.map +0 -1
  395. package/src/client.ts +0 -1771
  396. package/src/schemas.ts +0 -2760
@@ -0,0 +1,63 @@
1
+ /** Storage resource CRUD sub-client. */
2
+ import { apiDownload, isUnsafePathSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ /**
7
+ * Body for a storage upload (POST `/api/storage/resources`). Exactly ONE of
8
+ * `content_text` (stored verbatim) or `content_base64` (a base64 string decoded to
9
+ * bytes) supplies the content for `id`; uploading an existing id overwrites it
10
+ * (provider passthrough semantics).
11
+ */
12
+ export interface StorageUploadBody {
13
+ readonly id: string;
14
+ readonly content_text?: string;
15
+ readonly content_base64?: string;
16
+ }
17
+
18
+ /**
19
+ * Encode a possibly-nested storage id / directory path for use in a URL path,
20
+ * percent-encoding each segment while preserving the `/` separators. A storage id
21
+ * can be a nested path (e.g. `a/b c.txt`); encoding each segment independently keeps
22
+ * the slashes structural and escapes everything else (spaces, `#`, `?`, …).
23
+ *
24
+ * Dot segments (`.`, `..`), empty segments (a leading/trailing/doubled `/`, or an
25
+ * empty id) are REJECTED loudly rather than encoded: the browser's WHATWG URL parser
26
+ * removes dot-segments from a relative request path before it is sent — collapsing
27
+ * `..` even when percent-encoded — so such an id can never be faithfully transmitted.
28
+ * Left to encode, it would silently retarget the request at an unrelated same-origin
29
+ * route. The server enforces the same relative-path rule; failing fast here keeps a
30
+ * malformed id from ever leaving the client.
31
+ */
32
+ function encodePath(id: string): string {
33
+ const segments = id.split('/');
34
+ if (segments.some(isUnsafePathSegment)) {
35
+ throw new Error("storage path must be a relative path with no empty, '.', or '..' segment");
36
+ }
37
+ return segments.map(encodeURIComponent).join('/');
38
+ }
39
+
40
+ export function storageClient(t: Transport) {
41
+ const { config, req } = t;
42
+ return {
43
+ // The content store a storage-provider plugin exposes. Dead by default:
44
+ // `getStorageInfo` reports `present: false` (a 200 empty state) and every other
45
+ // door answers a loud 501 when no provider is installed. Ids may be nested
46
+ // paths, so each is `encodePath`-encoded (per-segment, `/` preserved).
47
+ getStorageInfo: (signal?: AbortSignal) => req('/api/storage', s.storageInfo, { signal }),
48
+ listStorageResources: (signal?: AbortSignal) =>
49
+ req('/api/storage/resources', s.storageResourceList, { signal }),
50
+ statStorageResource: (id: string, signal?: AbortSignal) =>
51
+ req(`/api/storage/resources/${encodePath(id)}/stat`, s.storageResourceStat, { signal }),
52
+ downloadStorageResource: (id: string, signal?: AbortSignal): Promise<Blob> =>
53
+ apiDownload(config, `/api/storage/resources/${encodePath(id)}/content`, { signal }),
54
+ uploadStorageResource: (body: StorageUploadBody) =>
55
+ req('/api/storage/resources', s.storageResourceStored, { method: 'POST', body }),
56
+ deleteStorageResource: (id: string) =>
57
+ req(`/api/storage/resources/${encodePath(id)}`, s.storageResourceDeleted, {
58
+ method: 'DELETE',
59
+ }),
60
+ deleteStorageDir: (path: string) =>
61
+ req(`/api/storage/dirs/${encodePath(path)}`, s.storageDirDeleted, { method: 'DELETE' }),
62
+ };
63
+ }
@@ -0,0 +1,17 @@
1
+ /** Sub-MCP mount registry sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ export function subMcpClient(t: Transport) {
7
+ const { req } = t;
8
+ return {
9
+ listSubMcp: (signal?: AbortSignal) => req('/api/sub-mcp', s.subMcpList, { signal }),
10
+ // `transport` is optional at the client boundary (the server defaults it to
11
+ // `http`); when omitted, JSON.stringify drops the undefined key from the body.
12
+ createSubMcp: (slug: string, tools: string[], transport?: string) =>
13
+ req('/api/sub-mcp', s.subMcpCreated, { method: 'POST', body: { slug, tools, transport } }),
14
+ deleteSubMcp: (slug: string) =>
15
+ req(`/api/sub-mcp/${encodeSegment(slug)}`, s.subMcpRemoved, { method: 'DELETE' }),
16
+ };
17
+ }
@@ -0,0 +1,16 @@
1
+ /** System health and pluggable-kind status sub-client. */
2
+ import { apiText } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ export function systemClient(t: Transport) {
7
+ const { config, req } = t;
8
+ return {
9
+ getHealth: (signal?: AbortSignal) => apiText(config, '/health', { signal }),
10
+
11
+ // The pluggable-kind status table (`GET /api/system/kinds`) — a JSON `{data}`
12
+ // envelope, one row per kind with its active/default/off state.
13
+ getSystemKinds: (signal?: AbortSignal) =>
14
+ req('/api/system/kinds', s.kindStatusList, { signal }),
15
+ };
16
+ }
@@ -0,0 +1,28 @@
1
+ /** Prompt-template listing, render and mutation sub-client. */
2
+ import * as s from '../schemas';
3
+ import type { Transport } from './transport';
4
+
5
+ export function templatesClient(t: Transport) {
6
+ const { req } = t;
7
+ return {
8
+ listTemplates: (signal?: AbortSignal) => req('/api/templates', s.templateNames, { signal }),
9
+ getTemplate: (templateId: string) =>
10
+ req('/api/template', s.templateDetail, { method: 'POST', body: { template_id: templateId } }),
11
+ uploadTemplate: (path: string, content: string) =>
12
+ req('/api/upload-template', s.templateUploaded, { method: 'POST', body: { path, content } }),
13
+ deleteTemplate: (path: string) =>
14
+ req('/api/delete-template', s.templateDeleted, { method: 'POST', body: { path } }),
15
+ // Delete every stored template under a directory prefix. Unlike the idempotent
16
+ // single delete, the server 404s a prefix matching nothing and 400s the template
17
+ // root; those surface as loud errors, never a faked success.
18
+ deleteTemplateDir: (path: string) =>
19
+ req('/api/delete-template-dir', s.templateDirDeleted, { method: 'POST', body: { path } }),
20
+ // Render one authored text — inline `content` OR a stored template `id`, plus its
21
+ // render `kwargs` — and parse the rendered output. The value is the shared
22
+ // `TemplatedText` (exactly one source); the route wraps it under `text`.
23
+ renderTemplate: (text: s.TemplatedText) =>
24
+ req('/api/render-template', s.templateRendered, { method: 'POST', body: { text } }),
25
+ clearTemplatesCache: () =>
26
+ req('/api/clear-templates-cache', s.templateCacheCleared, { method: 'POST' }),
27
+ };
28
+ }
@@ -0,0 +1,24 @@
1
+ /** Tool extension listing and authoring sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ export function toolExtensionsClient(t: Transport) {
7
+ const { req } = t;
8
+ return {
9
+ listExtensions: (signal?: AbortSignal) => req('/api/extensions', s.extensions, { signal }),
10
+ // A MANIFEST-provided tool's current combos + the extension catalog. (A preset
11
+ // tool's extensions live on the preset spec, read via `getPreset`.)
12
+ getToolExtensions: (name: string, signal?: AbortSignal) =>
13
+ req(`/api/tools/${encodeSegment(name)}/extensions`, s.toolExtensions, { signal }),
14
+ // Author ALL of a MANIFEST tool's combos at once (the full lossless list; `[]`
15
+ // clears). Returns the apply result: the local reload (`status` + `env_keys`) plus
16
+ // the mode-wrapped fleet `fanout` of the reload it broadcast, from which the shared
17
+ // fleet-report handler surfaces any failed propagation.
18
+ setToolExtensions: (name: string, combos: readonly s.PresetExtensionElement[][]) =>
19
+ req(`/api/tools/${encodeSegment(name)}/extensions`, s.toolExtensionsApplyResult, {
20
+ method: 'POST',
21
+ body: { combos },
22
+ }),
23
+ };
24
+ }
@@ -0,0 +1,65 @@
1
+ /** Tool-organization overlay (folders and meta) sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ /**
7
+ * Merge-patch body for a tool's overlay row (PATCH
8
+ * `/api/tool-meta/tools/{tool_name}`). Only the fields PRESENT are written; a
9
+ * present-null clears (`hidden: null` writes the tri-state "defer to the plugin
10
+ * declaration" state, `display_name: null` clears the label, `folder_id: null`
11
+ * unfiles); a present `tags` array replaces the whole set. Absent fields are left
12
+ * untouched — there is no full-row replace, so a display-name+tags editor sends
13
+ * ONLY those two keys with no risk to `folder_id`/`hidden`. At least one field must
14
+ * be present. A blank/whitespace-only `display_name` is rejected by the API (422) —
15
+ * callers map an emptied label to `null`, never `""`. A present `badges` array
16
+ * replaces the whole overlay set (INFORMATIONAL labels, never an enforced gate).
17
+ */
18
+ export interface ToolMetaPatch {
19
+ readonly display_name?: string | null;
20
+ readonly folder_id?: string | null;
21
+ readonly tags?: readonly string[];
22
+ readonly badges?: readonly string[];
23
+ readonly hidden?: boolean | null;
24
+ }
25
+
26
+ export function toolMetaClient(t: Transport) {
27
+ const { req } = t;
28
+ return {
29
+ // The folder tree + per-tool overlay rows in one read; the features merge the
30
+ // rows against the live tool list and native tags client-side.
31
+ listToolMeta: (signal?: AbortSignal) => req('/api/tool-meta', s.toolMetaOverlay, { signal }),
32
+ // Merge-patch a tool's overlay row (create it if absent). `patch` carries ONLY
33
+ // the fields to change — a present-null clears, an absent field is untouched,
34
+ // and a present `tags` array replaces the set.
35
+ upsertToolMeta: (toolName: string, patch: ToolMetaPatch) =>
36
+ req(`/api/tool-meta/tools/${encodeSegment(toolName)}`, s.toolMetaRecord, {
37
+ method: 'PATCH',
38
+ body: patch,
39
+ }),
40
+ deleteToolMeta: (toolName: string) =>
41
+ req(`/api/tool-meta/tools/${encodeSegment(toolName)}`, s.toolMetaDeleted, {
42
+ method: 'DELETE',
43
+ }),
44
+ createFolder: (name: string, parentId: string | null = null) =>
45
+ req('/api/tool-meta/folders', s.folderRecord, {
46
+ method: 'POST',
47
+ body: { name, parent_id: parentId },
48
+ }),
49
+ renameFolder: (folderId: string, name: string) =>
50
+ req(`/api/tool-meta/folders/${encodeSegment(folderId)}/rename`, s.folderRecord, {
51
+ method: 'POST',
52
+ body: { name },
53
+ }),
54
+ // `null` re-parents to the root.
55
+ moveFolder: (folderId: string, parentId: string | null) =>
56
+ req(`/api/tool-meta/folders/${encodeSegment(folderId)}/move`, s.folderRecord, {
57
+ method: 'POST',
58
+ body: { parent_id: parentId },
59
+ }),
60
+ deleteFolder: (folderId: string) =>
61
+ req(`/api/tool-meta/folders/${encodeSegment(folderId)}`, s.folderDeleted, {
62
+ method: 'DELETE',
63
+ }),
64
+ };
65
+ }
@@ -0,0 +1,66 @@
1
+ /** Tool catalog, run, live-registry admin and tag sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import { getToolRun, listToolRuns, submitToolRun, type SubmitToolRunArgs } from '../tool-runs';
5
+ import type { Transport } from './transport';
6
+
7
+ export interface RunToolArgs {
8
+ readonly tool: string;
9
+ readonly kwargs?: Record<string, unknown>;
10
+ }
11
+
12
+ /**
13
+ * Identify one app tool for a live-registry op (`POST /api/tools/reload`,
14
+ * `POST /api/tools/remove`): its `kind` (the reloader kind a plugin registered,
15
+ * e.g. `example_tool`) and `name`. `targets` optionally restricts the fleet
16
+ * fan-out to specific workers; omit it to reach every worker.
17
+ */
18
+ export interface ToolAdminArgs {
19
+ readonly kind: string;
20
+ readonly name: string;
21
+ readonly targets?: string[];
22
+ }
23
+
24
+ export function toolsClient(t: Transport) {
25
+ const { config, req } = t;
26
+ return {
27
+ listTools: (signal?: AbortSignal) => req('/api/tools', s.toolNames, { signal }),
28
+ getToolSchema: (name: string, signal?: AbortSignal) =>
29
+ req(`/api/tools/${encodeSegment(name)}/schema`, s.toolSchema, { signal }),
30
+ getAllToolSchemas: (signal?: AbortSignal) =>
31
+ req('/api/tools-schema', s.allToolSchemas, { signal }),
32
+ runTool: (args: RunToolArgs, signal?: AbortSignal) =>
33
+ req('/api/run-tool', s.runToolResult, {
34
+ method: 'POST',
35
+ // The backend's `/api/run-tool` door reads `{tool_name, arguments}` (the
36
+ // shape `read_tool_call` enforces and the background `/api/tool-runs`
37
+ // submit already sends). The SPA keeps `tool`/`kwargs` as its ergonomic
38
+ // arg names; map them onto the wire fields here.
39
+ body: { tool_name: args.tool, arguments: args.kwargs ?? {} },
40
+ signal,
41
+ }),
42
+
43
+ // Re-register or remove one app tool by kind+name, applied on this worker and
44
+ // broadcast to the fleet (all workers, or only `targets`); both return the bare
45
+ // per-worker `fleetResult` the shared fleet-report handler renders. Both are
46
+ // fenced + destructive server-side, so a refusal surfaces as a loud error.
47
+ reloadTool: (args: ToolAdminArgs) =>
48
+ req('/api/tools/reload', s.fleetResult, {
49
+ method: 'POST',
50
+ body: { kind: args.kind, name: args.name, targets: args.targets ?? null },
51
+ }),
52
+ removeTool: (args: ToolAdminArgs) =>
53
+ req('/api/tools/remove', s.fleetResult, {
54
+ method: 'POST',
55
+ body: { kind: args.kind, name: args.name, targets: args.targets ?? null },
56
+ }),
57
+
58
+ submitToolRun: (args: SubmitToolRunArgs, signal?: AbortSignal) =>
59
+ submitToolRun(config, args, signal),
60
+ getToolRun: (runId: string, signal?: AbortSignal) => getToolRun(config, runId, signal),
61
+ listToolRuns: (toolName: string, signal?: AbortSignal) =>
62
+ listToolRuns(config, toolName, signal),
63
+
64
+ listToolTags: (signal?: AbortSignal) => req('/api/tools/tags', s.toolTags, { signal }),
65
+ };
66
+ }
@@ -0,0 +1,18 @@
1
+ /** The shared transport seam every sub-client method flows through. */
2
+ import type { ApiConfig } from '../http';
3
+ import { apiRequest } from '../http';
4
+
5
+ /**
6
+ * Build the per-request transport for one client: the bound `config` and the
7
+ * `req` chokepoint that validates every response against its schema.
8
+ */
9
+ export function makeTransport(config: ApiConfig) {
10
+ const req = <S extends Parameters<typeof apiRequest>[2]>(
11
+ path: string,
12
+ schema: S,
13
+ options?: Parameters<typeof apiRequest>[3],
14
+ ) => apiRequest(config, path, schema, options);
15
+ return { config, req };
16
+ }
17
+
18
+ export type Transport = ReturnType<typeof makeTransport>;
@@ -0,0 +1,51 @@
1
+ /** Trigger-link mint, listing and revoke sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ /**
7
+ * Body for minting a trigger link (POST `/api/hooks/trigger-links`). `ttl_seconds`
8
+ * is REQUIRED and nullable — a positive int is a timed link, `null` is a permanent
9
+ * link (`0`/negative is a loud server 400); it is always sent EXPLICITLY (a dropped
10
+ * key 400s server-side). `name` is optional (the server generates one when omitted);
11
+ * `tool_kwargs` is the optional per-link params merged last into every fire.
12
+ */
13
+ export interface TriggerLinkCreateBody {
14
+ readonly topic: string;
15
+ readonly name?: string;
16
+ readonly execution_key: string;
17
+ // The link is a token door; this is its only auth knob (token → token+api_key).
18
+ readonly require_api_key: boolean;
19
+ readonly ttl_seconds: number | null;
20
+ readonly tool_kwargs?: Record<string, unknown> | null;
21
+ }
22
+
23
+ export function triggerLinksClient(t: Transport) {
24
+ const { req } = t;
25
+ return {
26
+ // Mint a trigger link for a topic. `ttl_seconds` is sent EXPLICITLY
27
+ // (including `null` for a permanent link) — the server requires the key present
28
+ // and 400s a dropped one; `tool_kwargs` is omitted when undefined. The raw
29
+ // `token` rides the response ONCE (composed into the QR URL client-side).
30
+ createTriggerLink: (body: TriggerLinkCreateBody) =>
31
+ req('/api/hooks/trigger-links', s.triggerLinkCreated, {
32
+ method: 'POST',
33
+ body: {
34
+ topic: body.topic,
35
+ name: body.name,
36
+ execution_key: body.execution_key,
37
+ require_api_key: body.require_api_key,
38
+ ttl_seconds: body.ttl_seconds,
39
+ tool_kwargs: body.tool_kwargs,
40
+ },
41
+ }),
42
+ // Every live trigger-link record (hash prefix only — never a raw token).
43
+ listTriggerLinks: (signal?: AbortSignal) =>
44
+ req('/api/hooks/trigger-links', s.triggerLinkList, { signal }),
45
+ // Revoke a link by name (immediate + durable). 404 when the name is unknown.
46
+ deleteTriggerLink: (name: string) =>
47
+ req(`/api/hooks/trigger-links/${encodeSegment(name)}`, s.triggerLinkDeleted, {
48
+ method: 'DELETE',
49
+ }),
50
+ };
51
+ }
@@ -0,0 +1,45 @@
1
+ /** Web-channel entry-gate sub-client. */
2
+ import { encodeSegment } from '../http';
3
+ import * as s from '../schemas';
4
+ import type { Transport } from './transport';
5
+
6
+ /**
7
+ * Body for minting a web entry code (POST
8
+ * `/api/channels/web/gates/{identity}/codes`). Both fields are optional-nullable
9
+ * and sent EXPLICITLY: `label` is a human tag (`null` for none); `expires_at` is
10
+ * an ISO-8601 instant the code dies at (`null` for a code that never expires).
11
+ */
12
+ export interface WebEntryCodeMintBody {
13
+ readonly label: string | null;
14
+ readonly expires_at: string | null;
15
+ }
16
+
17
+ export function webEntryGateClient(t: Transport) {
18
+ const { req } = t;
19
+ return {
20
+ // A web route's entry gate, keyed by its `our_identity`: read the flag + its
21
+ // live codes, flip the flag, mint a code (raw returned ONCE), revoke a code by
22
+ // its hash id. The conversation monitor's first route mutations.
23
+ getWebEntryGate: (identity: string, signal?: AbortSignal) =>
24
+ req(`/api/channels/web/gates/${encodeSegment(identity)}`, s.webEntryGate, { signal }),
25
+ setWebEntryGate: (identity: string, enabled: boolean) =>
26
+ req(`/api/channels/web/gates/${encodeSegment(identity)}`, s.webEntryGateState, {
27
+ method: 'PUT',
28
+ body: { enabled },
29
+ }),
30
+ // Mint a code. `label`/`expires_at` are sent EXPLICITLY (including `null`). The
31
+ // raw `code` rides the reply ONCE (composed into the chat URL client-side).
32
+ mintWebEntryCode: (identity: string, body: WebEntryCodeMintBody) =>
33
+ req(`/api/channels/web/gates/${encodeSegment(identity)}/codes`, s.webEntryCodeMinted, {
34
+ method: 'POST',
35
+ body: { label: body.label, expires_at: body.expires_at },
36
+ }),
37
+ // Revoke a code by its hash id (immediate + durable). 404 when the id is unknown.
38
+ revokeWebEntryCode: (identity: string, codeId: string) =>
39
+ req(
40
+ `/api/channels/web/gates/${encodeSegment(identity)}/codes/${encodeSegment(codeId)}`,
41
+ s.webEntryCodeRevoked,
42
+ { method: 'DELETE' },
43
+ ),
44
+ };
45
+ }
package/src/errors.ts CHANGED
@@ -71,6 +71,22 @@ export class ApiLoginFailedError extends Error {
71
71
  }
72
72
  }
73
73
 
74
+ /**
75
+ * A setup attempt was REJECTED — a bad or throttled token (403/429), an
76
+ * already-initialized deployment (409), an unsupported setup door (501), or an
77
+ * invalid body (400/422). Like {@link ApiLoginFailedError} it is NOT an
78
+ * ApiUnauthorizedError: it renders inline on the setup entry, keyed on `status`
79
+ * so the page shows the right copy, never a global unauthorized redirect.
80
+ */
81
+ export class ApiSetupFailedError extends Error {
82
+ readonly status: number;
83
+ constructor(message: string, status: number) {
84
+ super(message);
85
+ this.name = 'ApiSetupFailedError';
86
+ this.status = status;
87
+ }
88
+ }
89
+
74
90
  /**
75
91
  * The response body did not match its declared zod schema. This is a contract
76
92
  * drift between the Studio and the skeleton — a loud, visible error, never a
package/src/http.ts CHANGED
@@ -92,30 +92,18 @@ function retryAfterSeconds(header: string | null): number | undefined {
92
92
  return Number.isFinite(seconds) && seconds >= 0 ? seconds : undefined;
93
93
  }
94
94
 
95
- export async function apiRequest<S extends z.ZodType>(
96
- config: ApiConfig,
95
+ /**
96
+ * Read the JSON body of a request response, applying the transport's error
97
+ * policy: a non-2xx status throws (a 409 as `ApiConflictError` carrying the
98
+ * parsed body, every other as `ApiError` with the server message/code and the
99
+ * named `Retry-After` delay), and a body that is not JSON throws `ApiSchemaError`
100
+ * on a 2xx or `ApiError` otherwise. Returns the parsed payload only for a 2xx.
101
+ */
102
+ async function readJsonPayload(
103
+ response: Response,
97
104
  path: string,
98
- schema: S,
99
- options: RequestOptions = {},
100
- ): Promise<z.infer<S>> {
101
- const doFetch = config.fetch ?? globalThis.fetch;
102
- const token = config.getToken();
103
- const headers: Record<string, string> = { ...JSON_HEADERS };
104
- if (token) headers['x-api-key'] = token;
105
-
106
- const response = await doFetch(buildUrl(config.baseUrl ?? '', path, options.query), {
107
- method: options.method ?? 'GET',
108
- headers,
109
- body: options.body === undefined ? undefined : JSON.stringify(options.body),
110
- signal: options.signal,
111
- });
112
-
113
- if (response.status === 401) throw new ApiUnauthorizedError();
114
-
115
- // `Retry-After` rides on the error so a retrying caller waits the delay the server
116
- // named (the gated routes' reloading 503 carries one) instead of guessing.
117
- const retryAfter = retryAfterSeconds(response.headers.get('Retry-After'));
118
-
105
+ retryAfter: number | undefined,
106
+ ): Promise<unknown> {
119
107
  let payload: unknown;
120
108
  try {
121
109
  payload = await response.json();
@@ -138,15 +126,51 @@ export async function apiRequest<S extends z.ZodType>(
138
126
  throw new ApiError(text, response.status, code, retryAfter, payload);
139
127
  }
140
128
 
129
+ return payload;
130
+ }
131
+
132
+ /** Unwrap the `{ data }` envelope and validate it against `schema`; a missing
133
+ * envelope or a validation failure throws `ApiSchemaError`. */
134
+ function validateEnvelope<S extends z.ZodType>(
135
+ schema: S,
136
+ path: string,
137
+ payload: unknown,
138
+ ): z.infer<S> {
141
139
  if (!isDataEnvelope(payload)) {
142
140
  throw new ApiSchemaError(path, 'response was not a { data } envelope');
143
141
  }
144
-
145
142
  const parsed = schema.safeParse(payload.data);
146
143
  if (!parsed.success) throw new ApiSchemaError(path, parsed.error.issues);
147
144
  return parsed.data;
148
145
  }
149
146
 
147
+ export async function apiRequest<S extends z.ZodType>(
148
+ config: ApiConfig,
149
+ path: string,
150
+ schema: S,
151
+ options: RequestOptions = {},
152
+ ): Promise<z.infer<S>> {
153
+ const doFetch = config.fetch ?? globalThis.fetch;
154
+ const token = config.getToken();
155
+ const headers: Record<string, string> = { ...JSON_HEADERS };
156
+ if (token) headers['x-api-key'] = token;
157
+
158
+ const response = await doFetch(buildUrl(config.baseUrl ?? '', path, options.query), {
159
+ method: options.method ?? 'GET',
160
+ headers,
161
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
162
+ signal: options.signal,
163
+ });
164
+
165
+ if (response.status === 401) throw new ApiUnauthorizedError();
166
+
167
+ // `Retry-After` rides on the error so a retrying caller waits the delay the server
168
+ // named (the gated routes' reloading 503 carries one) instead of guessing.
169
+ const retryAfter = retryAfterSeconds(response.headers.get('Retry-After'));
170
+ const payload = await readJsonPayload(response, path, retryAfter);
171
+ return validateEnvelope(schema, path, payload);
172
+ }
173
+
150
174
  /**
151
175
  * Fetch a plain-text endpoint (the skeleton's `/health` → `"OK"`). No `{ data }`
152
176
  * envelope, no zod — the body is returned verbatim. Non-2xx still throws loudly.