cartethyia 2.7.8 → 2.7.9

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 (371) hide show
  1. package/.env.example +0 -1
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-ixwgKk53.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-BmttNV3U.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-ChJuNNVN.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-BZAJtPg1.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BJ87Pp0U.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  33. package/dist/dashboard/assets/{QuotaPage-CMJKNYWb.js → QuotaPage-gkSyRAKa.js} +1 -1
  34. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-CFNcATJJ.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-YoD4tY5l.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  45. package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
  46. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  47. package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
  48. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-DdbT_7Am.js} +1 -1
  50. package/dist/dashboard/assets/badge-DdbT_7Am.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-CsGN-nbm.js} +1 -1
  54. package/dist/dashboard/assets/banner-CsGN-nbm.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-mgZ_TLLZ.js} +1 -1
  64. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CJ4Zty89.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-B5L2jPQ_.js} +1 -1
  70. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-DkjUouAj.js} +1 -1
  72. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  73. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-B-LVLV6o.js} +1 -1
  74. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  75. package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
  76. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
  78. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-CrsdLVsn.js} +1 -1
  80. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-BU64Lqvp.js} +2 -2
  82. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-B2aDLyyg.js} +1 -1
  84. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-BCVkdR9d.js} +1 -1
  86. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-DNcAdMK-.js} +1 -1
  88. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-DgrIH0hw.js} +1 -1
  90. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-Cae01iu8.js} +1 -1
  92. package/dist/dashboard/assets/live-Cae01iu8.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-CRib7H_2.js} +1 -1
  96. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-BKQE8Xpz.js} +1 -1
  98. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-ecPvG_8g.js} +1 -1
  100. package/dist/dashboard/assets/pagination-ecPvG_8g.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/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  106. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  107. package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
  108. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  109. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-ChoFWyKz.js} +1 -1
  110. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  111. package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
  112. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DWCwU3fh.js} +1 -1
  114. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-Cg3VL5J_.js} +1 -1
  116. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  117. package/dist/dashboard/assets/story/25817331.webp +0 -0
  118. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  119. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  120. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  121. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  122. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  123. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  124. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  125. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-Db70BMix.js} +1 -1
  126. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  127. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
  128. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  129. package/dist/dashboard/console-backgrounds.webp +0 -0
  130. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  131. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  132. package/dist/dashboard/index.html +3 -3
  133. package/dist/dashboard/index.html.gz +0 -0
  134. package/dist/dashboard/precompressed-assets.json +57 -55
  135. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  136. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  137. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  138. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  139. package/package.json +1 -1
  140. package/src/boot/dependencies.ts +11 -3
  141. package/src/config/cooldowns.ts +1 -1
  142. package/src/config/spec.ts +0 -6
  143. package/src/console/api-keys/routes.ts +11 -4
  144. package/src/console/console-router.ts +7 -0
  145. package/src/console/dashboard-assets.ts +5 -5
  146. package/src/console/domain-registration.ts +27 -0
  147. package/src/console/ip-bans/routes.ts +99 -0
  148. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  149. package/src/console/providers/catalog/contracts.ts +123 -0
  150. package/src/console/providers/catalog/model-operations.ts +122 -10
  151. package/src/console/providers/catalog/provider-operations.ts +116 -8
  152. package/src/console/providers/catalog/public-model-store.ts +0 -0
  153. package/src/console/providers/catalog/routes.ts +64 -4
  154. package/src/console/providers/catalog/store-helpers.ts +30 -1
  155. package/src/console/providers/catalog/store.ts +243 -3
  156. package/src/console/quota/account-quota-global.ts +89 -1
  157. package/src/console/quota/native-identity.ts +2 -0
  158. package/src/console/settings/contracts.ts +18 -0
  159. package/src/console/settings/store.ts +32 -0
  160. package/src/console/system-prompt/contracts.ts +0 -1
  161. package/src/main.ts +1 -0
  162. package/src/observability/event-query.ts +18 -4
  163. package/src/persistence/dialect.ts +12 -0
  164. package/src/persistence/schema.ts +20 -2
  165. package/src/protocol/dsml.ts +1 -1
  166. package/src/protocol/gateway-error.ts +1 -0
  167. package/src/protocol/reasoning.ts +1 -1
  168. package/src/protocol/request/chat.ts +4 -4
  169. package/src/protocol/request/messages.ts +1 -1
  170. package/src/protocol/response/gemini.ts +6 -1
  171. package/src/protocol/sse-decoder.ts +25 -8
  172. package/src/protocol/stream-error-frames.ts +13 -3
  173. package/src/protocol/translation/quirks.ts +1 -1
  174. package/src/protocol/translation/tool-repair.ts +2 -2
  175. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  176. package/src/protocol/upstream-parsing.ts +1 -1
  177. package/src/providers/adapters/compatible-adapter.ts +7 -5
  178. package/src/providers/authentication/oauth-refresh-service.ts +149 -36
  179. package/src/providers/default-registry.ts +68 -3
  180. package/src/providers/discovery/discovery-types.ts +12 -0
  181. package/src/providers/discovery/probe-phases.ts +61 -30
  182. package/src/providers/discovery/probing-service.ts +279 -164
  183. package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
  184. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  185. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  186. package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
  187. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  188. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  189. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  190. package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
  191. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  192. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  193. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  194. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  195. package/src/providers/integrations/buddy/capabilities.ts +4 -41
  196. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  197. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
  198. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
  199. package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
  200. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  201. package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
  202. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  203. package/src/providers/integrations/cursor/cursor-request.ts +9 -8
  204. package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
  205. package/src/providers/integrations/cursor/cursor.ts +50 -30
  206. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  207. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  208. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  209. package/src/providers/integrations/devin/devin-http.ts +91 -0
  210. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  211. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  212. package/src/providers/integrations/devin/devin.ts +539 -0
  213. package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
  214. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  215. package/src/providers/integrations/factory/factory.ts +2 -2
  216. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  217. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  218. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  219. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  220. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  221. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  222. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  223. package/src/providers/integrations/freepi/freepi.ts +292 -0
  224. package/src/providers/integrations/gemini.ts +2 -2
  225. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  226. package/src/providers/integrations/hermes.ts +4 -12
  227. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  228. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  229. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  230. package/src/providers/integrations/perch/perch-protocol.ts +211 -0
  231. package/src/providers/integrations/perch/perch-quota.ts +137 -0
  232. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  233. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  234. package/src/providers/integrations/perch/perch.ts +315 -0
  235. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  236. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  237. package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
  238. package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
  239. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  240. package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
  241. package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
  242. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -6
  243. package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
  244. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  245. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  246. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  247. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  248. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  249. package/src/providers/integrations/qoder/qoder.ts +3 -6
  250. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  251. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  252. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  253. package/src/providers/integrations/trae/trae-protocol.ts +3 -6
  254. package/src/providers/integrations/trae/trae-quota.ts +103 -21
  255. package/src/providers/integrations/trae/trae-realm.ts +36 -15
  256. package/src/providers/integrations/trae/trae.ts +97 -7
  257. package/src/providers/operations/account-error-classification.ts +66 -1
  258. package/src/providers/operations/account-health-service.ts +192 -26
  259. package/src/providers/operations/account-refresh-status.ts +284 -0
  260. package/src/providers/operations/account-tier.ts +67 -0
  261. package/src/providers/operations/client-versions.ts +37 -99
  262. package/src/providers/operations/cooldown-rules.ts +61 -0
  263. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  264. package/src/providers/provider-contracts.ts +29 -0
  265. package/src/providers/provider-metadata.ts +59 -4
  266. package/src/providers/provider-model-definition.ts +36 -0
  267. package/src/providers/quota/refresh.ts +21 -0
  268. package/src/security/abuse.ts +71 -0
  269. package/src/transport/dispatch/proxy-request.ts +1 -0
  270. package/src/transport/dispatch/streaming/stream.ts +1 -0
  271. package/src/transport/dispatch/upstream.ts +4 -1
  272. package/src/transport/failure-policy.ts +113 -7
  273. package/src/transport/routing/router.ts +25 -1
  274. package/src/transport/surface/messages/adapter.ts +2 -1
  275. package/src/transport/surface/responses/encode.ts +12 -1
  276. package/src/workers/daily-checkin.ts +1 -1
  277. package/src/workers/quota-refresh-sweep.ts +1 -1
  278. package/src/workers/scheduler.ts +22 -4
  279. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  280. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  281. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  282. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  283. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  284. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  285. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  286. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  287. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  288. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  289. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  290. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  291. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  292. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  293. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  294. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  295. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  296. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  297. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  298. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  299. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  300. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  301. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  302. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  303. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  304. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  305. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  306. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  307. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  308. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  309. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  310. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  311. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  312. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  313. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  314. package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
  315. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  316. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  317. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  318. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  319. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  320. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  321. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  322. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  323. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  324. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  325. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  326. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  327. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  328. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  329. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  330. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  331. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  332. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  333. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  334. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  335. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  336. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  337. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  338. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  339. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  340. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  341. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  342. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  343. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  344. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  345. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  346. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  347. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  348. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  349. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  350. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  351. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  352. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  353. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  354. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  355. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  356. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  357. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  358. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  359. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  360. package/dist/dashboard/console-backgrounds.jpg +0 -0
  361. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  362. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  363. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  364. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  365. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  366. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  367. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  368. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  369. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  370. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-B7kIWFm5.js} +0 -0
  371. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-B7kIWFm5.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,
@@ -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,29 @@ 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
+ ...(typeof adapter?.systemPrompt === "function"
267
+ ? { providerSystemPrompt: adapter.systemPrompt() }
268
+ : {}),
262
269
  });
263
270
  let capturedRequest = canonicalRequest;
264
271
  const parsedProviderId = parseProviderId(providerId);
@@ -282,7 +289,6 @@ export class ProviderProbingService {
282
289
  : { probe_surface: normalizedRequest.surface }),
283
290
  };
284
291
 
285
- const probeSignal = AbortSignal.timeout(30_000);
286
292
  const events: CanonicalEvent[] = [];
287
293
  /**
288
294
  * Populated when the probe dials. Declared here, not inside the try, so the
@@ -337,7 +343,7 @@ export class ProviderProbingService {
337
343
  candidate,
338
344
  {
339
345
  credential,
340
- deadline: startedAt + 30_000,
346
+ deadline: startedAt + probeTimeoutMs,
341
347
  abort_signal: probeSignal,
342
348
  outbound_fetch: probeFetch,
343
349
  },
@@ -373,7 +379,7 @@ export class ProviderProbingService {
373
379
  candidate,
374
380
  {
375
381
  credential,
376
- deadline: startedAt + 30_000,
382
+ deadline: startedAt + probeTimeoutMs,
377
383
  abort_signal: probeSignal,
378
384
  outbound_fetch: probeFetch,
379
385
  },
@@ -415,7 +421,7 @@ export class ProviderProbingService {
415
421
  capturedRequest = request;
416
422
  for await (const event of adapter.dispatch(request, candidate, {
417
423
  credential,
418
- deadline: startedAt + 30_000,
424
+ deadline: startedAt + probeTimeoutMs,
419
425
  abort_signal: probeSignal,
420
426
  outbound_fetch: probeFetch,
421
427
  // A bidi-only provider (Cursor's Connect-RPC Run) has no `fetch`
@@ -433,7 +439,8 @@ export class ProviderProbingService {
433
439
  if (!hasMeaningfulOutput(events)) throw new Error("probe response content empty");
434
440
  } catch (error) {
435
441
  const message = error instanceof Error ? error.message : String(error);
436
- if (!/empty\s+(?:response\s+)?content|response\s+content\s+empty/i.test(message)) {
442
+ // `fast` is deliberately single-shot: no 1024-token streamed retry.
443
+ if (fast || !/empty\s+(?:response\s+)?content|response\s+content\s+empty/i.test(message)) {
437
444
  throw error;
438
445
  }
439
446
  // Some reasoning models spend the short probe budget before producing
@@ -526,26 +533,40 @@ export class ProviderProbingService {
526
533
  latencyMs,
527
534
  });
528
535
 
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
- });
536
+ // Probe telemetry rides the same batch buffer as live traffic. `fast`
537
+ // keeps only the liveness row — no token-speed/usage/TTFB detail — so a
538
+ // "test all models" run stays cheap; `detail` is unchanged.
539
+ if (fast) {
540
+ this.telemetryBuffer?.enqueue({
541
+ tenantId,
542
+ requestId,
543
+ sourceSurface,
544
+ requestedModel: modelId,
545
+ endpoint: endpointPath,
546
+ providerId,
547
+ latencyMs,
548
+ stream: false,
549
+ status: ok ? "completed" : "failed",
550
+ });
551
+ } else {
552
+ this.telemetryBuffer?.enqueue({
553
+ tenantId,
554
+ requestId,
555
+ sourceSurface,
556
+ requestedModel: modelId,
557
+ endpoint: endpointPath,
558
+ providerId,
559
+ ...(accountId ? { accountId } : {}),
560
+ ...(networkPoolId ? { networkPoolId } : {}),
561
+ latencyMs,
562
+ ...(ttfbMs !== undefined ? { ttfbMs } : {}),
563
+ stream: capturedRequest.stream,
564
+ status: ok ? "completed" : "failed",
565
+ ...(ok ? {} : { errorCategory: errorMessage ?? "probe_failed" }),
566
+ ...(usage ? { usage } : {}),
567
+ ...(tokensPerSec !== undefined ? { tokensPerSec } : {}),
568
+ });
569
+ }
549
570
  if (this.telemetryBuffer && payloadCapture.enabled) {
550
571
  if (payloadCapture.mode === "metadata") {
551
572
  // Stage presence only: the provider request line (method, URL,
@@ -596,7 +617,13 @@ export class ProviderProbingService {
596
617
  }
597
618
  }
598
619
 
599
- const sample = extractSample(events);
620
+ const rawSample = extractSample(events);
621
+ // `fast` answers one question — did the model talk back? — so an 80-char
622
+ // excerpt is enough; `detail` keeps the full sample verbatim (D40).
623
+ const sample =
624
+ rawSample === undefined || !fast
625
+ ? rawSample
626
+ : rawSample.slice(0, PROBE_FAST_SAMPLE_MAX_CHARS);
600
627
  // The HTTP-only `readReportedModel` covers response-body transports; a
601
628
  // non-HTTP transport (Qoder gRPC) reports the upstream echo as a
602
629
  // `response_start` event instead — fall back to that when the body read
@@ -611,11 +638,11 @@ export class ProviderProbingService {
611
638
  ...(accountId ? { accountId } : {}),
612
639
  ok,
613
640
  latencyMs,
614
- ...(ttfbMs !== undefined ? { ttfbMs } : {}),
641
+ ...(fast || ttfbMs === undefined ? {} : { ttfbMs }),
615
642
  ...(upstreamStatusCode === undefined ? {} : { statusCode: upstreamStatusCode }),
616
643
  ...(sample ? { sample } : {}),
617
- ...(reportedModel === undefined ? {} : { reportedModel }),
618
- ...(providerMetadata === undefined || Object.keys(providerMetadata).length === 0
644
+ ...(fast || reportedModel === undefined ? {} : { reportedModel }),
645
+ ...(fast || providerMetadata === undefined || Object.keys(providerMetadata).length === 0
619
646
  ? {}
620
647
  : { providerMetadata }),
621
648
  ...(searchResults !== undefined && searchResults.length > 0 ? { searchResults } : {}),
@@ -717,38 +744,54 @@ export class ProviderProbingService {
717
744
  }
718
745
  }
719
746
 
720
- async syncModels(tenantId: string, providerId: string): Promise<{ synced: number }> {
747
+ /**
748
+ * Fetches one provider's model listing through the exact same two branches
749
+ * `syncModels` uses — a registry resolver, or the generic OpenAI-compatible
750
+ * `/models` fetch for BYOK — without writing anything. `supported:false`
751
+ * names a provider with no listing path so the Add-Model UI can offer its
752
+ * manual fallback instead of reporting a transport failure.
753
+ *
754
+ * The account is the first usable one (`selectUsableAccount`), the same
755
+ * selection sync uses (D27); its label is returned when available so the
756
+ * surface can say whose roster it is showing.
757
+ */
758
+ async discoverModels(
759
+ tenantId: string,
760
+ providerId: string,
761
+ ): Promise<{
762
+ readonly supported: boolean;
763
+ readonly models: readonly ModelDefinition[];
764
+ readonly accountLabel?: string;
765
+ /** Wire resolution context + BYOK family constraints for a later commit. */
766
+ readonly wire: {
767
+ readonly providerRow: ProbeProviderRow | undefined;
768
+ readonly supportedWireFamilies: readonly WireFamily[] | undefined;
769
+ readonly baseUrl: string | undefined;
770
+ };
771
+ }> {
721
772
  const outbound = asOutboundBinding(await this.outboundFetchFor(tenantId, providerId));
722
773
  try {
723
774
  const discoveryFetch = asFetchLike(outbound.fetch);
724
775
  const resolver = await this.providerRegistry.resolveModelDiscovery(providerId);
776
+ const account = await this.usableAccount(tenantId, providerId);
777
+ const providerRow = await this.loadProviderWireRow(providerId);
725
778
  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) ?? "";
779
+ // `providerBaseUrl` throws for a non-bundled id; a custom provider can
780
+ // still register a discovery resolver, and its own row's base URL is
781
+ // the authority. Only a bundled provider falls back to metadata.
782
+ const effectiveBaseUrl =
783
+ providerRow?.baseUrl ??
784
+ (isBundledProviderId(providerId) ? providerBaseUrl(providerId) : "");
736
785
  let discovered: readonly ModelDefinition[] | null;
737
786
  try {
738
787
  discovered = await resolver({
739
788
  baseUrl: effectiveBaseUrl,
740
- credential: secret ?? "",
789
+ credential: account?.secret ?? "",
741
790
  ...(discoveryFetch ? { fetcher: discoveryFetch } : {}),
742
791
  ...(account?.authState === undefined ? {} : { authState: account.authState }),
743
792
  });
744
793
  } 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
- );
794
+ throw this.discoveryFailure(providerId, error);
752
795
  }
753
796
  if (!discovered) {
754
797
  throw new GatewayError(
@@ -759,101 +802,92 @@ export class ProviderProbingService {
759
802
  "upstream",
760
803
  );
761
804
  }
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
- );
805
+ return {
806
+ supported: true,
807
+ models: discovered,
808
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
809
+ wire: { providerRow, supportedWireFamilies: undefined, baseUrl: effectiveBaseUrl },
810
+ };
770
811
  }
771
-
772
- const providerRow = await this.loadProviderWireRow(providerId);
773
812
  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
- }
813
+ const wireProfile = resolveByokWireProfile(
814
+ providerRow.wireFamilyDefault,
815
+ providerRow.compatibilityProfile as CompatibilityProfile | null,
816
+ );
817
+ const modelsBaseUrl = modelDiscoveryBaseUrl(providerRow.baseUrl);
818
+ let discovered: readonly ModelDefinition[] | null;
819
+ try {
820
+ discovered = await fetchOpenAICompatibleModels({
821
+ baseUrl: modelsBaseUrl,
822
+ providerId,
823
+ headers: {
824
+ ...authHeaders(
825
+ wireProfile.authHeaderShape,
826
+ new TextEncoder().encode(account?.secret ?? ""),
827
+ "api_key",
828
+ ),
829
+ },
830
+ ...(discoveryFetch ? { fetcher: discoveryFetch as typeof fetch } : {}),
831
+ });
832
+ } catch (error) {
833
+ throw this.discoveryFailure(providerId, error);
834
+ }
835
+ if (discovered && discovered.length > 0) {
836
+ return {
837
+ supported: true,
838
+ models: discovered,
839
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
840
+ wire: {
841
+ providerRow,
842
+ supportedWireFamilies: wireProfile.supportedWireFamilies,
843
+ baseUrl: providerRow.baseUrl ?? undefined,
844
+ },
845
+ };
822
846
  }
823
847
  }
848
+ return {
849
+ supported: false,
850
+ models: [],
851
+ ...(account?.label === undefined ? {} : { accountLabel: account.label }),
852
+ wire: { providerRow, supportedWireFamilies: undefined, baseUrl: undefined },
853
+ };
854
+ } finally {
855
+ outbound.release?.();
856
+ }
857
+ }
824
858
 
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
- );
859
+ /** One upstream-shaped error for both discovery branches. */
860
+ private discoveryFailure(providerId: string, error: unknown): GatewayError {
861
+ return new GatewayError(
862
+ "transport_unavailable",
863
+ 502,
864
+ `Model discovery failed for ${providerId}: ${error instanceof Error ? error.message : "unknown error"} (request {requestId}). The connection ghosted us — come back in a moment 👻`,
865
+ {},
866
+ "upstream",
867
+ );
868
+ }
869
+
870
+ /**
871
+ * The single write path for every discovery result: one fresh fetch, one
872
+ * subset, one source. Sync passes the whole roster as `discovered`; the
873
+ * Add-Model commit passes the operator's selected definitions as `manual`.
874
+ * `definitionsFrom` is applied to the fresh fetch so a stale client-side
875
+ * `ModelDefinition` can never decide what gets stored.
876
+ */
877
+ private async persistRoster(
878
+ tenantId: string,
879
+ providerId: string,
880
+ definitionsFrom: (
881
+ fresh: readonly ModelDefinition[],
882
+ ) => readonly ModelDefinition[],
883
+ source?: "manual",
884
+ unsupportedError?: (accountExists: boolean) => GatewayError,
885
+ ): Promise<{ synced: number }> {
886
+ const fresh = await this.discoverModels(tenantId, providerId);
887
+ if (!fresh.supported) {
888
+ if (unsupportedError) {
889
+ throw unsupportedError((await this.usableAccount(tenantId, providerId)) !== undefined);
854
890
  }
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
891
  throw new GatewayError(
858
892
  "capability_unsupported",
859
893
  400,
@@ -861,9 +895,75 @@ export class ProviderProbingService {
861
895
  {},
862
896
  "cartethyia",
863
897
  );
864
- } finally {
865
- outbound.release?.();
866
898
  }
899
+ const ctx = wireContextFrom(fresh.wire.providerRow);
900
+ return this.persistDiscoveredModels(
901
+ providerId,
902
+ definitionsFrom(fresh.models),
903
+ (modelId) => resolveDiscoveredWire(modelId, ctx, undefined, this.defaultEndpoints),
904
+ ctx.endpointPathsByWireFamily,
905
+ fresh.wire.baseUrl,
906
+ fresh.wire.supportedWireFamilies,
907
+ source,
908
+ );
909
+ }
910
+
911
+ /**
912
+ * Full sync: one fetch, upsert every id as `discovered` (or `auto_free`).
913
+ * Its original prerequisite diagnostics are preserved: a custom provider
914
+ * with no account is told to add one, a bundled provider with no listing
915
+ * endpoint is told its catalog ships with the release.
916
+ */
917
+ async syncModels(tenantId: string, providerId: string): Promise<{ synced: number }> {
918
+ return this.persistRoster(
919
+ tenantId,
920
+ providerId,
921
+ (fresh) => fresh,
922
+ undefined,
923
+ (accountExists) => {
924
+ if (!isBundledProviderId(providerId) || !accountExists) {
925
+ const prefix = isBundledProviderId(providerId)
926
+ ? `Add an account for ${providerId} before fetching its models`
927
+ : `Add an account for ${providerId} first — model listing needs a credential`;
928
+ return new GatewayError(
929
+ "invalid_request",
930
+ 400,
931
+ `${prefix} (request {requestId}). Wrong key, right door — check the credential 🔑`,
932
+ {},
933
+ "cartethyia",
934
+ );
935
+ }
936
+ return new GatewayError(
937
+ "capability_unsupported",
938
+ 400,
939
+ `${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 🧰`,
940
+ {},
941
+ "cartethyia",
942
+ );
943
+ },
944
+ );
945
+ }
946
+
947
+ /**
948
+ * Persists an operator-selected subset of a freshly fetched roster as
949
+ * `source="manual"` (D28). The store has already intersected the requested
950
+ * ids against its own fetch; this second fetch is the anti-forgery boundary
951
+ * and the source of every stored `ModelDefinition`.
952
+ */
953
+ async commitDiscoveredModels(
954
+ tenantId: string,
955
+ providerId: string,
956
+ selectedIds: readonly string[],
957
+ ): Promise<{ synced: number }> {
958
+ return this.persistRoster(
959
+ tenantId,
960
+ providerId,
961
+ (fresh) => {
962
+ const wanted = new Set(selectedIds);
963
+ return fresh.filter((definition) => wanted.has(definition.modelId));
964
+ },
965
+ "manual",
966
+ );
867
967
  }
868
968
 
869
969
  private async persistDiscoveredModels(
@@ -873,6 +973,7 @@ export class ProviderProbingService {
873
973
  endpointOverrides?: Partial<Record<WireFamily, string>>,
874
974
  baseUrl?: string,
875
975
  supportedWireFamilies?: readonly WireFamily[],
976
+ source?: "manual",
876
977
  ): Promise<{ synced: number }> {
877
978
  // Static definitions are the injected bundled catalog; the probing service
878
979
  // and the console store are both handed the same startup-built map.
@@ -892,6 +993,10 @@ export class ProviderProbingService {
892
993
  // rows for the same ids afterwards.
893
994
  const resolvedPairs: Array<{ modelId: string; endpointPath: string }> = [];
894
995
  const rows: Array<typeof models.$inferInsert> = [];
996
+ // A `manual` commit is an operator's subset, not a full roster. The
997
+ // builtin-id skip that protects sync from creating shadow rows would hide
998
+ // a legitimately selected operator row, so only sync applies it.
999
+ const skipBuiltinIds = source === undefined;
895
1000
  // `(modelId, endpointPath)` is the row identity and the upsert's conflict
896
1001
  // target, so a listing that repeats a pair (the bespoke discoveries return
897
1002
  // raw upstream ids) would make a multi-row `ON CONFLICT DO UPDATE` fail with
@@ -900,7 +1005,7 @@ export class ProviderProbingService {
900
1005
  const seenPairs = new Set<string>();
901
1006
  for (const item of discoveredModels) {
902
1007
  const modelId = typeof item === "string" ? item : item.modelId;
903
- if (builtinIds.has(modelId)) continue;
1008
+ if (skipBuiltinIds && builtinIds.has(modelId)) continue;
904
1009
  const resolved = resolve(modelId);
905
1010
  const discDef = typeof item === "string" ? undefined : item;
906
1011
  const { wireFamily, endpointPath } = applyDiscoveredWire({
@@ -927,6 +1032,10 @@ export class ProviderProbingService {
927
1032
  reasoning: knownDefinition?.reasoning ?? discDef?.reasoning ?? false,
928
1033
  toolCall: knownDefinition?.toolCall ?? discDef?.toolCall ?? false,
929
1034
  cost: modelsDevCatalog.costFor(providerId, modelId),
1035
+ // R-E: fetched warnings win, a matching static definition is the
1036
+ // fallback, and an unknown id stores `NULL` (operator-editable).
1037
+ warningMetadata:
1038
+ discDef?.warnings ?? knownDefinition?.warnings ?? null,
930
1039
  // Discovery builds its rows from the same static catalog, so a fetched
931
1040
  // row carries the same answer a seeded one does. Null when neither
932
1041
  // source marked it, which reads as "unknown" rather than "free".
@@ -948,7 +1057,8 @@ export class ProviderProbingService {
948
1057
  // source, because the model list groups on it: a free-tier row is not
949
1058
  // an ordinary fetched one, and collapsing both into `discovered` lost
950
1059
  // that distinction at the only point where it is still known.
951
- source: discDef?.freeTier === true ? "auto_free" : "discovered",
1060
+ source:
1061
+ source ?? (discDef?.freeTier === true ? "auto_free" : "discovered"),
952
1062
  sourceUpdatedAt,
953
1063
  enabled: true,
954
1064
  ...(discDef?.dialect === undefined && knownDefinition?.dialect === undefined
@@ -975,6 +1085,7 @@ export class ProviderProbingService {
975
1085
  cost: sql`excluded.cost`,
976
1086
  proOnly: sql`excluded.pro_only`,
977
1087
  freeTier: sql`excluded.free_tier`,
1088
+ warningMetadata: sql`excluded.warning_metadata`,
978
1089
  dialect: sql`excluded.dialect`,
979
1090
  // The proposed row's own source, not a constant: a free-tier
980
1091
  // discovery and an ordinary one write the same upsert, and pinning
@@ -986,6 +1097,9 @@ export class ProviderProbingService {
986
1097
  });
987
1098
  }
988
1099
  const synced = rows.length;
1100
+ // A manual commit is an operator's selection, not a roster replacement:
1101
+ // pruning would delete unrelated discovered rows with the same ids.
1102
+ if (source === "manual") return { synced };
989
1103
  // Drop this provider's discovered rows for the synced ids that are no
990
1104
  // longer part of the resolved set — the stale `(model, endpoint)` pair left
991
1105
  // behind when a corrected wire family moved a model to a new path. Rows the
@@ -1015,13 +1129,6 @@ export class ProviderProbingService {
1015
1129
  return { synced };
1016
1130
  }
1017
1131
 
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
1132
  /**
1026
1133
  * The account a credential-scoped discovery will use, with its non-secret
1027
1134
  * auth configuration. Selection is unchanged — first usable row, preferring a
@@ -1030,7 +1137,11 @@ export class ProviderProbingService {
1030
1137
  private async usableAccount(
1031
1138
  tenantId: string,
1032
1139
  providerId: string,
1033
- ): Promise<{ readonly secret: string; readonly authState?: Readonly<Record<string, unknown>> } | undefined> {
1140
+ ): Promise<{
1141
+ readonly secret: string;
1142
+ readonly label?: string;
1143
+ readonly authState?: Readonly<Record<string, unknown>>;
1144
+ } | undefined> {
1034
1145
  const rows = await this.db
1035
1146
  .select()
1036
1147
  .from(providerAccounts)
@@ -1045,6 +1156,7 @@ export class ProviderProbingService {
1045
1156
  const authState = isRecord(usable.authState) ? usable.authState as Readonly<Record<string, unknown>> : undefined;
1046
1157
  return {
1047
1158
  secret: decryptCredentialToString(usable.credentialCiphertext),
1159
+ ...(typeof usable.label === "string" ? { label: usable.label } : {}),
1048
1160
  ...(authState === undefined ? {} : { authState }),
1049
1161
  };
1050
1162
  }
@@ -1056,6 +1168,9 @@ export class ProviderProbingService {
1056
1168
  * carries its model route and service kind so native services never fall
1057
1169
  * through to an LLM probe.
1058
1170
  *
1171
+ * `mode` flows through every request, so "Test all models" (D43) can use
1172
+ * the cheap `fast` path end to end.
1173
+ *
1059
1174
  * Sliding window beats barrel-batching here: the historical pattern waited
1060
1175
  * for the slowest probe in each group of `N` before starting the next group,
1061
1176
  * so one slow route stalled nine others. With the worker loop, a worker