cartethyia 2.7.8 → 2.8.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 (519) hide show
  1. package/.env.example +3 -3
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  46. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
  48. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
  50. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  51. package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
  52. package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
  53. package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
  54. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  55. package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
  56. package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
  57. package/dist/dashboard/assets/brand/favicon.webp +0 -0
  58. package/dist/dashboard/assets/brand/login_register.webp +0 -0
  59. package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
  60. package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
  61. package/dist/dashboard/assets/brand/og_image.webp +0 -0
  62. package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
  63. package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
  64. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
  70. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
  72. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  74. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  75. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
  76. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
  78. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
  80. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
  82. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
  84. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
  86. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
  88. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
  90. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
  92. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
  94. package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
  95. package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
  96. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
  98. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
  100. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  101. package/dist/dashboard/assets/providers/devin.svg +9 -0
  102. package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
  103. package/dist/dashboard/assets/providers/windsurf.svg +10 -0
  104. package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
  105. package/dist/dashboard/assets/providers/zro.svg +3 -0
  106. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  107. package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  108. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  109. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  110. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
  112. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
  114. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
  116. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  117. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
  118. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  119. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
  120. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  121. package/dist/dashboard/assets/story/25817331.webp +0 -0
  122. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  123. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  124. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  125. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  126. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  127. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  128. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  129. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
  130. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  131. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
  132. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  133. package/dist/dashboard/console-backgrounds.webp +0 -0
  134. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  135. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  136. package/dist/dashboard/index.html +3 -3
  137. package/dist/dashboard/index.html.gz +0 -0
  138. package/dist/dashboard/precompressed-assets.json +59 -56
  139. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  140. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  141. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  142. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  143. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  144. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  145. package/package.json +1 -1
  146. package/src/boot/app.ts +10 -13
  147. package/src/boot/dependencies.ts +12 -4
  148. package/src/boot/flush.ts +20 -0
  149. package/src/boot/lifecycle.ts +7 -5
  150. package/src/config/cooldowns.ts +1 -1
  151. package/src/config/routing.ts +26 -0
  152. package/src/config/spec-metadata.ts +72 -0
  153. package/src/config/spec.ts +20 -6
  154. package/src/console/api-keys/contracts.ts +12 -1
  155. package/src/console/api-keys/routes.ts +50 -31
  156. package/src/console/auth/account-reset.ts +7 -9
  157. package/src/console/backup/contracts.ts +4 -0
  158. package/src/console/backup/export-endpoint.ts +7 -5
  159. package/src/console/cli-tools/service.ts +6 -12
  160. package/src/console/console-router-guards.ts +23 -16
  161. package/src/console/console-router.ts +7 -0
  162. package/src/console/dashboard-assets.ts +5 -5
  163. package/src/console/domain-registration.ts +27 -2
  164. package/src/console/inferhub/normalize.ts +24 -28
  165. package/src/console/inferhub/types.ts +3 -0
  166. package/src/console/ip-bans/routes.ts +93 -0
  167. package/src/console/observability/store.ts +0 -53
  168. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  169. package/src/console/providers/catalog/contracts.ts +123 -0
  170. package/src/console/providers/catalog/discover-constants.ts +6 -0
  171. package/src/console/providers/catalog/model-operations.ts +122 -10
  172. package/src/console/providers/catalog/provider-operations.ts +116 -8
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/routes.ts +65 -4
  175. package/src/console/providers/catalog/store-helpers.ts +30 -1
  176. package/src/console/providers/catalog/store.ts +249 -4
  177. package/src/console/quota/account-quota-global.ts +97 -19
  178. package/src/console/quota/account-quota-shared.ts +49 -0
  179. package/src/console/quota/account-quota-tenant.ts +6 -18
  180. package/src/console/quota/native-identity.ts +2 -0
  181. package/src/console/quota/quota-view.ts +19 -3
  182. package/src/console/routing/pools/operations.ts +31 -46
  183. package/src/console/settings/contracts.ts +18 -0
  184. package/src/console/settings/store.ts +32 -0
  185. package/src/console/shared/errors.ts +33 -6
  186. package/src/console/system-prompt/contracts.ts +0 -1
  187. package/src/main.ts +3 -3
  188. package/src/network/pool/byte-accounting.ts +3 -2
  189. package/src/network/pool/selector.ts +182 -39
  190. package/src/network/pool-cooldown-store.ts +89 -14
  191. package/src/network/pool-health.ts +49 -61
  192. package/src/network/rate-limit-verdict.ts +1 -1
  193. package/src/network/ssrf.ts +6 -6
  194. package/src/observability/event-log.ts +7 -361
  195. package/src/observability/event-query.ts +18 -4
  196. package/src/observability/performance-metrics.ts +9 -0
  197. package/src/observability/runtime-metrics.ts +14 -9
  198. package/src/observability/telemetry-buffer.ts +15 -25
  199. package/src/observability/telemetry-reconciliation.ts +9 -37
  200. package/src/observability/telemetry-row.ts +125 -0
  201. package/src/persistence/db-mode.ts +4 -1
  202. package/src/persistence/dialect.ts +30 -0
  203. package/src/persistence/migrate.ts +6 -3
  204. package/src/persistence/schema.ts +28 -2
  205. package/src/persistence/ttl-cache.ts +20 -2
  206. package/src/protocol/codecs.ts +1 -1
  207. package/src/protocol/dsml.ts +35 -87
  208. package/src/protocol/gateway-error.ts +1 -0
  209. package/src/protocol/markup-stream-parser.ts +195 -0
  210. package/src/protocol/reasoning-text.ts +19 -0
  211. package/src/protocol/reasoning.ts +1 -1
  212. package/src/protocol/request/chat.ts +14 -14
  213. package/src/protocol/request/gemini.ts +8 -2
  214. package/src/protocol/request/messages.ts +4 -5
  215. package/src/protocol/response/chat.ts +12 -20
  216. package/src/protocol/response/gemini.ts +6 -1
  217. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  218. package/src/protocol/response/responses.ts +18 -2
  219. package/src/protocol/sse-decoder.ts +25 -8
  220. package/src/protocol/stop-reason.ts +23 -1
  221. package/src/protocol/stream-error-frames.ts +13 -3
  222. package/src/protocol/think-split.ts +112 -0
  223. package/src/protocol/translation/dialect/qwen.ts +26 -58
  224. package/src/protocol/translation/quirks.ts +1 -1
  225. package/src/protocol/translation/tool-repair.ts +2 -2
  226. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  227. package/src/protocol/upstream-parsing.ts +1 -1
  228. package/src/providers/adapters/compatible-adapter.ts +78 -17
  229. package/src/providers/authentication/oauth-client.ts +1 -1
  230. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  231. package/src/providers/authentication/oauth-refresh-service.ts +153 -59
  232. package/src/providers/configured-provider.ts +148 -0
  233. package/src/providers/default-registry.ts +117 -27
  234. package/src/providers/discovery/discovery-types.ts +12 -0
  235. package/src/providers/discovery/probe-phases.ts +74 -33
  236. package/src/providers/discovery/probing-service.ts +293 -165
  237. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  238. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  239. package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
  240. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  241. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  242. package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
  243. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  244. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  245. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  246. package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
  247. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  248. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  249. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  250. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  251. package/src/providers/integrations/buddy/capabilities.ts +6 -43
  252. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  253. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
  254. package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
  255. package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
  256. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  257. package/src/providers/integrations/cline/cline.ts +2 -2
  258. package/src/providers/integrations/commandcode/quota.ts +22 -28
  259. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  260. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  261. package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
  262. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  263. package/src/providers/integrations/cursor/cursor-request.ts +35 -20
  264. package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
  265. package/src/providers/integrations/cursor/cursor.ts +139 -70
  266. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  267. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  268. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  269. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  270. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  271. package/src/providers/integrations/devin/devin-http.ts +91 -0
  272. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  273. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  274. package/src/providers/integrations/devin/devin.ts +539 -0
  275. package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
  276. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  277. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  278. package/src/providers/integrations/factory/factory.ts +2 -2
  279. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  280. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  281. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  282. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  283. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  284. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  285. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  286. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  287. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  288. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  289. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  290. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  291. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  292. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  293. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  294. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  295. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  296. package/src/providers/integrations/freepi/freepi.ts +292 -0
  297. package/src/providers/integrations/gemini.ts +2 -2
  298. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  299. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  300. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  301. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  302. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
  303. package/src/providers/integrations/hermes/index.ts +2 -0
  304. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  305. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  306. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  307. package/src/providers/integrations/kiro/kiro.ts +15 -19
  308. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  309. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  310. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  311. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  312. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  313. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  314. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  315. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  316. package/src/providers/integrations/perch/perch-protocol.ts +208 -0
  317. package/src/providers/integrations/perch/perch-quota.ts +123 -0
  318. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  319. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  320. package/src/providers/integrations/perch/perch.ts +305 -0
  321. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  322. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  323. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  324. package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
  325. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  326. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  327. package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
  328. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  329. package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
  330. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
  331. package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
  332. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  333. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  334. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  335. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  336. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  337. package/src/providers/integrations/qoder/qoder.ts +3 -7
  338. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  339. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  340. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  341. package/src/providers/integrations/trae/trae-protocol.ts +12 -11
  342. package/src/providers/integrations/trae/trae-quota.ts +96 -36
  343. package/src/providers/integrations/trae/trae-realm.ts +74 -15
  344. package/src/providers/integrations/trae/trae.ts +99 -26
  345. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  346. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  347. package/src/providers/integrations/zro/zro.ts +204 -0
  348. package/src/providers/operations/account-error-classification.ts +235 -278
  349. package/src/providers/operations/account-health-service.ts +273 -26
  350. package/src/providers/operations/account-refresh-status.ts +269 -0
  351. package/src/providers/operations/account-tier.ts +66 -0
  352. package/src/providers/operations/client-version-resolver.ts +1 -1
  353. package/src/providers/operations/client-versions.ts +42 -150
  354. package/src/providers/operations/cooldown-rules.ts +56 -0
  355. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  356. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  357. package/src/providers/operations/provider-catalog-service.ts +1 -1
  358. package/src/providers/operations/provider-credential-service.ts +102 -32
  359. package/src/providers/operations/provider-rules/github.ts +48 -0
  360. package/src/providers/operations/provider-rules/grok.ts +33 -0
  361. package/src/providers/operations/provider-rules/inception.ts +49 -0
  362. package/src/providers/operations/provider-rules/index.ts +32 -0
  363. package/src/providers/operations/provider-rules/perch.ts +37 -0
  364. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  365. package/src/providers/operations/provider-rules/trae.ts +81 -0
  366. package/src/providers/provider-contracts.ts +33 -2
  367. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  368. package/src/providers/provider-metadata.ts +99 -14
  369. package/src/providers/provider-model-definition.ts +36 -0
  370. package/src/providers/provider-probe.ts +15 -1
  371. package/src/providers/provider-registry.ts +42 -33
  372. package/src/providers/quota/quota-limit-windows.ts +16 -5
  373. package/src/providers/quota/quota-support.ts +1 -1
  374. package/src/providers/quota/quota-window-parser.ts +90 -18
  375. package/src/providers/quota/refresh.ts +96 -18
  376. package/src/security/abuse.ts +71 -0
  377. package/src/security/api-key-auth-cache.ts +1 -1
  378. package/src/security/api-key-auth.ts +6 -2
  379. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  380. package/src/transport/dispatch/attempt-loop.ts +71 -17
  381. package/src/transport/dispatch/generation.ts +2 -8
  382. package/src/transport/dispatch/native-shared.ts +25 -0
  383. package/src/transport/dispatch/proxy-request.ts +9 -46
  384. package/src/transport/dispatch/responses-compact.ts +2 -8
  385. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  386. package/src/transport/dispatch/streaming/stream.ts +9 -36
  387. package/src/transport/dispatch/systemone.ts +2 -8
  388. package/src/transport/dispatch/token-count.ts +2 -6
  389. package/src/transport/dispatch/upstream.ts +67 -1
  390. package/src/transport/drain-endpoint.ts +31 -4
  391. package/src/transport/failure-policy.ts +129 -37
  392. package/src/transport/guards/body-policy.ts +7 -1
  393. package/src/transport/guards/error-lifecycle.ts +13 -1
  394. package/src/transport/guards/request-context.ts +22 -14
  395. package/src/transport/request/preparer.ts +81 -71
  396. package/src/transport/routing/bounded-lru.ts +14 -0
  397. package/src/transport/routing/drain-reserve.ts +129 -0
  398. package/src/transport/routing/plan-gate.ts +7 -1
  399. package/src/transport/routing/round-robin.ts +60 -0
  400. package/src/transport/routing/route-catalog.ts +123 -111
  401. package/src/transport/routing/route-model.ts +51 -9
  402. package/src/transport/routing/router.ts +418 -33
  403. package/src/transport/surface/adapters.ts +11 -2
  404. package/src/transport/surface/chat/adapter.ts +2 -2
  405. package/src/transport/surface/chat/encode.ts +47 -4
  406. package/src/transport/surface/chat/parse.ts +2 -12
  407. package/src/transport/surface/completion.ts +44 -33
  408. package/src/transport/surface/content-parts.ts +21 -0
  409. package/src/transport/surface/messages/adapter.ts +9 -2
  410. package/src/transport/surface/messages/encode.ts +11 -3
  411. package/src/transport/surface/messages/parse.ts +2 -1
  412. package/src/transport/surface/messages/stream.ts +33 -17
  413. package/src/transport/surface/responses/encode.ts +34 -6
  414. package/src/transport/surface/responses/parse.ts +3 -11
  415. package/src/workers/daily-checkin.ts +1 -1
  416. package/src/workers/quota-refresh-sweep.ts +1 -1
  417. package/src/workers/scheduler.ts +22 -4
  418. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  419. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  420. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  421. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  422. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  423. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  424. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  425. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  426. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  427. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  428. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  429. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  430. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  431. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  432. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  433. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  434. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  435. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  436. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  437. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  438. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  439. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  440. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  441. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  442. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
  443. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  444. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  445. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  446. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  447. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  448. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  449. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  450. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  451. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  452. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  453. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  454. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  455. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  456. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  457. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  458. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  459. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  460. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  461. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  462. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  463. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  464. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  465. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  466. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  467. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  468. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  469. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  470. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  471. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  472. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  473. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  474. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  475. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  476. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  477. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  478. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  479. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  480. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  481. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  482. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  483. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  484. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  485. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  486. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  487. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  488. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  489. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  490. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  491. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  492. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  493. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  494. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  495. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  496. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  497. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  498. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  499. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  500. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  501. package/dist/dashboard/console-backgrounds.jpg +0 -0
  502. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  503. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  504. package/src/providers/bundled-discovery-providers.ts +0 -98
  505. package/src/providers/configured-openai-providers.ts +0 -120
  506. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  507. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  508. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  509. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  510. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  511. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  512. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  513. package/src/providers/operations/model-discovery-cache.ts +0 -34
  514. package/src/providers/operations/probe-fetch.ts +0 -15
  515. package/src/providers/operations/provider-version-cache.ts +0 -30
  516. package/src/providers/provider-capabilities.ts +0 -33
  517. package/src/providers/provider-credential-envelope.ts +0 -43
  518. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
  519. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -62,6 +62,72 @@ export const ACCOUNT_STATUSES = ["active", "disabled"] as const;
62
62
  /** One operator-settable account status. */
63
63
  export type AccountStatus = (typeof ACCOUNT_STATUSES)[number];
64
64
 
65
+ /** Display-only warning type shared with the dashboard contract mirror. */
66
+ export type { ModelWarning, ModelWarningSeverity } from "../../../providers/provider-model-definition";
67
+ import type { ModelWarning } from "../../../providers/provider-model-definition";
68
+
69
+ /**
70
+ * Outcome of one provider-scoped bulk account action (area 02).
71
+ *
72
+ * A tenant-scoped action never touches shared (`tenant_id IS NULL`) rows; the
73
+ * global-admin pair of routes does. `globalSkipped` counts those rows on the
74
+ * tenant path so the console can say why its visible list and the DB disagree.
75
+ * `deleted`/`missing` are present on delete; `updated` on disable. Both actions
76
+ * are idempotent: a second call finds no matching row and reports zero.
77
+ */
78
+ export interface ProviderAccountBulkActionResult {
79
+ readonly ok: boolean;
80
+ readonly updated: number;
81
+ readonly deleted?: readonly string[];
82
+ readonly missing?: readonly string[];
83
+ readonly globalSkipped: number;
84
+ }
85
+
86
+ /** Cap on one discovery-commit request (D29). Kept pure (no `node:*`) so the dashboard can import it. */
87
+ export { DISCOVER_MODEL_COMMIT_MAX } from "./discover-constants";
88
+
89
+ /**
90
+ * One model the provider's own listing advertised, projected for the Add-Model
91
+ * picker. Read-only: `GET .../models/discover` writes nothing. `models`
92
+ * metadata here is the fetched `ModelDefinition`, not the static catalog's
93
+ * guess, so the operator can see exactly what the commit will store.
94
+ */
95
+ export interface DiscoverModelCandidate {
96
+ readonly modelId: string;
97
+ readonly wireFamily: string;
98
+ readonly serviceKind: string;
99
+ readonly endpointPath: string;
100
+ readonly contextLimit: number | null;
101
+ readonly outputLimit: number | null;
102
+ readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
103
+ readonly reasoning: boolean;
104
+ readonly toolCall: boolean;
105
+ readonly freeTier?: boolean;
106
+ readonly proOnly?: boolean;
107
+ readonly alreadyRegistered: boolean;
108
+ }
109
+
110
+ /** Result of `GET .../models/discover`. */
111
+ export interface DiscoverModelCandidatesResponse {
112
+ readonly supported: boolean;
113
+ readonly candidates: readonly DiscoverModelCandidate[];
114
+ /** Label of the usable account whose credential fetched the listing (D27). */
115
+ readonly accountLabel?: string;
116
+ }
117
+
118
+ /** Body of `POST .../models/discover/commit`. */
119
+ export interface DiscoverModelCommitRequest {
120
+ readonly modelIds: readonly string[];
121
+ }
122
+
123
+ /** Outcome of a discovery commit: the ids persisted and the ones skipped. */
124
+ export interface DiscoverModelCommitResult {
125
+ readonly ok: boolean;
126
+ readonly committed: readonly string[];
127
+ readonly skipped: readonly string[];
128
+ readonly synced: number;
129
+ }
130
+
65
131
  const VALID_WIRE_FAMILIES: Record<WireFamily, true> = {
66
132
  chat: true,
67
133
  responses: true,
@@ -121,6 +187,16 @@ const ADAPTER_OWNED: Record<string, true> = {
121
187
  };
122
188
 
123
189
  export function validateCompatibilityProfile(profile: CompatibilityProfile): void {
190
+ if (profile.service_kinds !== undefined) {
191
+ if (!Array.isArray(profile.service_kinds)) {
192
+ throw new GatewayError("invalid_request", 400, "service_kinds must be an array (request {requestId}). Check the shape of what you sent — then resend 🪞");
193
+ }
194
+ for (const kind of profile.service_kinds) {
195
+ if (!isServiceKind(kind)) {
196
+ throw new GatewayError("invalid_request", 400, `unknown service kind ${String(kind)} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
197
+ }
198
+ }
199
+ }
124
200
  if (profile.cli_identity !== undefined) {
125
201
  if (typeof profile.cli_identity !== "boolean") {
126
202
  throw new GatewayError("invalid_request", 400, "cli_identity must be boolean (request {requestId}). Check the shape of what you sent — then resend 🪞");
@@ -358,6 +434,8 @@ export interface ModelCatalogEntry {
358
434
  * provider's own model directory states it.
359
435
  */
360
436
  freeTier?: boolean;
437
+ /** Display-only warnings; `null` in storage means none. */
438
+ warnings?: readonly ModelWarning[];
361
439
  }
362
440
  export interface FlatModelCatalogEntry {
363
441
  readonly providerId: string;
@@ -407,6 +485,11 @@ export interface ModelMetadataOverride {
407
485
  readonly image?: boolean;
408
486
  readonly reasoning?: boolean;
409
487
  readonly toolCall?: boolean;
488
+ /**
489
+ * Display-only warnings, editable only on non-builtin rows. `null` clears
490
+ * the stored value; absence leaves it alone.
491
+ */
492
+ readonly warnings?: readonly ModelWarning[] | null;
410
493
  }
411
494
 
412
495
  /** Register payload: model ids plus optional operator metadata. */
@@ -484,6 +567,27 @@ export interface UpdateProviderAccountRequest {
484
567
  */
485
568
  staticToken?: boolean;
486
569
  }
570
+ /**
571
+ * Response of `POST /providers/:providerId/accounts/:accountId/refresh-token`
572
+ * (area 04). The refresh never changes `status`: `statusAfter` is re-read from
573
+ * the database after the attempt and is never `"active"` by fiat — a disabled
574
+ * account stays disabled, only an operator can re-enable it.
575
+ */
576
+ export interface RefreshAccountTokenResponse {
577
+ readonly ok: boolean;
578
+ readonly statusAfter: string;
579
+ /** True when an upstream token refresh actually minted a fresh token. */
580
+ readonly tokenRefreshed?: boolean;
581
+ /** ISO timestamp of the fresh access-token expiry, when known. */
582
+ readonly expiresAt?: string;
583
+ /** ISO timestamp of the refresh attempt. */
584
+ readonly refreshedAt?: string;
585
+ /** Structured reason when `ok` is false (`not_oauth`, `static_token`, …). */
586
+ readonly error?: string;
587
+ /** Human detail (failure message) when available. */
588
+ readonly category?: string;
589
+ }
590
+
487
591
  export interface ProviderAccountTokenUsage {
488
592
  readonly requests: number;
489
593
  readonly errors: number;
@@ -786,6 +890,7 @@ export interface ProviderCatalogStore {
786
890
  probeAllModels(
787
891
  tenantId: string,
788
892
  providerId: string,
893
+ mode?: ProbeModelRequest["mode"],
789
894
  ): Promise<ProbeAllModelsResult>;
790
895
  probeAllAccounts(
791
896
  tenantId: string,
@@ -817,6 +922,20 @@ export interface ProviderCatalogStore {
817
922
  request: UpdateModelMetadataRequest,
818
923
  ): Promise<boolean>;
819
924
  syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
925
+ /**
926
+ * Read-only `/models` discovery. Never persists. `supported: false` names a
927
+ * provider with no listing endpoint so the UI can fall back to manual add.
928
+ */
929
+ listDiscoveryCandidates(
930
+ tenantId: string,
931
+ providerId: string,
932
+ ): Promise<{ readonly supported: boolean; readonly candidates: readonly DiscoverModelCandidate[]; readonly accountLabel?: string }>;
933
+ /** Persists a verified subset of a fresh discovery as `source="manual"`. */
934
+ commitDiscoveredModels(
935
+ tenantId: string,
936
+ providerId: string,
937
+ modelIds: readonly string[],
938
+ ): Promise<{ readonly committed: readonly string[]; readonly skipped: readonly string[]; readonly synced: number }>;
820
939
  listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
821
940
  listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
822
941
  /**
@@ -891,6 +1010,10 @@ export interface ProviderCatalogStore {
891
1010
  dayKey: string,
892
1011
  ): Promise<readonly BuddyGrowthTaskLedgerEntry[]>;
893
1012
  recoverAccount(tenantId: string, providerId: string, accountId: string): Promise<boolean>;
1013
+ /** One UPDATE over this tenant's cooling accounts (D07/D08); evidence kept. */
1014
+ disableCooldowns(tenantId: string, providerId: string): Promise<ProviderAccountBulkActionResult>;
1015
+ /** One DELETE over this tenant's banned accounts (D12); idempotent. */
1016
+ deleteBanned(tenantId: string, providerId: string): Promise<ProviderAccountBulkActionResult>;
894
1017
  }
895
1018
 
896
1019
  /**
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pure discovery constants, kept free of any `node:*` imports so the
3
+ * dashboard can import them without dragging Node crypto into the browser
4
+ * bundle — the same reason `ban-constants.ts` and `usage-dimensions.ts` exist.
5
+ */
6
+ export const DISCOVER_MODEL_COMMIT_MAX = 100;
@@ -11,7 +11,17 @@
11
11
  import type { AccessDecision } from "../../../security/access-control";
12
12
  import { ConsoleDomainError, requireScope, requireTenantAnyScope } from "../../shared/errors";
13
13
  import { GatewayError } from "../../../protocol/gateway-error";
14
- import { MODEL_READ_SCOPES, MODEL_WRITE_SCOPES } from "./contracts";
14
+ import {
15
+ DISCOVER_MODEL_COMMIT_MAX,
16
+ MODEL_READ_SCOPES,
17
+ MODEL_WRITE_SCOPES,
18
+ } from "./contracts";
19
+ import type { DiscoverModelCandidatesResponse, DiscoverModelCommitResult } from "./contracts";
20
+ import {
21
+ MODEL_WARNING_KIND_MAX_LENGTH,
22
+ MODEL_WARNING_MESSAGE_MAX_LENGTH,
23
+ MODEL_WARNING_SEVERITIES,
24
+ } from "../../../providers/provider-model-definition";
15
25
  import type {
16
26
  ByokConnectionTestRequest,
17
27
  ByokConnectionTestResult,
@@ -47,6 +57,60 @@ function validateModelMetadata(metadata: ModelMetadataOverride | undefined): voi
47
57
  `${field} must be a positive integer, or null for unknown`,
48
58
  );
49
59
  }
60
+ // `null` clears the stored blob; an absent field leaves it alone. Only a
61
+ // non-builtin row can actually carry an edit — the store enforces that
62
+ // invariant. Bounds here stop a hostile payload from persisting a multi-MB
63
+ // display banner; severity is a closed whitelist.
64
+ if (metadata.warnings === null || metadata.warnings === undefined) return;
65
+ if (!Array.isArray(metadata.warnings)) {
66
+ throw new ConsoleDomainError("invalid_request", 400, "warnings must be an array or null");
67
+ }
68
+ for (const [index, warning] of metadata.warnings.entries()) {
69
+ if (warning === null || typeof warning !== "object" || Array.isArray(warning)) {
70
+ throw new ConsoleDomainError("invalid_request", 400, `warnings[${index}] must be an object`);
71
+ }
72
+ const kind = (warning as { kind?: unknown }).kind;
73
+ const message = (warning as { message?: unknown }).message;
74
+ const severity = (warning as { severity?: unknown }).severity;
75
+ if (typeof kind !== "string" || kind.trim().length === 0 || kind.length > MODEL_WARNING_KIND_MAX_LENGTH) {
76
+ throw new ConsoleDomainError(
77
+ "invalid_request",
78
+ 400,
79
+ `warnings[${index}].kind must be a non-empty string up to ${MODEL_WARNING_KIND_MAX_LENGTH} chars`,
80
+ );
81
+ }
82
+ if (typeof message !== "string" || message.trim().length === 0 || message.length > MODEL_WARNING_MESSAGE_MAX_LENGTH) {
83
+ throw new ConsoleDomainError(
84
+ "invalid_request",
85
+ 400,
86
+ `warnings[${index}].message must be a non-empty string up to ${MODEL_WARNING_MESSAGE_MAX_LENGTH} chars`,
87
+ );
88
+ }
89
+ if (
90
+ severity !== undefined &&
91
+ (typeof severity !== "string" ||
92
+ !(MODEL_WARNING_SEVERITIES as readonly string[]).includes(severity))
93
+ ) {
94
+ throw new ConsoleDomainError(
95
+ "invalid_request",
96
+ 400,
97
+ `warnings[${index}].severity must be one of ${MODEL_WARNING_SEVERITIES.join(", ")}`,
98
+ );
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Discovery lives in the provider layer and raises `GatewayError`; the console
105
+ * speaks `ConsoleDomainError`. Keeping the exact code/status/message lets the
106
+ * dashboard toast name the real upstream reason rather than a generic failure.
107
+ */
108
+ function translateDiscoveryError(error: unknown): unknown {
109
+ if (error instanceof ConsoleDomainError) return error;
110
+ if (error instanceof GatewayError) {
111
+ return new ConsoleDomainError(error.code, error.status, error.message);
112
+ }
113
+ return error;
50
114
  }
51
115
 
52
116
  export function createModelCatalogOperations(config: ProviderCatalogConfig) {
@@ -164,14 +228,7 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
164
228
  await config.snapshotInvalidator?.invalidate();
165
229
  return result;
166
230
  } catch (error) {
167
- // Discovery runs in the provider layer and raises `GatewayError`; the
168
- // console speaks `ConsoleDomainError`. Translating here keeps the
169
- // upstream-vs-operator distinction and the status code intact, so the
170
- // dashboard toast names the real reason instead of a generic failure.
171
- if (error instanceof GatewayError) {
172
- throw new ConsoleDomainError(error.code, error.status, error.message);
173
- }
174
- throw error;
231
+ throw translateDiscoveryError(error);
175
232
  }
176
233
  }
177
234
  /** Manually registers additional model ids on an existing provider (tenant-bound, no upstream call). */,
@@ -247,9 +304,10 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
247
304
  async probeAllModels(
248
305
  access: AccessDecision | undefined,
249
306
  providerId: string,
307
+ mode?: ProbeModelRequest["mode"],
250
308
  ): Promise<ProbeAllModelsResult> {
251
309
  const a = requireTenantAnyScope(access, MODEL_WRITE_SCOPES);
252
- return config.store.probeAllModels(a.tenantId, providerId);
310
+ return config.store.probeAllModels(a.tenantId, providerId, mode);
253
311
  },
254
312
  async probeAllAccounts(
255
313
  access: AccessDecision | undefined,
@@ -299,6 +357,60 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
299
357
  await config.snapshotInvalidator?.invalidate();
300
358
  return { success: true };
301
359
  },
360
+ /**
361
+ * `GET .../models/discover`: fetch the provider's own listing without any
362
+ * write (D30). A provider with no listing endpoint answers `supported:false`
363
+ * so the UI can fall back to manual add instead of showing an error.
364
+ */
365
+ async listDiscoveryCandidates(
366
+ access: AccessDecision | undefined,
367
+ providerId: string,
368
+ ): Promise<DiscoverModelCandidatesResponse> {
369
+ const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
370
+ try {
371
+ return await config.store.listDiscoveryCandidates(a.tenantId, providerId);
372
+ } catch (error) {
373
+ throw translateDiscoveryError(error);
374
+ }
375
+ },
376
+ /**
377
+ * `POST .../models/discover/commit`: the store re-fetches the roster and
378
+ * rejects ids outside it (D31), so no hand-forged id can be persisted.
379
+ */
380
+ async commitDiscoveredModels(
381
+ access: AccessDecision | undefined,
382
+ providerId: string,
383
+ modelIds: readonly string[],
384
+ ): Promise<DiscoverModelCommitResult> {
385
+ const a = requireTenantAnyScope(access, MODEL_WRITE_SCOPES);
386
+ if (modelIds.some((id) => typeof id !== "string" || id.trim().length === 0)) {
387
+ throw new ConsoleDomainError(
388
+ "invalid_request",
389
+ 400,
390
+ "modelIds must be non-empty model ids",
391
+ );
392
+ }
393
+ if (modelIds.length === 0 || modelIds.length > DISCOVER_MODEL_COMMIT_MAX) {
394
+ throw new ConsoleDomainError(
395
+ "invalid_request",
396
+ 400,
397
+ `modelIds must contain 1-${DISCOVER_MODEL_COMMIT_MAX} entries`,
398
+ );
399
+ }
400
+ try {
401
+ const result = await config.store.commitDiscoveredModels(a.tenantId, providerId, modelIds);
402
+ await config.auditSink?.record({
403
+ access: a,
404
+ action: "provider.models.registered",
405
+ target: providerId,
406
+ detail: { modelIds: result.committed, source: "discover-commit" },
407
+ });
408
+ await config.snapshotInvalidator?.invalidate();
409
+ return { ok: true, ...result };
410
+ } catch (error) {
411
+ throw translateDiscoveryError(error);
412
+ }
413
+ },
302
414
  async deleteModelsBulk(
303
415
  access: AccessDecision | undefined,
304
416
  providerId: string,
@@ -15,6 +15,7 @@ import {
15
15
  ConsoleDomainError,
16
16
  requireGlobalAdmin,
17
17
  requireTenantAnyScope,
18
+ requireTenantScope,
18
19
  } from "../../shared/errors";
19
20
  import { cachedReadWithEtag, conditionalResponse } from "../../shared/read-through-cache";
20
21
  import type { RedisClient } from "../../../persistence/redis";
@@ -24,10 +25,11 @@ import { isBundledProviderId } from "../../../providers/provider-contracts";
24
25
  import type { ProviderRegistry } from "../../../providers/provider-registry";
25
26
  import { getProviderClientVersion, getRouteIdentityVersions } from "../../../providers/operations/client-versions";
26
27
  import type { OAuthLoginField } from "../../../providers/authentication/oauth-flow-store";
27
- import { providerCredentialHint, providerCredentialUrl, providerDisplayName, providerHasAdapterUserAgent, providerOperatorNotice, providerServiceKinds, type CompatibilityProfile } from "../../../providers/provider-metadata";
28
+ import { providerCredentialHint, providerCredentialUrl, providerDisplayName, providerHasAdapterUserAgent, providerOperatorNotice, providerServiceKinds, customProviderServiceKinds, type CompatibilityProfile } from "../../../providers/provider-metadata";
28
29
  import type { AccountHealthEventRecord } from "../../../providers/operations/account-health-service";
29
30
  import { healthEventBuffer } from "../../../persistence/health-event-flush";
30
- import { CREDENTIAL_MODES, PROVIDER_READ_SCOPES, PROVIDER_WRITE_SCOPES, type BuddyGrowthTaskLedgerEntry } from "./contracts";
31
+ import { CREDENTIAL_MODES, PROVIDER_READ_SCOPES, PROVIDER_WRITE_SCOPES, type BuddyGrowthTaskLedgerEntry, type ProviderAccountBulkActionResult, type RefreshAccountTokenResponse } from "./contracts";
32
+ import type { RefreshAccountStatusOutcome } from "../../../providers/operations/account-refresh-status";
31
33
  import { enqueueCheckin } from "../../../workers/daily-checkin";
32
34
  import {
33
35
  isWireFamily,
@@ -87,12 +89,16 @@ export function sanitizeProviderResponse(
87
89
  const credentialHint = providerCredentialHint(p.providerId);
88
90
  const operatorNotice = providerOperatorNotice(p.providerId);
89
91
  const clientVersion = getProviderClientVersion(p.providerId);
92
+ const compatibilityProfile = p.compatibilityProfile as CompatibilityProfile | undefined;
93
+ const isBuiltIn = (p.isBuiltIn as boolean | undefined) ?? false;
90
94
  const response: ProviderResponse = {
91
95
  providerId: p.providerId,
92
- serviceKinds: providerServiceKinds(p.providerId),
96
+ serviceKinds: isBuiltIn
97
+ ? providerServiceKinds(p.providerId)
98
+ : customProviderServiceKinds(compatibilityProfile),
93
99
  displayName: providerDisplayName(p.providerId),
94
100
  enabled: (p.enabled as boolean | undefined) ?? true,
95
- isBuiltIn: (p.isBuiltIn as boolean | undefined) ?? false,
101
+ isBuiltIn,
96
102
  requiresAccount: (p.requiresAccount as boolean | undefined) ?? true,
97
103
  hasAdapterUserAgent: providerHasAdapterUserAgent(p.providerId),
98
104
  // No model advertises search; see the note in provider-metadata.ts.
@@ -102,7 +108,7 @@ export function sanitizeProviderResponse(
102
108
  ...(operatorNotice === undefined ? {} : { operatorNotice }),
103
109
  ...(clientVersion === undefined ? {} : { clientVersion }),
104
110
  routeIdentityVersions: getRouteIdentityVersions(),
105
- supportsModelDiscovery: (p.supportsModelDiscovery as boolean | undefined) ?? !p.isBuiltIn,
111
+ supportsModelDiscovery: (p.supportsModelDiscovery as boolean | undefined) ?? !isBuiltIn,
106
112
  ...(typeof p.createdAt === "string" ? { createdAt: p.createdAt } : {}),
107
113
  ...(typeof p.updatedAt === "string" ? { updatedAt: p.updatedAt } : {}),
108
114
  };
@@ -182,6 +188,15 @@ export interface ProviderCatalogConfig {
182
188
  providerId: string,
183
189
  accountId: string,
184
190
  ) => Promise<{ readonly accessToken: string; readonly refreshToken?: string }>;
191
+ /**
192
+ * One-shot manual OAuth token refresh (area 04). Provided by the composition
193
+ * root; when absent the refresh-token route answers 501 rather than silently
194
+ * reporting success.
195
+ */
196
+ readonly refreshAccountStatus?: (
197
+ accountId: string,
198
+ opts?: { readonly origin?: "ui" | "sweep" | "dispatch" },
199
+ ) => Promise<RefreshAccountStatusOutcome>;
185
200
  }
186
201
  async function attachProviderCapabilities(
187
202
  response: ProviderResponse,
@@ -715,6 +730,101 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
715
730
  await config.snapshotInvalidator?.invalidate();
716
731
  return { success: true };
717
732
  },
733
+ /**
734
+ * One UPDATE over the tenant's cooling accounts, with `cooldownUntil` and
735
+ * `modelCooldowns` preserved as evidence (D07/D08). Shared rows are not
736
+ * writable here: the platform-admin pair of routes owns them (D10).
737
+ */
738
+ async disableCooldowns(
739
+ access: AccessDecision | undefined,
740
+ providerId: string,
741
+ ): Promise<ProviderAccountBulkActionResult> {
742
+ const a = requireTenantAnyScope(access, PROVIDER_WRITE_SCOPES);
743
+ const result = await config.store.disableCooldowns(a.tenantId, providerId);
744
+ await config.auditSink?.record({
745
+ access: a,
746
+ action: "provider_account.bulk_disabled_cooldowns",
747
+ target: providerId,
748
+ detail: { providerId, updated: result.updated, globalSkipped: result.globalSkipped },
749
+ });
750
+ await config.snapshotInvalidator?.invalidate();
751
+ return { ok: true, updated: result.updated, globalSkipped: result.globalSkipped };
752
+ },
753
+ /**
754
+ * One DELETE over the tenant's strictly banned accounts (D12). Empty
755
+ * matches are a successful no-op, so a second click is idempotent.
756
+ */
757
+ async deleteBanned(
758
+ access: AccessDecision | undefined,
759
+ providerId: string,
760
+ ): Promise<ProviderAccountBulkActionResult> {
761
+ const a = requireTenantAnyScope(access, PROVIDER_WRITE_SCOPES);
762
+ const result = await config.store.deleteBanned(a.tenantId, providerId);
763
+ await config.auditSink?.record({
764
+ access: a,
765
+ action: "provider_account.bulk_deleted_banned",
766
+ target: providerId,
767
+ detail: {
768
+ providerId,
769
+ deleted: result.deleted ?? [],
770
+ missing: result.missing ?? [],
771
+ globalSkipped: result.globalSkipped,
772
+ },
773
+ });
774
+ await config.snapshotInvalidator?.invalidate();
775
+ return {
776
+ ok: true,
777
+ updated: 0,
778
+ deleted: result.deleted ?? [],
779
+ missing: result.missing ?? [],
780
+ globalSkipped: result.globalSkipped,
781
+ };
782
+ },
783
+ async refreshAccountToken(
784
+ access: AccessDecision | undefined,
785
+ providerId: string,
786
+ accountId: string,
787
+ ): Promise<RefreshAccountTokenResponse> {
788
+ // The refresh changes no routing state and is a per-account operator
789
+ // action, so the browser session's own write scope is sufficient.
790
+ const a = requireTenantScope(access, "dashboard:write");
791
+ // 404 unless the account exists and belongs to this provider/tenant.
792
+ const accounts = await config.store.listAccounts(a.tenantId, providerId);
793
+ const owned = accounts.find((account) => account.id === accountId && account.tenantId === a.tenantId);
794
+ if (!owned)
795
+ throw new ConsoleDomainError("account_not_found", 404, `Account ${accountId} not found`);
796
+ // 409 for a static token (nothing to re-mint), 400 for non-OAuth —
797
+ // before calling the refresher, so the row is never touched on a
798
+ // rejectable request.
799
+ if (owned.staticToken === true)
800
+ throw new ConsoleDomainError("static_token", 409, "Account carries a static token that cannot be refreshed");
801
+ if (owned.credentialKind !== "oauth")
802
+ throw new ConsoleDomainError("not_oauth", 400, "Account is not OAuth; token refresh is not applicable");
803
+ if (!config.refreshAccountStatus)
804
+ throw new ConsoleDomainError("refresh_unavailable", 501, "Token refresh is not configured on this host");
805
+ const outcome = await config.refreshAccountStatus(accountId, { origin: "ui" });
806
+ await config.auditSink?.record({
807
+ access: a,
808
+ action: "provider_account.token_refreshed",
809
+ target: accountId,
810
+ detail: {
811
+ providerId,
812
+ ok: outcome.ok,
813
+ statusAfter: outcome.statusAfter,
814
+ ...(outcome.error === undefined ? {} : { error: outcome.error }),
815
+ },
816
+ });
817
+ await config.snapshotInvalidator?.invalidate();
818
+ return {
819
+ ok: outcome.ok,
820
+ statusAfter: outcome.statusAfter,
821
+ ...(outcome.tokenRefreshed ? { tokenRefreshed: true } : {}),
822
+ ...(outcome.expiresAt === undefined ? {} : { expiresAt: outcome.expiresAt }),
823
+ ...(outcome.refreshedAt === undefined ? {} : { refreshedAt: outcome.refreshedAt }),
824
+ ...(outcome.error === undefined ? {} : { error: outcome.error }),
825
+ ...(outcome.category === undefined ? {} : { category: outcome.category }),
826
+ };
827
+ },
718
828
  /**
719
829
  * Exports the requested accounts with their decrypted credentials.
720
830
  *
@@ -778,9 +888,7 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
778
888
  // channel check (11128) rejects the imported account.
779
889
  const materialized =
780
890
  account.providerId === "cb" ||
781
- account.providerId === "cbcn" ||
782
- account.providerId === "workbuddy" ||
783
- account.providerId === "workbuddycn"
891
+ account.providerId === "cbcn"
784
892
  // dynamic-import: B-02 lazy graph — buddy device-binding materializer, buddy-family only.
785
893
  // The buddy-identity graph is lazy-loaded: the device-binding
786
894
  // materializer only runs for buddy-family accounts (B-02).
@@ -2,9 +2,10 @@ import { consoleErrorHandler } from "../../shared/errors";
2
2
  import { literalUnion } from "../../shared/elysia-schema";
3
3
  import { Elysia, t, type AnyElysia } from "elysia";
4
4
  import { SERVICE_KINDS, WIRE_FAMILIES } from "../../../protocol/canonical-model";
5
- import { PROBE_REASONING_EFFORTS } from "../../../providers/discovery/discovery-types";
5
+ import { PROBE_MODES, PROBE_REASONING_EFFORTS } from "../../../providers/discovery/discovery-types";
6
6
  import {
7
7
  ACCOUNT_STATUSES,
8
+ DISCOVER_MODEL_COMMIT_MAX,
8
9
  CREDENTIAL_KINDS,
9
10
  CREDENTIAL_MODES,
10
11
  } from "./contracts";
@@ -18,6 +19,7 @@ import type {
18
19
  ByokConnectionTestRequest,
19
20
  CreateProviderAccountRequest,
20
21
  CreateProviderRequest,
22
+ DiscoverModelCommitRequest,
21
23
  ProbeModelRequest,
22
24
  RegisterModelsRequest,
23
25
  SetModelEnabledRequest,
@@ -42,6 +44,7 @@ const compatibilityProfileSchema = t.Object({
42
44
  }),
43
45
  ),
44
46
  ),
47
+ service_kinds: t.Optional(t.Array(literalUnion(SERVICE_KINDS))),
45
48
  streaming_usage_mode: t.Optional(t.Union([t.Literal("include_usage"), t.Literal("none")])),
46
49
  structured_output: t.Optional(
47
50
  t.Object({
@@ -110,12 +113,28 @@ const modelMetadataOverrideBody = t.Object({
110
113
  image: t.Optional(t.Boolean()),
111
114
  reasoning: t.Optional(t.Boolean()),
112
115
  toolCall: t.Optional(t.Boolean()),
116
+ warnings: t.Optional(
117
+ t.Union([
118
+ t.Null(),
119
+ t.Array(
120
+ t.Object({
121
+ kind: t.String(),
122
+ message: t.String(),
123
+ severity: t.Optional(literalUnion(["info", "warn", "critical"])),
124
+ updatedAt: t.Optional(t.String()),
125
+ }),
126
+ ),
127
+ ]),
128
+ ),
113
129
  });
114
130
  const registerModelsBody = t.Object({
115
131
  modelIds: t.Array(t.String()),
116
132
  wireFamily: t.Optional(t.String()),
117
133
  metadata: t.Optional(modelMetadataOverrideBody),
118
134
  });
135
+ const discoverCommitBody = t.Object({
136
+ modelIds: t.Array(t.String(), { minItems: 1, maxItems: DISCOVER_MODEL_COMMIT_MAX }),
137
+ });
119
138
  const probeModelBody = t.Object({
120
139
  modelId: t.String(),
121
140
  serviceKind: t.Optional(literalUnion(SERVICE_KINDS)),
@@ -127,6 +146,7 @@ const probeModelBody = t.Object({
127
146
  maxOutputTokens: t.Optional(t.Integer({ minimum: 1, maximum: 131072 })),
128
147
  stream: t.Optional(t.Boolean()),
129
148
  surface: t.Optional(literalUnion(["default", "cosy", "grpc", "ide"])),
149
+ mode: t.Optional(literalUnion(PROBE_MODES)),
130
150
  });
131
151
  const byokConnectionTestBody = t.Object({
132
152
  baseUrl: t.String(),
@@ -275,9 +295,42 @@ export function createProviderCatalogRoutes(config: ProviderCatalogConfig): AnyE
275
295
  );
276
296
  },
277
297
  )
278
- .post("/:providerId/models/test-models", async ({ request, params }) => {
279
- return await modelFactory.probeAllModels(config.accessResolver(request), params.providerId);
280
- })
298
+ .post(
299
+ "/:providerId/models/test-models",
300
+ { body: t.Optional(t.Object({ mode: t.Optional(literalUnion(PROBE_MODES)) })) },
301
+ async ({ request, params, body }) => {
302
+ return await modelFactory.probeAllModels(
303
+ config.accessResolver(request),
304
+ params.providerId,
305
+ body?.mode,
306
+ );
307
+ },
308
+ )
309
+ // Discovery must be declared before `/:providerId/models/discover/commit`
310
+ // would be shadowed by a dynamic segment; Elysia resolves both here.
311
+ .get("/:providerId/models/discover", async ({ request, params }) => {
312
+ return await modelFactory.listDiscoveryCandidates(
313
+ config.accessResolver(request),
314
+ params.providerId,
315
+ );
316
+ })
317
+ .post(
318
+ "/:providerId/models/discover/commit",
319
+ { body: discoverCommitBody },
320
+ async ({ request, params, body }) => {
321
+ return await modelFactory.commitDiscoveredModels(
322
+ config.accessResolver(request),
323
+ params.providerId,
324
+ (body as DiscoverModelCommitRequest).modelIds,
325
+ );
326
+ },
327
+ )
328
+ .post("/:providerId/accounts/actions/disable-cooldowns", async ({ request, params }) => {
329
+ return await factory.disableCooldowns(config.accessResolver(request), params.providerId);
330
+ })
331
+ .post("/:providerId/accounts/actions/delete-banned", async ({ request, params }) => {
332
+ return await factory.deleteBanned(config.accessResolver(request), params.providerId);
333
+ })
281
334
  .post(
282
335
  "/:providerId/accounts/probe",
283
336
  { body: probeModelBody },
@@ -408,6 +461,14 @@ export function createProviderCatalogRoutes(config: ProviderCatalogConfig): AnyE
408
461
  params.accountId,
409
462
  );
410
463
  return recovered;
464
+ })
465
+ .post("/:providerId/accounts/:accountId/refresh-token", async ({ request, params }) => {
466
+ const refreshed = await factory.refreshAccountToken(
467
+ config.accessResolver(request),
468
+ params.providerId,
469
+ params.accountId,
470
+ );
471
+ return refreshed;
411
472
  })
412
473
  .post(
413
474
  "/:providerId/accounts/export",