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
@@ -47,7 +47,7 @@ import {
47
47
  grok407ProbeFailure,
48
48
  hasMeaningfulOutput,
49
49
  loadProbePreferences,
50
- qoderCmodelProbePrompt,
50
+ PROBE_FAST_SAMPLE_MAX_CHARS,
51
51
  recordProbeHealth,
52
52
  resolveProbeAdapter,
53
53
  resolveProbeTarget,
@@ -56,7 +56,7 @@ import {
56
56
  wireContextFrom,
57
57
  type ProbeProviderRow,
58
58
  } from "./probe-phases";
59
- import { createProbeFetch } from "../operations/probe-fetch";
59
+ import { createProbeFetch } from "../provider-probe";
60
60
  import { authHeaders } from "../adapters/compatible-adapter";
61
61
  import {
62
62
  byokAuthHeaderShape,
@@ -188,14 +188,16 @@ export class ProviderProbingService {
188
188
  request: ProbeModelRequest,
189
189
  ): Promise<ProbeModelResult> {
190
190
  const startedAt = Date.now();
191
- // Qoder's IDE surface is the only transport that echoes provider metadata
192
- // (`provider_model`, `target`, …) in its raw_usage, so probes for Qoder
193
- // default to it unless the operator picked a surface explicitly. This
194
- // covers every entry point (single model, all models, all accounts).
191
+ // Qoder's legacy COSY surface is the only live chat transport since
192
+ // 2026-11-07 (the gRPC host is NXDOMAIN and the IDE `/model/v1` surface
193
+ // answers 404 on every alive host). Probes for Qoder default to it unless
194
+ // the operator picked a surface explicitly. The IDE surface also echoes
195
+ // provider metadata, but that is worthless when the surface itself is
196
+ // dead — a probe must report what live traffic would do.
195
197
  const normalizedRequest: ProbeModelRequest =
196
198
  (providerId === "qoder" || providerId === "qodercn") &&
197
199
  (request.surface === undefined || request.surface === "default")
198
- ? { ...request, surface: "ide" }
200
+ ? { ...request, surface: "cosy" }
199
201
  : request;
200
202
  const modelId = normalizedRequest.modelId;
201
203
  // One provider snapshot per probe: the contract gate, the account gate, the
@@ -241,24 +243,42 @@ export class ProviderProbingService {
241
243
  };
242
244
  }
243
245
 
244
- const { probeReasoning, payloadCapture } = await loadProbePreferences({
246
+ const preferences = await loadProbePreferences({
245
247
  db: this.db,
246
248
  tenantId,
247
249
  wireFamily,
248
250
  request,
249
251
  });
250
- // Qoder's gRPC surface refuses a probe that asks a model to identify
251
- // itself (406 from the upstream proxy) for one roster row (`cmodel`,
252
- // virtual id `cantus`). dmodel/qmodel/smodel accept the identity prompt
253
- // fine — only cmodel refuses it — so the workaround is scoped to that
254
- // row, not to every Qoder probe, and never overrides an explicit prompt.
255
- const qoderCmodelPrompt = qoderCmodelProbePrompt(providerId, modelId, normalizedRequest.prompt);
256
- const probeRequest = qoderCmodelPrompt === undefined ? normalizedRequest : { ...normalizedRequest, prompt: qoderCmodelPrompt };
252
+ const { probeReasoning, payloadCapture } = preferences;
253
+ // `detail` is the exact pre-existing behaviour; only `fast` changes any
254
+ // budget. The identity prompt itself is shared by both modes (D42).
255
+ const fast = preferences.mode === "fast";
256
+ const probeTimeoutMs = preferences.timeoutMs ?? 30_000;
257
+ const probeSignal = AbortSignal.timeout(probeTimeoutMs);
257
258
  const canonicalRequest = buildProbeCanonicalRequest({
258
259
  modelId,
259
- request: probeRequest,
260
+ request: normalizedRequest,
260
261
  probeReasoning,
261
262
  sourceSurface,
263
+ ...(preferences.maxOutputTokens === undefined
264
+ ? {}
265
+ : { maxOutputTokens: preferences.maxOutputTokens }),
266
+ // Freebuff's admission policy gate flags repeated identical probes as
267
+ // automated farming (`prompt_policy_refused` / "same message sent over
268
+ // and over"). Its probe must be minimal and always distinct per model —
269
+ // no worker system template, no shared identity question. `ping` is the
270
+ // same minimal greeting the fast-probe path already uses; the model id
271
+ // keeps every probe distinct so the "same message over and over" gate
272
+ // cannot flag a batch run.
273
+ ...(providerId === "freebuff"
274
+ ? {
275
+ defaultPromptOverride: `ping ${modelId}`,
276
+ providerSystemPrompt: undefined,
277
+ }
278
+ : {}),
279
+ ...(typeof adapter?.systemPrompt === "function" && providerId !== "freebuff"
280
+ ? { providerSystemPrompt: adapter.systemPrompt() }
281
+ : {}),
262
282
  });
263
283
  let capturedRequest = canonicalRequest;
264
284
  const parsedProviderId = parseProviderId(providerId);
@@ -282,7 +302,6 @@ export class ProviderProbingService {
282
302
  : { probe_surface: normalizedRequest.surface }),
283
303
  };
284
304
 
285
- const probeSignal = AbortSignal.timeout(30_000);
286
305
  const events: CanonicalEvent[] = [];
287
306
  /**
288
307
  * Populated when the probe dials. Declared here, not inside the try, so the
@@ -337,7 +356,7 @@ export class ProviderProbingService {
337
356
  candidate,
338
357
  {
339
358
  credential,
340
- deadline: startedAt + 30_000,
359
+ deadline: startedAt + probeTimeoutMs,
341
360
  abort_signal: probeSignal,
342
361
  outbound_fetch: probeFetch,
343
362
  },
@@ -373,7 +392,7 @@ export class ProviderProbingService {
373
392
  candidate,
374
393
  {
375
394
  credential,
376
- deadline: startedAt + 30_000,
395
+ deadline: startedAt + probeTimeoutMs,
377
396
  abort_signal: probeSignal,
378
397
  outbound_fetch: probeFetch,
379
398
  },
@@ -415,7 +434,7 @@ export class ProviderProbingService {
415
434
  capturedRequest = request;
416
435
  for await (const event of adapter.dispatch(request, candidate, {
417
436
  credential,
418
- deadline: startedAt + 30_000,
437
+ deadline: startedAt + probeTimeoutMs,
419
438
  abort_signal: probeSignal,
420
439
  outbound_fetch: probeFetch,
421
440
  // A bidi-only provider (Cursor's Connect-RPC Run) has no `fetch`
@@ -433,7 +452,8 @@ export class ProviderProbingService {
433
452
  if (!hasMeaningfulOutput(events)) throw new Error("probe response content empty");
434
453
  } catch (error) {
435
454
  const message = error instanceof Error ? error.message : String(error);
436
- if (!/empty\s+(?:response\s+)?content|response\s+content\s+empty/i.test(message)) {
455
+ // `fast` is deliberately single-shot: no 1024-token streamed retry.
456
+ if (fast || !/empty\s+(?:response\s+)?content|response\s+content\s+empty/i.test(message)) {
437
457
  throw error;
438
458
  }
439
459
  // Some reasoning models spend the short probe budget before producing
@@ -526,26 +546,40 @@ export class ProviderProbingService {
526
546
  latencyMs,
527
547
  });
528
548
 
529
- // Probe telemetry rides the same batch buffer as live traffic. Keep the
530
- // probe bodies linked by request_id so the console can inspect the exact
531
- // canonical request and provider event stream that produced the result.
532
- this.telemetryBuffer?.enqueue({
533
- tenantId,
534
- requestId,
535
- sourceSurface,
536
- requestedModel: modelId,
537
- endpoint: endpointPath,
538
- providerId,
539
- ...(accountId ? { accountId } : {}),
540
- ...(networkPoolId ? { networkPoolId } : {}),
541
- latencyMs,
542
- ...(ttfbMs !== undefined ? { ttfbMs } : {}),
543
- stream: capturedRequest.stream,
544
- status: ok ? "completed" : "failed",
545
- ...(ok ? {} : { errorCategory: errorMessage ?? "probe_failed" }),
546
- ...(usage ? { usage } : {}),
547
- ...(tokensPerSec !== undefined ? { tokensPerSec } : {}),
548
- });
549
+ // Probe telemetry rides the same batch buffer as live traffic. `fast`
550
+ // keeps only the liveness row — no token-speed/usage/TTFB detail — so a
551
+ // "test all models" run stays cheap; `detail` is unchanged.
552
+ if (fast) {
553
+ this.telemetryBuffer?.enqueue({
554
+ tenantId,
555
+ requestId,
556
+ sourceSurface,
557
+ requestedModel: modelId,
558
+ endpoint: endpointPath,
559
+ providerId,
560
+ latencyMs,
561
+ stream: false,
562
+ status: ok ? "completed" : "failed",
563
+ });
564
+ } else {
565
+ this.telemetryBuffer?.enqueue({
566
+ tenantId,
567
+ requestId,
568
+ sourceSurface,
569
+ requestedModel: modelId,
570
+ endpoint: endpointPath,
571
+ providerId,
572
+ ...(accountId ? { accountId } : {}),
573
+ ...(networkPoolId ? { networkPoolId } : {}),
574
+ latencyMs,
575
+ ...(ttfbMs !== undefined ? { ttfbMs } : {}),
576
+ stream: capturedRequest.stream,
577
+ status: ok ? "completed" : "failed",
578
+ ...(ok ? {} : { errorCategory: errorMessage ?? "probe_failed" }),
579
+ ...(usage ? { usage } : {}),
580
+ ...(tokensPerSec !== undefined ? { tokensPerSec } : {}),
581
+ });
582
+ }
549
583
  if (this.telemetryBuffer && payloadCapture.enabled) {
550
584
  if (payloadCapture.mode === "metadata") {
551
585
  // Stage presence only: the provider request line (method, URL,
@@ -596,7 +630,13 @@ export class ProviderProbingService {
596
630
  }
597
631
  }
598
632
 
599
- const sample = extractSample(events);
633
+ const rawSample = extractSample(events);
634
+ // `fast` answers one question — did the model talk back? — so an 80-char
635
+ // excerpt is enough; `detail` keeps the full sample verbatim (D40).
636
+ const sample =
637
+ rawSample === undefined || !fast
638
+ ? rawSample
639
+ : rawSample.slice(0, PROBE_FAST_SAMPLE_MAX_CHARS);
600
640
  // The HTTP-only `readReportedModel` covers response-body transports; a
601
641
  // non-HTTP transport (Qoder gRPC) reports the upstream echo as a
602
642
  // `response_start` event instead — fall back to that when the body read
@@ -611,11 +651,11 @@ export class ProviderProbingService {
611
651
  ...(accountId ? { accountId } : {}),
612
652
  ok,
613
653
  latencyMs,
614
- ...(ttfbMs !== undefined ? { ttfbMs } : {}),
654
+ ...(fast || ttfbMs === undefined ? {} : { ttfbMs }),
615
655
  ...(upstreamStatusCode === undefined ? {} : { statusCode: upstreamStatusCode }),
616
656
  ...(sample ? { sample } : {}),
617
- ...(reportedModel === undefined ? {} : { reportedModel }),
618
- ...(providerMetadata === undefined || Object.keys(providerMetadata).length === 0
657
+ ...(fast || reportedModel === undefined ? {} : { reportedModel }),
658
+ ...(fast || providerMetadata === undefined || Object.keys(providerMetadata).length === 0
619
659
  ? {}
620
660
  : { providerMetadata }),
621
661
  ...(searchResults !== undefined && searchResults.length > 0 ? { searchResults } : {}),
@@ -717,38 +757,54 @@ export class ProviderProbingService {
717
757
  }
718
758
  }
719
759
 
720
- async syncModels(tenantId: string, providerId: string): Promise<{ synced: number }> {
760
+ /**
761
+ * Fetches one provider's model listing through the exact same two branches
762
+ * `syncModels` uses — a registry resolver, or the generic OpenAI-compatible
763
+ * `/models` fetch for BYOK — without writing anything. `supported:false`
764
+ * names a provider with no listing path so the Add-Model UI can offer its
765
+ * manual fallback instead of reporting a transport failure.
766
+ *
767
+ * The account is the first usable one (`selectUsableAccount`), the same
768
+ * selection sync uses (D27); its label is returned when available so the
769
+ * surface can say whose roster it is showing.
770
+ */
771
+ async discoverModels(
772
+ tenantId: string,
773
+ providerId: string,
774
+ ): Promise<{
775
+ readonly supported: boolean;
776
+ readonly models: readonly ModelDefinition[];
777
+ readonly accountLabel?: string;
778
+ /** Wire resolution context + BYOK family constraints for a later commit. */
779
+ readonly wire: {
780
+ readonly providerRow: ProbeProviderRow | undefined;
781
+ readonly supportedWireFamilies: readonly WireFamily[] | undefined;
782
+ readonly baseUrl: string | undefined;
783
+ };
784
+ }> {
721
785
  const outbound = asOutboundBinding(await this.outboundFetchFor(tenantId, providerId));
722
786
  try {
723
787
  const discoveryFetch = asFetchLike(outbound.fetch);
724
788
  const resolver = await this.providerRegistry.resolveModelDiscovery(providerId);
789
+ const account = await this.usableAccount(tenantId, providerId);
790
+ const providerRow = await this.loadProviderWireRow(providerId);
725
791
  if (resolver) {
726
- // An account is used when one is usable, and the fetch is attempted
727
- // either way. Gating on a credential only made the common case
728
- // unreachable: most providers list their models without auth, and the
729
- // ones that do not answer 401 themselves, which names the actual
730
- // problem where a local 409 named a missing account the operator may
731
- // not even need.
732
- const account = await this.usableAccount(tenantId, providerId);
733
- const secret = account?.secret;
734
- const providerRow = await this.loadProviderWireRow(providerId);
735
- const effectiveBaseUrl = providerRow?.baseUrl ?? providerBaseUrl(providerId) ?? "";
792
+ // `providerBaseUrl` throws for a non-bundled id; a custom provider can
793
+ // still register a discovery resolver, and its own row's base URL is
794
+ // the authority. Only a bundled provider falls back to metadata.
795
+ const effectiveBaseUrl =
796
+ providerRow?.baseUrl ??
797
+ (isBundledProviderId(providerId) ? providerBaseUrl(providerId) : "");
736
798
  let discovered: readonly ModelDefinition[] | null;
737
799
  try {
738
800
  discovered = await resolver({
739
801
  baseUrl: effectiveBaseUrl,
740
- credential: secret ?? "",
802
+ credential: account?.secret ?? "",
741
803
  ...(discoveryFetch ? { fetcher: discoveryFetch } : {}),
742
804
  ...(account?.authState === undefined ? {} : { authState: account.authState }),
743
805
  });
744
806
  } catch (error) {
745
- throw new GatewayError(
746
- "transport_unavailable",
747
- 502,
748
- `Model discovery failed for ${providerId}: ${error instanceof Error ? error.message : "unknown error"} (request {requestId}). The connection ghosted us — come back in a moment 👻`,
749
- {},
750
- "upstream",
751
- );
807
+ throw this.discoveryFailure(providerId, error);
752
808
  }
753
809
  if (!discovered) {
754
810
  throw new GatewayError(
@@ -759,101 +815,92 @@ export class ProviderProbingService {
759
815
  "upstream",
760
816
  );
761
817
  }
762
- const ctx = wireContextFrom(providerRow);
763
- return this.persistDiscoveredModels(
764
- providerId,
765
- discovered,
766
- (modelId) => resolveDiscoveredWire(modelId, ctx, undefined, this.defaultEndpoints),
767
- ctx.endpointPathsByWireFamily,
768
- effectiveBaseUrl,
769
- );
818
+ return {
819
+ supported: true,
820
+ models: discovered,
821
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
822
+ wire: { providerRow, supportedWireFamilies: undefined, baseUrl: effectiveBaseUrl },
823
+ };
770
824
  }
771
-
772
- const providerRow = await this.loadProviderWireRow(providerId);
773
825
  if (providerRow?.baseUrl) {
774
- const secret = await this.usableAccountSecret(tenantId, providerId);
775
- if (secret) {
776
- // An Anthropic-compatible root reads `x-api-key`, not bearer auth,
777
- // and a bare host needs its `/v1` segment before `/models` joins on.
778
- // Both are derived from the same wire profile dispatch uses, so
779
- // auto-fetch behaves identically for either flavor of custom provider.
780
- const wireProfile = resolveByokWireProfile(
781
- providerRow.wireFamilyDefault,
782
- providerRow.compatibilityProfile as CompatibilityProfile | null,
783
- );
784
- const modelsBaseUrl = modelDiscoveryBaseUrl(providerRow.baseUrl);
785
- let discovered: readonly ModelDefinition[] | null;
786
- try {
787
- discovered = await fetchOpenAICompatibleModels({
788
- baseUrl: modelsBaseUrl,
789
- providerId,
790
- headers: {
791
- ...authHeaders(
792
- wireProfile.authHeaderShape,
793
- new TextEncoder().encode(secret),
794
- "api_key",
795
- ),
796
- },
797
- ...(discoveryFetch ? { fetcher: discoveryFetch as typeof fetch } : {}),
798
- });
799
- } catch (error) {
800
- throw new GatewayError(
801
- "transport_unavailable",
802
- 502,
803
- `Model discovery failed for ${providerId}: ${error instanceof Error ? error.message : "unknown error"} (request {requestId}). The connection ghosted us — come back in a moment 👻`,
804
- {},
805
- "upstream",
806
- );
807
- }
808
- if (discovered && discovered.length > 0) {
809
- const ctx = wireContextFrom(providerRow);
810
- return this.persistDiscoveredModels(
811
- providerId,
812
- discovered,
813
- (modelId) => resolveDiscoveredWire(modelId, ctx, undefined, this.defaultEndpoints),
814
- ctx.endpointPathsByWireFamily,
815
- providerRow.baseUrl ?? undefined,
816
- // The generic `/models` fetch guesses a wire from the model id;
817
- // the row's own profile is what the adapter will enforce, so it
818
- // decides which guess is admissible.
819
- wireProfile.supportedWireFamilies,
820
- );
821
- }
826
+ const wireProfile = resolveByokWireProfile(
827
+ providerRow.wireFamilyDefault,
828
+ providerRow.compatibilityProfile as CompatibilityProfile | null,
829
+ );
830
+ const modelsBaseUrl = modelDiscoveryBaseUrl(providerRow.baseUrl);
831
+ let discovered: readonly ModelDefinition[] | null;
832
+ try {
833
+ discovered = await fetchOpenAICompatibleModels({
834
+ baseUrl: modelsBaseUrl,
835
+ providerId,
836
+ headers: {
837
+ ...authHeaders(
838
+ wireProfile.authHeaderShape,
839
+ new TextEncoder().encode(account?.secret ?? ""),
840
+ "api_key",
841
+ ),
842
+ },
843
+ ...(discoveryFetch ? { fetcher: discoveryFetch as typeof fetch } : {}),
844
+ });
845
+ } catch (error) {
846
+ throw this.discoveryFailure(providerId, error);
847
+ }
848
+ if (discovered && discovered.length > 0) {
849
+ return {
850
+ supported: true,
851
+ models: discovered,
852
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
853
+ wire: {
854
+ providerRow,
855
+ supportedWireFamilies: wireProfile.supportedWireFamilies,
856
+ baseUrl: providerRow.baseUrl ?? undefined,
857
+ },
858
+ };
822
859
  }
823
860
  }
861
+ return {
862
+ supported: false,
863
+ models: [],
864
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
865
+ wire: { providerRow, supportedWireFamilies: undefined, baseUrl: undefined },
866
+ };
867
+ } finally {
868
+ outbound.release?.();
869
+ }
870
+ }
824
871
 
825
- // Two reasons a sync reaches here with nothing dialed, and the operator
826
- // needs to be told which one. Returning `{synced: 0}` for both made an
827
- // unsupported provider indistinguishable from a successful fetch that
828
- // found nothing — the console showed a green "0 synced" for a provider
829
- // it can never sync.
830
- const bundled = isBundledProviderId(providerId);
831
- if (!bundled) {
832
- // A custom provider has a row and a base URL but no usable credential.
833
- // The fix is an account, and saying "no listing endpoint" would send the
834
- // operator looking for a capability the provider may well have.
835
- throw new GatewayError(
836
- "invalid_request",
837
- 400,
838
- `Add an account for ${providerId} first — model listing needs a credential (request {requestId}). Wrong key, right door — check the credential 🔑`,
839
- {},
840
- "cartethyia",
841
- );
842
- }
843
- if (!(await this.usableAccountSecret(tenantId, providerId))) {
844
- // A bundled provider with an account would have gone through the
845
- // resolver branch above, so reaching here means none is registered.
846
- // Add one first, then fetch.
847
- throw new GatewayError(
848
- "invalid_request",
849
- 400,
850
- `Add an account for ${providerId} before fetching its models (request {requestId}). Wrong key, right door — check the credential 🔑`,
851
- {},
852
- "cartethyia",
853
- );
872
+ /** One upstream-shaped error for both discovery branches. */
873
+ private discoveryFailure(providerId: string, error: unknown): GatewayError {
874
+ return new GatewayError(
875
+ "transport_unavailable",
876
+ 502,
877
+ `Model discovery failed for ${providerId}: ${error instanceof Error ? error.message : "unknown error"} (request {requestId}). The connection ghosted us — come back in a moment 👻`,
878
+ {},
879
+ "upstream",
880
+ );
881
+ }
882
+
883
+ /**
884
+ * The single write path for every discovery result: one fresh fetch, one
885
+ * subset, one source. Sync passes the whole roster as `discovered`; the
886
+ * Add-Model commit passes the operator's selected definitions as `manual`.
887
+ * `definitionsFrom` is applied to the fresh fetch so a stale client-side
888
+ * `ModelDefinition` can never decide what gets stored.
889
+ */
890
+ private async persistRoster(
891
+ tenantId: string,
892
+ providerId: string,
893
+ definitionsFrom: (
894
+ fresh: readonly ModelDefinition[],
895
+ ) => readonly ModelDefinition[],
896
+ source?: "manual",
897
+ unsupportedError?: (accountExists: boolean) => GatewayError,
898
+ ): Promise<{ synced: number }> {
899
+ const fresh = await this.discoverModels(tenantId, providerId);
900
+ if (!fresh.supported) {
901
+ if (unsupportedError) {
902
+ throw unsupportedError((await this.usableAccount(tenantId, providerId)) !== undefined);
854
903
  }
855
- // A credential exists and the provider still has no listing path: the
856
- // catalog is the compiled one, and new models arrive with an update.
857
904
  throw new GatewayError(
858
905
  "capability_unsupported",
859
906
  400,
@@ -861,9 +908,75 @@ export class ProviderProbingService {
861
908
  {},
862
909
  "cartethyia",
863
910
  );
864
- } finally {
865
- outbound.release?.();
866
911
  }
912
+ const ctx = wireContextFrom(fresh.wire.providerRow);
913
+ return this.persistDiscoveredModels(
914
+ providerId,
915
+ definitionsFrom(fresh.models),
916
+ (modelId) => resolveDiscoveredWire(modelId, ctx, undefined, this.defaultEndpoints),
917
+ ctx.endpointPathsByWireFamily,
918
+ fresh.wire.baseUrl,
919
+ fresh.wire.supportedWireFamilies,
920
+ source,
921
+ );
922
+ }
923
+
924
+ /**
925
+ * Full sync: one fetch, upsert every id as `discovered` (or `auto_free`).
926
+ * Its original prerequisite diagnostics are preserved: a custom provider
927
+ * with no account is told to add one, a bundled provider with no listing
928
+ * endpoint is told its catalog ships with the release.
929
+ */
930
+ async syncModels(tenantId: string, providerId: string): Promise<{ synced: number }> {
931
+ return this.persistRoster(
932
+ tenantId,
933
+ providerId,
934
+ (fresh) => fresh,
935
+ undefined,
936
+ (accountExists) => {
937
+ if (!isBundledProviderId(providerId) || !accountExists) {
938
+ const prefix = isBundledProviderId(providerId)
939
+ ? `Add an account for ${providerId} before fetching its models`
940
+ : `Add an account for ${providerId} first — model listing needs a credential`;
941
+ return new GatewayError(
942
+ "invalid_request",
943
+ 400,
944
+ `${prefix} (request {requestId}). Wrong key, right door — check the credential 🔑`,
945
+ {},
946
+ "cartethyia",
947
+ );
948
+ }
949
+ return new GatewayError(
950
+ "capability_unsupported",
951
+ 400,
952
+ `${providerId} publishes no model-listing endpoint (request {requestId}). Its catalog ships with Cartethyia; new models appear with an update. Know your tools — this one just isn't in the box 🧰`,
953
+ {},
954
+ "cartethyia",
955
+ );
956
+ },
957
+ );
958
+ }
959
+
960
+ /**
961
+ * Persists an operator-selected subset of a freshly fetched roster as
962
+ * `source="manual"` (D28). The store has already intersected the requested
963
+ * ids against its own fetch; this second fetch is the anti-forgery boundary
964
+ * and the source of every stored `ModelDefinition`.
965
+ */
966
+ async commitDiscoveredModels(
967
+ tenantId: string,
968
+ providerId: string,
969
+ selectedIds: readonly string[],
970
+ ): Promise<{ synced: number }> {
971
+ return this.persistRoster(
972
+ tenantId,
973
+ providerId,
974
+ (fresh) => {
975
+ const wanted = new Set(selectedIds);
976
+ return fresh.filter((definition) => wanted.has(definition.modelId));
977
+ },
978
+ "manual",
979
+ );
867
980
  }
868
981
 
869
982
  private async persistDiscoveredModels(
@@ -873,6 +986,7 @@ export class ProviderProbingService {
873
986
  endpointOverrides?: Partial<Record<WireFamily, string>>,
874
987
  baseUrl?: string,
875
988
  supportedWireFamilies?: readonly WireFamily[],
989
+ source?: "manual",
876
990
  ): Promise<{ synced: number }> {
877
991
  // Static definitions are the injected bundled catalog; the probing service
878
992
  // and the console store are both handed the same startup-built map.
@@ -892,6 +1006,10 @@ export class ProviderProbingService {
892
1006
  // rows for the same ids afterwards.
893
1007
  const resolvedPairs: Array<{ modelId: string; endpointPath: string }> = [];
894
1008
  const rows: Array<typeof models.$inferInsert> = [];
1009
+ // A `manual` commit is an operator's subset, not a full roster. The
1010
+ // builtin-id skip that protects sync from creating shadow rows would hide
1011
+ // a legitimately selected operator row, so only sync applies it.
1012
+ const skipBuiltinIds = source === undefined;
895
1013
  // `(modelId, endpointPath)` is the row identity and the upsert's conflict
896
1014
  // target, so a listing that repeats a pair (the bespoke discoveries return
897
1015
  // raw upstream ids) would make a multi-row `ON CONFLICT DO UPDATE` fail with
@@ -900,7 +1018,7 @@ export class ProviderProbingService {
900
1018
  const seenPairs = new Set<string>();
901
1019
  for (const item of discoveredModels) {
902
1020
  const modelId = typeof item === "string" ? item : item.modelId;
903
- if (builtinIds.has(modelId)) continue;
1021
+ if (skipBuiltinIds && builtinIds.has(modelId)) continue;
904
1022
  const resolved = resolve(modelId);
905
1023
  const discDef = typeof item === "string" ? undefined : item;
906
1024
  const { wireFamily, endpointPath } = applyDiscoveredWire({
@@ -927,6 +1045,10 @@ export class ProviderProbingService {
927
1045
  reasoning: knownDefinition?.reasoning ?? discDef?.reasoning ?? false,
928
1046
  toolCall: knownDefinition?.toolCall ?? discDef?.toolCall ?? false,
929
1047
  cost: modelsDevCatalog.costFor(providerId, modelId),
1048
+ // R-E: fetched warnings win, a matching static definition is the
1049
+ // fallback, and an unknown id stores `NULL` (operator-editable).
1050
+ warningMetadata:
1051
+ discDef?.warnings ?? knownDefinition?.warnings ?? null,
930
1052
  // Discovery builds its rows from the same static catalog, so a fetched
931
1053
  // row carries the same answer a seeded one does. Null when neither
932
1054
  // source marked it, which reads as "unknown" rather than "free".
@@ -948,7 +1070,8 @@ export class ProviderProbingService {
948
1070
  // source, because the model list groups on it: a free-tier row is not
949
1071
  // an ordinary fetched one, and collapsing both into `discovered` lost
950
1072
  // that distinction at the only point where it is still known.
951
- source: discDef?.freeTier === true ? "auto_free" : "discovered",
1073
+ source:
1074
+ source ?? (discDef?.freeTier === true ? "auto_free" : "discovered"),
952
1075
  sourceUpdatedAt,
953
1076
  enabled: true,
954
1077
  ...(discDef?.dialect === undefined && knownDefinition?.dialect === undefined
@@ -975,6 +1098,7 @@ export class ProviderProbingService {
975
1098
  cost: sql`excluded.cost`,
976
1099
  proOnly: sql`excluded.pro_only`,
977
1100
  freeTier: sql`excluded.free_tier`,
1101
+ warningMetadata: sql`excluded.warning_metadata`,
978
1102
  dialect: sql`excluded.dialect`,
979
1103
  // The proposed row's own source, not a constant: a free-tier
980
1104
  // discovery and an ordinary one write the same upsert, and pinning
@@ -986,6 +1110,9 @@ export class ProviderProbingService {
986
1110
  });
987
1111
  }
988
1112
  const synced = rows.length;
1113
+ // A manual commit is an operator's selection, not a roster replacement:
1114
+ // pruning would delete unrelated discovered rows with the same ids.
1115
+ if (source === "manual") return { synced };
989
1116
  // Drop this provider's discovered rows for the synced ids that are no
990
1117
  // longer part of the resolved set — the stale `(model, endpoint)` pair left
991
1118
  // behind when a corrected wire family moved a model to a new path. Rows the
@@ -1015,13 +1142,6 @@ export class ProviderProbingService {
1015
1142
  return { synced };
1016
1143
  }
1017
1144
 
1018
- private async usableAccountSecret(
1019
- tenantId: string,
1020
- providerId: string,
1021
- ): Promise<string | undefined> {
1022
- return (await this.usableAccount(tenantId, providerId))?.secret;
1023
- }
1024
-
1025
1145
  /**
1026
1146
  * The account a credential-scoped discovery will use, with its non-secret
1027
1147
  * auth configuration. Selection is unchanged — first usable row, preferring a
@@ -1030,7 +1150,11 @@ export class ProviderProbingService {
1030
1150
  private async usableAccount(
1031
1151
  tenantId: string,
1032
1152
  providerId: string,
1033
- ): Promise<{ readonly secret: string; readonly authState?: Readonly<Record<string, unknown>> } | undefined> {
1153
+ ): Promise<{
1154
+ readonly secret: string;
1155
+ readonly label?: string;
1156
+ readonly authState?: Readonly<Record<string, unknown>>;
1157
+ } | undefined> {
1034
1158
  const rows = await this.db
1035
1159
  .select()
1036
1160
  .from(providerAccounts)
@@ -1045,6 +1169,7 @@ export class ProviderProbingService {
1045
1169
  const authState = isRecord(usable.authState) ? usable.authState as Readonly<Record<string, unknown>> : undefined;
1046
1170
  return {
1047
1171
  secret: decryptCredentialToString(usable.credentialCiphertext),
1172
+ ...(typeof usable.label === "string" ? { label: usable.label } : {}),
1048
1173
  ...(authState === undefined ? {} : { authState }),
1049
1174
  };
1050
1175
  }
@@ -1056,6 +1181,9 @@ export class ProviderProbingService {
1056
1181
  * carries its model route and service kind so native services never fall
1057
1182
  * through to an LLM probe.
1058
1183
  *
1184
+ * `mode` flows through every request, so "Test all models" (D43) can use
1185
+ * the cheap `fast` path end to end.
1186
+ *
1059
1187
  * Sliding window beats barrel-batching here: the historical pattern waited
1060
1188
  * for the slowest probe in each group of `N` before starting the next group,
1061
1189
  * so one slow route stalled nine others. With the worker loop, a worker