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
@@ -26,7 +26,8 @@ import type { CartethyiaDatabase } from "../../persistence/db-handle";
26
26
  import { sleep } from "../../shared/sleep";
27
27
  import { providerAccounts, providerOauthStates } from "../../persistence/schema";
28
28
  import { decryptCredentialToString, encryptCredential } from "../../security/crypto";
29
- import { invalidateCredentialCache } from "../operations/provider-credential-service";
29
+ import { invalidateCredentialCache, loadAccountWithOauthState } from "../operations/provider-credential-service";
30
+ import { applyAccountVerdict } from "../operations/account-health-service";
30
31
  import { resolveAccountDisabledRecoveryMs } from "../../config/cooldowns";
31
32
  import { asJsonbRecord } from "../../shared/coerce";
32
33
  import {
@@ -169,6 +170,22 @@ async function releaseLease(
169
170
  );
170
171
  }
171
172
 
173
+ /**
174
+ * D21 invariant guard: a refresh is not a health verdict, so mutating the
175
+ * account's `status` here would silently re-enable an account a refresh was
176
+ * never meant to resurrect. The `.set()` calls in `persistRefreshed` accept
177
+ * `status` at the type level (it is a column on `provider_accounts`), so the
178
+ * guard is a runtime assertion that fails loudly in development instead of
179
+ * letting a future edit change health state from the refresh path.
180
+ */
181
+ function assertRefreshNeverMutatesStatus(set: Record<string, unknown>): void {
182
+ if ("status" in set) {
183
+ throw new Error(
184
+ "persistRefreshed must never write `status`; a refresh is not a health verdict",
185
+ );
186
+ }
187
+ }
188
+
172
189
  /** Persists a successful refresh and releases the lease, fenced on still holding it. */
173
190
  async function persistRefreshed(
174
191
  db: CartethyiaDatabase,
@@ -177,16 +194,21 @@ async function persistRefreshed(
177
194
  result: OAuthTokenRefreshResult,
178
195
  ): Promise<boolean> {
179
196
  return db.transaction(async (tx) => {
197
+ // This function must never write `status`; a refresh is not a health
198
+ // verdict. The guard below makes an accidental status write a loud dev
199
+ // failure rather than a silent re-enable.
200
+ const oauthSet = {
201
+ ...(result.refresh === undefined
202
+ ? {}
203
+ : { refreshCiphertext: encryptCredential(result.refresh) }),
204
+ expiresAt: result.expiresAt,
205
+ leaseOwner: null,
206
+ leaseExpiresAt: null,
207
+ };
208
+ assertRefreshNeverMutatesStatus(oauthSet);
180
209
  const leaseRows = await tx
181
210
  .update(providerOauthStates)
182
- .set({
183
- ...(result.refresh === undefined
184
- ? {}
185
- : { refreshCiphertext: encryptCredential(result.refresh) }),
186
- expiresAt: result.expiresAt,
187
- leaseOwner: null,
188
- leaseExpiresAt: null,
189
- })
211
+ .set(oauthSet)
190
212
  .where(
191
213
  and(
192
214
  eq(providerOauthStates.providerAccountId, accountId),
@@ -195,23 +217,27 @@ async function persistRefreshed(
195
217
  )
196
218
  .returning({ id: providerOauthStates.providerAccountId });
197
219
  if (leaseRows.length === 0) return false;
220
+ const accountSet = {
221
+ credentialCiphertext: encryptCredential(result.access),
222
+ // Only what the refresh reported: a refresher that learned nothing new
223
+ // leaves the stored configuration alone rather than blanking it.
224
+ // A learned identity only replaces a default label — an operator's
225
+ // custom rename is never overwritten. The CASE keeps the read and the
226
+ // write in one statement, so a concurrent rename cannot lose to the
227
+ // refresh's learned identity.
228
+ ...(result.accountLabel === undefined
229
+ ? {}
230
+ : {
231
+ label: sql`CASE WHEN lower(trim(${providerAccounts.label})) = lower(trim(${providerAccounts.providerId})) THEN ${result.accountLabel} ELSE ${providerAccounts.label} END`,
232
+ }),
233
+ ...(result.auth_state === undefined ? {} : { authState: result.auth_state }),
234
+ };
235
+ // The second half of the D21 invariant: the credential write must never
236
+ // touch the account's health state either.
237
+ assertRefreshNeverMutatesStatus(accountSet);
198
238
  await tx
199
239
  .update(providerAccounts)
200
- .set({
201
- credentialCiphertext: encryptCredential(result.access),
202
- // Only what the refresh reported: a refresher that learned nothing new
203
- // leaves the stored configuration alone rather than blanking it.
204
- // A learned identity only replaces a default label — an operator's
205
- // custom rename is never overwritten. The CASE keeps the read and the
206
- // write in one statement, so a concurrent rename cannot lose to the
207
- // refresh's learned identity.
208
- ...(result.accountLabel === undefined
209
- ? {}
210
- : {
211
- label: sql`CASE WHEN lower(trim(${providerAccounts.label})) = lower(trim(${providerAccounts.providerId})) THEN ${result.accountLabel} ELSE ${providerAccounts.label} END`,
212
- }),
213
- ...(result.auth_state === undefined ? {} : { authState: result.auth_state }),
214
- })
240
+ .set(accountSet)
215
241
  .where(eq(providerAccounts.id, accountId));
216
242
  invalidateCredentialCache(accountId);
217
243
  return true;
@@ -240,12 +266,21 @@ function buildRefreshContext(row: AccountRow, includeAccessToken: boolean): OAut
240
266
  /**
241
267
  * Marks an account permanently unusable after a definitive refresh failure,
242
268
  * fenced on the lease.
269
+ *
270
+ * @param preserveOperatorDisable When true, an account that is already
271
+ * `disabled` with no `cooldownUntil` — the operator's own disable — keeps its
272
+ * lack of a recovery deadline (D23). A machine disable (this function being
273
+ * the machine) always writes a deadline for itself, but it must not convert
274
+ * an operator's terminal decision into a recoverable one: adding a deadline
275
+ * to an operator-disabled row is what made the Recover button appear on an
276
+ * account the operator disabled on purpose.
243
277
  */
244
278
  async function disableAccount(
245
279
  db: CartethyiaDatabase,
246
280
  accountId: string,
247
281
  owner: string,
248
282
  message: string,
283
+ options: { readonly preserveOperatorDisable?: boolean } = {},
249
284
  ): Promise<void> {
250
285
  await db.transaction(async (tx) => {
251
286
  const leaseRows = await tx
@@ -260,30 +295,32 @@ async function disableAccount(
260
295
  .returning({ id: providerOauthStates.providerAccountId });
261
296
  if (leaseRows.length === 0) return;
262
297
  const disabledRecoveryMs = resolveAccountDisabledRecoveryMs();
263
- await tx
264
- .update(providerAccounts)
265
- .set({
266
- status: "disabled",
267
- lastError: message.slice(0, 500),
268
- // `auth_invalidated` is the category the console reads to render the
269
- // "Re-login required" pill, and the one the quota sweep excludes so it
270
- // stops re-probing a credential that cannot repair itself. The refresh
271
- // grant was definitively rejected and the stored access token could not
272
- // be shown to still work, so this is exactly that state.
273
- lastErrorCategory: "auth_invalidated",
274
- lastErrorAt: new Date(),
275
- // Carries a recovery deadline so `sweepExpiredCooldowns` can bring this
276
- // account back once it elapses; without one, one definitive rejection
277
- // parks the credential for good. The deadline is also what distinguishes
278
- // this (machine-disabled) from an operator's own disable, which leaves
279
- // `cooldownUntil` null and is therefore never swept. See
280
- // `resolveAccountDisabledRecoveryMs`.
281
- ...(disabledRecoveryMs > 0
282
- ? { cooldownUntil: new Date(Date.now() + disabledRecoveryMs) }
283
- : {}),
284
- })
285
- .where(eq(providerAccounts.id, accountId));
286
- invalidateCredentialCache(accountId);
298
+ // Converge on the health application seam (proposal §3.5): a definitive
299
+ // OAuth refresh failure is a producer into `applyAccountVerdict`, not a
300
+ // separate state machine. The seam preserves D23 (operator-disable rows
301
+ // keep their `null` recovery deadline) via `preserveOperatorDisable`.
302
+ const applied = await applyAccountVerdict(tx, {
303
+ accountId,
304
+ status: "disabled",
305
+ // `auth_invalidated` is the category the console reads to render the
306
+ // "Re-login required" pill, and the one the quota sweep excludes so it
307
+ // stops re-probing a credential that cannot repair itself. The refresh
308
+ // grant was definitively rejected and the stored access token could not
309
+ // be shown to still work, so this is exactly that state.
310
+ reason: message,
311
+ category: "auth_invalidated",
312
+ // Carries a recovery deadline so `sweepExpiredCooldowns` can bring this
313
+ // account back once it elapses; without one, one definitive rejection
314
+ // parks the credential for good. The deadline is also what distinguishes
315
+ // this (machine-disabled) from an operator's own disable, which leaves
316
+ // `cooldownUntil` null and is therefore never swept (D23).
317
+ cooldownUntil:
318
+ disabledRecoveryMs > 0 ? new Date(Date.now() + disabledRecoveryMs) : null,
319
+ ...(options.preserveOperatorDisable === true
320
+ ? { preserveOperatorDisable: true }
321
+ : {}),
322
+ });
323
+ if (applied) invalidateCredentialCache(accountId);
287
324
  });
288
325
  }
289
326
 
@@ -299,20 +336,33 @@ async function disableAccount(
299
336
  * operator or an explicit recovery may re-enable it), while an `active` one
300
337
  * stays `active`. The static marker and the error clearing are orthogonal to
301
338
  * the account's health state.
339
+ *
340
+ * D24: for a `disabled` row the error fields are deliberately kept. Once the
341
+ * account is parked, `lastErrorCategory` is the only record of *why* (e.g.
342
+ * `auth_invalidated` → "Re-login required"); wiping it on the static
343
+ * annotation would leave a disabled account with no diagnosable reason. An
344
+ * `active` row still clears them — there the error is stale by definition.
302
345
  */
303
346
  async function markStaticToken(db: CartethyiaDatabase, accountId: string): Promise<void> {
347
+ const rows = await db
348
+ .select({ status: providerAccounts.status })
349
+ .from(providerAccounts)
350
+ .where(eq(providerAccounts.id, accountId))
351
+ .limit(1);
352
+ const isDisabled = rows[0]?.status === "disabled";
304
353
  await db
305
354
  .update(providerAccounts)
306
355
  .set({
307
356
  staticToken: true,
357
+ cooldownUntil: null,
308
358
  // A static token is a normal, usable credential, not an error state: any
309
359
  // error left by the failed refresh would otherwise keep the row reading
310
360
  // "Re-login required" while it is in fact serving. Mirrors the console's
311
- // static-token toggle.
312
- cooldownUntil: null,
313
- lastError: null,
314
- lastErrorCategory: null,
315
- lastErrorAt: null,
361
+ // static-token toggle. For a `disabled` row the category is preserved
362
+ // (D24), because there it is the diagnosis, not a stale artifact.
363
+ ...(isDisabled
364
+ ? {}
365
+ : { lastError: null, lastErrorCategory: null, lastErrorAt: null }),
316
366
  })
317
367
  .where(eq(providerAccounts.id, accountId));
318
368
  }
@@ -362,7 +412,8 @@ function failureStatus(error: unknown): number | undefined {
362
412
  * still perfectly usable. This pass re-reads those rows once at startup and
363
413
  * moves each to where it now belongs:
364
414
  *
365
- * - access token still usable ⇒ static token, back to `active`;
415
+ * - access token still usable ⇒ static token; the account stays `disabled`;
416
+ * only an operator can re-enable it;
366
417
  * - otherwise ⇒ the `auth_invalidated` category the console renders as
367
418
  * "Re-login required" and the quota sweep excludes.
368
419
  *
@@ -495,15 +546,20 @@ export class OAuthRefreshService {
495
546
  * that just refreshed cannot cause the caller to reuse a dead cached
496
547
  * secret. Returns the current decrypted access token, or `null` when the
497
548
  * account isn't OAuth, has no stored refresh token, or refresh failed.
549
+ *
550
+ * `opts.origin` labels who asked for the refresh (`ui`/`sweep`/`dispatch`)
551
+ * and is carried on the `token_refresh` log lines (D26). The origin is
552
+ * observational only — it never changes what a refresh does.
498
553
  */
499
554
  async ensureFreshAccessToken(
500
555
  accountId: string,
501
556
  refresher: OAuthTokenRefresher,
502
- opts: { force?: boolean; skewMs?: number } = {},
557
+ opts: { force?: boolean; skewMs?: number; origin?: "ui" | "sweep" | "dispatch" } = {},
503
558
  ): Promise<string | null> {
504
559
  const force = opts.force === true;
505
560
  // No explicit skew: the per-provider lead is applied at load time.
506
561
  const skewMs = opts.skewMs;
562
+ const origin = opts.origin ?? "sweep";
507
563
  // Forced refreshes get their own in-flight slot so they don't merge with
508
564
  // a raced non-force call that would otherwise short-circuit.
509
565
  const key = force ? `${accountId}:force` : accountId;
@@ -518,7 +574,7 @@ export class OAuthRefreshService {
518
574
  const oldestKey = this.#inFlight.keys().next().value;
519
575
  if (oldestKey !== undefined) this.#inFlight.delete(oldestKey);
520
576
  }
521
- const task = this.#refreshIfDue(accountId, refresher, skewMs, force).finally(() => {
577
+ const task = this.#refreshIfDue(accountId, refresher, skewMs, force, origin).finally(() => {
522
578
  this.#inFlight.delete(key);
523
579
  });
524
580
  this.#inFlight.set(key, task);
@@ -530,6 +586,7 @@ export class OAuthRefreshService {
530
586
  refresher: OAuthTokenRefresher,
531
587
  skewMs: number | undefined,
532
588
  force: boolean,
589
+ origin: "ui" | "sweep" | "dispatch",
533
590
  ): Promise<string | null> {
534
591
  const account = await loadAccountWithFreshness(this.#db, accountId, skewMs);
535
592
  if (!account || account.row.credentialKind !== "oauth") return null;
@@ -547,18 +604,24 @@ export class OAuthRefreshService {
547
604
  ? decryptCredentialToString(row.credentialCiphertext)
548
605
  : null;
549
606
  }
550
- return this.#refreshNow(row, refresher, skewMs);
607
+ return this.#refreshNow(row, refresher, skewMs, origin);
551
608
  }
552
609
 
553
610
  async #refreshNow(
554
611
  row: AccountRow,
555
612
  refresher: OAuthTokenRefresher,
556
613
  skewMs: number | undefined,
614
+ origin: "ui" | "sweep" | "dispatch",
557
615
  ): Promise<string | null> {
616
+ // D26: statusBefore is read once up front (the row projection used by the
617
+ // refresh path does not carry status) and statusAfter is re-read after any
618
+ // mutation, so the log proves a refresh never flipped the account.
619
+ const statusBefore = await readAccountStatus(this.#db, row.id);
558
620
  pushStructuredConsoleLog("info", "OAuth token refresh started", {
559
621
  event: "token_refresh",
560
622
  accountId: row.id,
561
623
  providerId: row.providerId,
624
+ details: { origin, statusBefore },
562
625
  });
563
626
  const owner = randomUUID();
564
627
  const leased = await acquireLease(this.#db, row.id, owner);
@@ -583,6 +646,7 @@ export class OAuthRefreshService {
583
646
  accountId: row.id,
584
647
  providerId: row.providerId,
585
648
  errorCode: "static_token",
649
+ details: { origin },
586
650
  });
587
651
  return null;
588
652
  }
@@ -594,12 +658,20 @@ export class OAuthRefreshService {
594
658
  buildRefreshContext(row, refresher.requiresAccessToken === true),
595
659
  );
596
660
  const persisted = await persistRefreshed(this.#db, row.id, owner, result);
661
+ // D26: `statusAfter` is re-read from the DB rather than assumed — a
662
+ // successful refresh never changes `status`, and the log proves it.
663
+ const statusAfter = await readAccountStatus(this.#db, row.id);
597
664
  pushStructuredConsoleLog(persisted ? "info" : "warn", persisted ? "OAuth token refresh completed" : "OAuth token refresh lost lease", {
598
665
  event: "token_refresh",
599
666
  accountId: row.id,
600
667
  providerId: row.providerId,
601
668
  ...(persisted ? {} : { errorCode: "refresh_lease_lost" }),
602
- details: { expiresAt: result.expiresAt.toISOString() },
669
+ details: {
670
+ expiresAt: result.expiresAt.toISOString(),
671
+ origin,
672
+ statusBefore,
673
+ statusAfter,
674
+ },
603
675
  });
604
676
  return persisted ? result.access : null;
605
677
  } catch (error) {
@@ -612,6 +684,7 @@ export class OAuthRefreshService {
612
684
  accountId: row.id,
613
685
  providerId: row.providerId,
614
686
  errorCode: classification,
687
+ details: { origin, statusBefore },
615
688
  });
616
689
  if (classification === "definitive") {
617
690
  // A definitive refresh failure means the refresh grant is dead — but
@@ -627,14 +700,29 @@ export class OAuthRefreshService {
627
700
  if (accessUsability === "usable") {
628
701
  await releaseLease(this.#db, row.id, owner);
629
702
  await markStaticToken(this.#db, row.id);
703
+ const statusAfter = await readAccountStatus(this.#db, row.id);
630
704
  pushStructuredConsoleLog("info", "OAuth refresh failed; access token still valid, marking static", {
631
705
  event: "token_refresh",
632
706
  accountId: row.id,
633
707
  providerId: row.providerId,
634
708
  errorCode: "static_token",
709
+ details: { origin, statusBefore, statusAfter },
635
710
  });
636
711
  } else {
637
- await disableAccount(this.#db, row.id, owner, failureMessage(error));
712
+ await disableAccount(this.#db, row.id, owner, failureMessage(error), {
713
+ // D23: a definitive refresh failure must not turn an operator's
714
+ // disable into a machine-recoverable one. If the row is already
715
+ // `disabled` without a deadline, leave it that way.
716
+ preserveOperatorDisable: true,
717
+ });
718
+ const statusAfter = await readAccountStatus(this.#db, row.id);
719
+ pushStructuredConsoleLog("warn", "OAuth refresh failed definitively; account disabled for re-auth", {
720
+ event: "token_refresh",
721
+ accountId: row.id,
722
+ providerId: row.providerId,
723
+ errorCode: classification,
724
+ details: { origin, statusBefore, statusAfter },
725
+ });
638
726
  }
639
727
  } else {
640
728
  await releaseLease(this.#db, row.id, owner);
@@ -643,3 +731,9 @@ export class OAuthRefreshService {
643
731
  }
644
732
  }
645
733
  }
734
+
735
+ /** Reads one account's status (used only for logging/observability, D26). */
736
+ async function readAccountStatus(db: CartethyiaDatabase, accountId: string): Promise<string | undefined> {
737
+ const loaded = await loadAccountWithOauthState(db, accountId, { includeStatus: true });
738
+ return loaded?.status;
739
+ }
@@ -3,6 +3,7 @@ import type { CanonicalRequest, WireFamily } from "../protocol/canonical-model";
3
3
  import { GatewayError } from "../protocol/gateway-error";
4
4
  import { OpenAICompatibleAdapter, withBearerAuthentication } from "./adapters/compatible-adapter";
5
5
  import { providerBaseUrl } from "./provider-metadata";
6
+ import type { ProviderModule } from "./provider-registry";
6
7
 
7
8
  export function ensurePayloadModel(
8
9
  payload: Record<string, unknown>,
@@ -15,6 +16,7 @@ export function ensurePayloadModel(
15
16
  payload["model"] = model;
16
17
  return model;
17
18
  }
19
+
18
20
  /**
19
21
  * Declarative description of one bearer-authenticated OpenAI-compatible provider.
20
22
  *
@@ -98,3 +100,149 @@ export function createApiKeyAdapter(
98
100
  }),
99
101
  );
100
102
  }
103
+
104
+ function apiKeySpec(
105
+ providerId: ProviderId,
106
+ paths: Readonly<Partial<Record<"chat" | "responses", string>>>,
107
+ ): ApiKeyProviderSpec {
108
+ return {
109
+ provider_id: providerId,
110
+ endpoint_paths_by_wire_family: paths,
111
+ };
112
+ }
113
+
114
+ const BEARER_PATHS = { chat: "/chat/completions", responses: "/responses" } as const;
115
+
116
+ /**
117
+ * Builtin providers whose entire adapter contract is "bearer auth against the
118
+ * provider's `BUNDLED_PROVIDER_METADATA` base URL, OpenAI-compatible chat +
119
+ * responses, with endpoint paths declared below the base's version root".
120
+ */
121
+ export const GENERIC_API_KEY_SPECS = {
122
+ larprouter: apiKeySpec("larprouter", BEARER_PATHS),
123
+ mistral: apiKeySpec("mistral", BEARER_PATHS),
124
+ fireworks: apiKeySpec("fireworks", BEARER_PATHS),
125
+ nvidia: apiKeySpec("nvidia", BEARER_PATHS),
126
+ gmi: apiKeySpec("gmi", BEARER_PATHS),
127
+ ollamacloud: apiKeySpec("ollamacloud", BEARER_PATHS),
128
+ together: apiKeySpec("together", BEARER_PATHS),
129
+ siliconflow: apiKeySpec("siliconflow", BEARER_PATHS),
130
+ siliconflowcn: apiKeySpec("siliconflowcn", BEARER_PATHS),
131
+ nanogpt: apiKeySpec("nanogpt", BEARER_PATHS),
132
+ baseten: apiKeySpec("baseten", BEARER_PATHS),
133
+ synthetic: apiKeySpec("synthetic", BEARER_PATHS),
134
+ stepfun: apiKeySpec("stepfun", BEARER_PATHS),
135
+ sakana: apiKeySpec("sakana", BEARER_PATHS),
136
+ helmcode: apiKeySpec("helmcode", BEARER_PATHS),
137
+ coralbricks: apiKeySpec("coralbricks", BEARER_PATHS),
138
+ aiand: apiKeySpec("aiand", BEARER_PATHS),
139
+ wafer: apiKeySpec("wafer", BEARER_PATHS),
140
+ coreweave: apiKeySpec("coreweave", BEARER_PATHS),
141
+ dashscope: apiKeySpec("dashscope", BEARER_PATHS),
142
+ "qwen-portal": apiKeySpec("qwen-portal", BEARER_PATHS),
143
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
144
+
145
+ export type GenericApiKeyProviderId = keyof typeof GENERIC_API_KEY_SPECS;
146
+
147
+ export const GENERIC_API_KEY_PROVIDER_IDS = Object.keys(
148
+ GENERIC_API_KEY_SPECS,
149
+ ) as readonly GenericApiKeyProviderId[];
150
+
151
+ /**
152
+ * Ids whose base URL already ends at a version segment.
153
+ */
154
+ export const VERSIONED_ROOT_API_KEY_SPECS = {
155
+ groq: apiKeySpec("groq", BEARER_PATHS),
156
+ deepinfra: apiKeySpec("deepinfra", BEARER_PATHS),
157
+ novita: apiKeySpec("novita", BEARER_PATHS),
158
+ venice: apiKeySpec("venice", BEARER_PATHS),
159
+ qianfan: apiKeySpec("qianfan", BEARER_PATHS),
160
+ zhipucoding: apiKeySpec("zhipucoding", BEARER_PATHS),
161
+ firepass: apiKeySpec("firepass", BEARER_PATHS),
162
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
163
+
164
+ export type VersionedRootApiKeyProviderId =
165
+ keyof typeof VERSIONED_ROOT_API_KEY_SPECS;
166
+
167
+ export const VERSIONED_ROOT_API_KEY_PROVIDER_IDS = Object.keys(
168
+ VERSIONED_ROOT_API_KEY_SPECS,
169
+ ) as readonly VersionedRootApiKeyProviderId[];
170
+
171
+ type DiscoveryBackedCapabilities = Pick<
172
+ ProviderModule,
173
+ "endpointPathsByWireFamily" | "loadAdapter" | "loadModelDiscovery"
174
+ >;
175
+
176
+ type ModelDiscoveryFactory = NonNullable<ProviderModule["loadModelDiscovery"]>;
177
+ export type OpenAIModelDiscoveryFactory = (
178
+ providerId: string,
179
+ options?: {
180
+ readonly headers?: (credential: string) => Readonly<Record<string, string>>;
181
+ },
182
+ ) => ModelDiscoveryFactory;
183
+
184
+ const DISCOVERY_BACKED_PROVIDER_SPECS = {
185
+ groq: VERSIONED_ROOT_API_KEY_SPECS.groq,
186
+ larprouter: GENERIC_API_KEY_SPECS.larprouter,
187
+ together: GENERIC_API_KEY_SPECS.together,
188
+ deepinfra: VERSIONED_ROOT_API_KEY_SPECS.deepinfra,
189
+ siliconflow: GENERIC_API_KEY_SPECS.siliconflow,
190
+ siliconflowcn: GENERIC_API_KEY_SPECS.siliconflowcn,
191
+ novita: VERSIONED_ROOT_API_KEY_SPECS.novita,
192
+ venice: VERSIONED_ROOT_API_KEY_SPECS.venice,
193
+ nanogpt: GENERIC_API_KEY_SPECS.nanogpt,
194
+ baseten: GENERIC_API_KEY_SPECS.baseten,
195
+ synthetic: GENERIC_API_KEY_SPECS.synthetic,
196
+ stepfun: GENERIC_API_KEY_SPECS.stepfun,
197
+ sakana: GENERIC_API_KEY_SPECS.sakana,
198
+ helmcode: GENERIC_API_KEY_SPECS.helmcode,
199
+ coralbricks: GENERIC_API_KEY_SPECS.coralbricks,
200
+ aiand: GENERIC_API_KEY_SPECS.aiand,
201
+ wafer: GENERIC_API_KEY_SPECS.wafer,
202
+ firepass: VERSIONED_ROOT_API_KEY_SPECS.firepass,
203
+ zhipucoding: VERSIONED_ROOT_API_KEY_SPECS.zhipucoding,
204
+ dashscope: GENERIC_API_KEY_SPECS.dashscope,
205
+ qianfan: VERSIONED_ROOT_API_KEY_SPECS.qianfan,
206
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
207
+
208
+ type DiscoveryBackedProviderId = keyof typeof DISCOVERY_BACKED_PROVIDER_SPECS;
209
+
210
+ /** Canonical discovery-backed provider ids, in declaration order. */
211
+ export const DISCOVERY_BACKED_PROVIDER_IDS = Object.keys(
212
+ DISCOVERY_BACKED_PROVIDER_SPECS,
213
+ ) as readonly DiscoveryBackedProviderId[];
214
+
215
+ export function discoveryBackedCapabilities(
216
+ providerId: string,
217
+ spec: ApiKeyProviderSpec,
218
+ openAIModelDiscovery: OpenAIModelDiscoveryFactory,
219
+ ): DiscoveryBackedCapabilities {
220
+ return {
221
+ ...(spec.endpoint_paths_by_wire_family === undefined
222
+ ? {}
223
+ : { endpointPathsByWireFamily: spec.endpoint_paths_by_wire_family }),
224
+ loadAdapter: async () => createApiKeyAdapter(spec),
225
+ loadModelDiscovery: openAIModelDiscovery(providerId, {
226
+ headers: (credential) => ({ authorization: `Bearer ${credential}` }),
227
+ }),
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Creates the standard provider capability table using the registry's shared
233
+ * discovery loader, keeping its lazy import and authentication behavior intact.
234
+ */
235
+ export function createDiscoveryBackedProviderCapabilities(
236
+ openAIModelDiscovery: OpenAIModelDiscoveryFactory,
237
+ ): Readonly<Record<DiscoveryBackedProviderId, DiscoveryBackedCapabilities>> {
238
+ const capabilities = {} as Record<DiscoveryBackedProviderId, DiscoveryBackedCapabilities>;
239
+ for (const providerId of DISCOVERY_BACKED_PROVIDER_IDS) {
240
+ capabilities[providerId] = discoveryBackedCapabilities(
241
+ providerId,
242
+ DISCOVERY_BACKED_PROVIDER_SPECS[providerId],
243
+ openAIModelDiscovery,
244
+ );
245
+ }
246
+ return capabilities;
247
+ }
248
+