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
@@ -60,6 +60,9 @@ export async function seedBundledModels(
60
60
  // states the tier, so the flag is carried rather than derived.
61
61
  freeTier: definition.freeTier ?? null,
62
62
  dialect: definition.dialect ?? null,
63
+ // Display-only: a static catalog that declares no warnings clears any
64
+ // stale value on the next boot rather than leaving an old banner.
65
+ warningMetadata: definition.warnings ?? null,
63
66
  source: "builtin",
64
67
  sourceUpdatedAt: now,
65
68
  enabled: true,
@@ -90,6 +93,7 @@ export async function seedBundledModels(
90
93
  proOnly: sql`excluded.pro_only`,
91
94
  freeTier: sql`excluded.free_tier`,
92
95
  dialect: sql`excluded.dialect`,
96
+ warningMetadata: sql`excluded.warning_metadata`,
93
97
  source: sql`'builtin'`,
94
98
  sourceUpdatedAt: sql`excluded.source_updated_at`,
95
99
  },
@@ -187,6 +187,13 @@ export interface ProviderDispatchContext {
187
187
  readonly outbound_attachment_fetch?: ValidatedOutboundFetch | undefined;
188
188
  /** Validated bidirectional HTTP/2 egress bound to the same selected network pool. */
189
189
  readonly outbound_http2?: ValidatedOutboundHttp2Bidi | undefined;
190
+ /**
191
+ * Capture sink for one attempt's provider exchange, filled by HTTP fetch
192
+ * wrapping. Adapters whose real upstream transport is not a `fetch`
193
+ * (Qoder's gRPC) write it directly so Request Detail shows the actual
194
+ * conversation call rather than an auth exchange or an empty panel.
195
+ */
196
+ readonly provider_capture?: ProviderCaptureExchange | undefined;
190
197
  /**
191
198
  * Route-selected fallback User-Agent from Routing Strategy; set when the
192
199
  * operator configured one (bundled API-key providers default to the Claude
@@ -261,6 +268,20 @@ export function applyRouteUserAgent(
261
268
  }
262
269
 
263
270
  /** ProviderDispatchTarget identity already resolved by routing before credential leasing. */
271
+ /**
272
+ * Capture sink for one attempt's provider exchange.
273
+ *
274
+ * HTTP transports fill this automatically by wrapping `outbound_fetch` (see
275
+ * `captureProviderExchange` in `transport/dispatch/attempt-finalize.ts`).
276
+ * Adapters whose real upstream call is not a fetch — Qoder's gRPC surface —
277
+ * write it directly so Request Detail shows the actual conversation call
278
+ * instead of an auth exchange or nothing.
279
+ */
280
+ export interface ProviderCaptureExchange {
281
+ request: unknown;
282
+ response: Promise<unknown>;
283
+ }
284
+
264
285
  export interface ProviderDispatchTarget {
265
286
  readonly provider_id: ProviderId;
266
287
  readonly model_id: string;
@@ -287,6 +308,14 @@ export interface ProviderAdapter {
287
308
  candidate: ProviderDispatchTarget,
288
309
  context: ProviderDispatchContext,
289
310
  ): AsyncIterable<CanonicalEvent>;
311
+ /**
312
+ * Optional provider-mandated system prompt, returned for probes and any
313
+ * caller that needs the upstream's required identity prompt. A provider
314
+ * whose wire refuses requests without a specific system prompt (Perch's
315
+ * Saffron fingerprint) declares it here so the probe path can carry it —
316
+ * the probe builder otherwise sends only the user message.
317
+ */
318
+ systemPrompt?(): string | undefined;
290
319
  /**
291
320
  * Optional native-protocol capability for the public `POST /v1/systemone`
292
321
  * route (the dispatch leg of the System One service, alongside the probing
@@ -32,6 +32,14 @@ export interface CompatibilityProfile {
32
32
  * bundled specs declare it on `ApiKeyProviderSpec.gatewayUserAgent`.
33
33
  */
34
34
  readonly gateway_user_agent?: boolean;
35
+ /**
36
+ * Which service surfaces this custom provider may serve. Absent means every
37
+ * kind is enabled (the historical default). The operator narrows this by
38
+ * checking only the capabilities their upstream actually answers — agentic
39
+ * (LLM) is always on when the list is present, and a kind the upstream
40
+ * cannot serve stays out of the provider's tab set.
41
+ */
42
+ readonly service_kinds?: readonly ServiceKind[];
35
43
  }
36
44
 
37
45
  /** Identity and routing defaults for every builtin provider. */
@@ -144,6 +152,20 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
144
152
  credentialUrl: "https://cursor.com",
145
153
  credentialHint: "Sign in with Cursor or import a Cursor API key.",
146
154
  },
155
+ {
156
+ id: "devin",
157
+ displayName: "Devin",
158
+ baseUrl: "https://server.codeium.com",
159
+ wireFamilyDefault: "chat",
160
+ requiresAccount: true,
161
+ bespokeWire: true,
162
+ hasAdapterUserAgent: true,
163
+ // Devin's upstream is a Connect-RPC protobuf service with its own framing;
164
+ // an egress proxy in front of it breaks the stream shape. Direct only.
165
+ defaultSkipProxy: true,
166
+ credentialUrl: "https://devin.ai",
167
+ credentialHint: "Sign in with Devin, Windsurf Enterprise, or import a Devin session token / Windsurf seat key.",
168
+ },
147
169
  { id: "kimi", displayName: "Kimi Code", baseUrl: "https://api.kimi.com/coding", credentialUrl: "https://platform.moonshot.ai/console/api-keys" },
148
170
  { id: "opencode", displayName: "OpenCode Free", baseUrl: "https://opencode.ai", requiresAccount: false, ipScopedRateLimit: true, hasAdapterUserAgent: true },
149
171
  { id: "opencode-zen", displayName: "OpenCode Zen", baseUrl: "https://opencode.ai", hasAdapterUserAgent: true, credentialUrl: "https://opencode.ai/auth" },
@@ -344,6 +366,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
344
366
  // User-Agent. Letting the operator overwrite it would let them break the
345
367
  // provider, so the console locks the control.
346
368
  hasAdapterUserAgent: true,
369
+ credentialHint: "Paste a Nous Portal API key (portal.nousresearch.com → API keys), or sign in with the Nous Portal device code (OAuth) for the subscription route.",
347
370
  },
348
371
  { id: "bai", displayName: "B.AI", baseUrl: "https://api.b.ai/v1", credentialUrl: "https://b.ai" },
349
372
  {
@@ -367,8 +390,6 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
367
390
  { id: "cline", displayName: "Cline", baseUrl: "https://api.cline.bot/api/v1", hasAdapterUserAgent: true, defaultSkipProxy: true, credentialUrl: "https://app.cline.bot" },
368
391
  { id: "cb", displayName: "CodeBuddy", baseUrl: "https://www.codebuddy.ai/v2", hasAdapterUserAgent: true, serviceKinds: ["llm", "imageGeneration", "videoGeneration"], credentialUrl: "https://www.codebuddy.ai", credentialHint: "Sign in with the same CodeBuddy account you use in the desktop app." },
369
392
  { id: "cbcn", displayName: "CodeBuddy CN", baseUrl: "https://copilot.tencent.com/v2", hasAdapterUserAgent: true, credentialUrl: "https://copilot.tencent.com", credentialHint: "Sign in with the same CodeBuddy China account you use in the desktop app." },
370
- { id: "workbuddy", displayName: "WorkBuddy", baseUrl: "https://www.workbuddy.ai", hasAdapterUserAgent: true, serviceKinds: ["llm", "imageGeneration", "videoGeneration"], credentialUrl: "https://www.workbuddy.ai", credentialHint: "Sign in with the same WorkBuddy account you use in the desktop app." },
371
- { id: "workbuddycn", displayName: "WorkBuddy CN", baseUrl: "https://www.workbuddy.cn", hasAdapterUserAgent: true, serviceKinds: ["llm", "imageGeneration", "videoGeneration"], credentialUrl: "https://www.workbuddy.cn", credentialHint: "Sign in with the same WorkBuddy China account you use in the desktop app." },
372
393
  {
373
394
  // Kilo Code resells the OpenRouter catalog. The base URL carries the API's
374
395
  // path prefix (`/api/openrouter`) and every model row carries only the wire
@@ -432,6 +453,22 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
432
453
  credentialUrl: "https://www.trae.com.cn/",
433
454
  credentialHint: "Paste a Trae CN account export (accessToken + refreshToken) or the raw access token from a Trae China account.",
434
455
  },
456
+ {
457
+ id: "freepi",
458
+ displayName: "Free-pi",
459
+ baseUrl: "https://api.freepi.ai/v1/chat/completions",
460
+ defaultSkipProxy: true,
461
+ credentialUrl: "https://freepi.ai",
462
+ credentialHint: "Sign in with a GitHub device code (free-pi login), or paste a free-pi JWT issued by the backend.",
463
+ },
464
+ {
465
+ id: "perch",
466
+ displayName: "Perch",
467
+ baseUrl: "https://app.perchai.app",
468
+ defaultSkipProxy: true,
469
+ credentialUrl: "https://chat.perchai.app",
470
+ credentialHint: "Sign in with Google or GitHub (Perch account). Starter includes Roost Standard and Standard 1M.",
471
+ },
435
472
  { id: "ollamacloud", displayName: "Ollama Cloud", baseUrl: "https://ollama.com/v1", credentialUrl: "https://ollama.com/settings/keys" },
436
473
  { id: "gemini", displayName: "Google Gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta", serviceKinds: ["llm", "websearch", "imageGeneration"], credentialUrl: "https://aistudio.google.com/app/apikey" },
437
474
  { id: "xiaomipg", displayName: "Xiaomi MiMo (PAYG)", baseUrl: "https://api.xiaomimimo.com/v1", credentialUrl: "https://platform.xiaomimimo.com" },
@@ -472,7 +509,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
472
509
  export type BundledProviderId = (typeof RAW_BUNDLED_PROVIDER_METADATA)[number]["id"];
473
510
 
474
511
  /**
475
- * The Buddy family: CodeBuddy, its China tenant, and WorkBuddy.
512
+ * The Buddy family: CodeBuddy and its China tenant.
476
513
  *
477
514
  * They share one billing and credit system, so several layers need to ask "is
478
515
  * this a Buddy provider" — the health recorder, the request preparer, and the
@@ -480,7 +517,7 @@ export type BundledProviderId = (typeof RAW_BUNDLED_PROVIDER_METADATA)[number]["
480
517
  * get added in one place and silently missed in the others. Declared here
481
518
  * beside the identities it names; the layers keep their own *policies*.
482
519
  */
483
- export const BUDDY_PROVIDER_IDS: ReadonlySet<string> = new Set(["cb", "cbcn", "workbuddy", "workbuddycn"]);
520
+ export const BUDDY_PROVIDER_IDS: ReadonlySet<string> = new Set(["cb", "cbcn"]);
484
521
 
485
522
  /**
486
523
  * Per-provider JWT verification defaults for issued OAuth access tokens. An
@@ -650,6 +687,24 @@ export function providerServiceKinds(providerId: string): readonly ServiceKind[]
650
687
  return SERVICE_KINDS;
651
688
  }
652
689
 
690
+ /**
691
+ * Resolves the service kinds for a custom (BYOK) provider row.
692
+ *
693
+ * The operator may narrow the surface set via `compatibilityProfile.service_kinds`
694
+ * (the Advanced Capabilities checkboxes in the Add Custom Provider modal). Absent,
695
+ * every kind is allowed — the historical default. A present list never drops
696
+ * `llm` (agentic is the floor of a custom provider).
697
+ */
698
+ export function customProviderServiceKinds(
699
+ compatibilityProfile: CompatibilityProfile | null | undefined,
700
+ ): readonly ServiceKind[] {
701
+ const kinds = compatibilityProfile?.service_kinds;
702
+ if (kinds === undefined || kinds.length === 0) return SERVICE_KINDS;
703
+ const set = new Set<ServiceKind>(SERVICE_KINDS.filter((kind) => kinds.includes(kind)));
704
+ set.add("llm");
705
+ return SERVICE_KINDS.filter((kind) => set.has(kind));
706
+ }
707
+
653
708
 
654
709
  /** Canonical builtin base URL used by adapters, discovery, and quota clients. */
655
710
  export function providerBaseUrl(providerId: string): string {
@@ -33,6 +33,37 @@ export const FREE_TIER_COST: ModelCostDefinition = {
33
33
  pricing_model: "pay-per-use",
34
34
  };
35
35
 
36
+ /**
37
+ * Severity of one display-only model warning. `warn` is the default so a
38
+ * catalog that omits severity does not silently render a critical banner.
39
+ */
40
+ export type ModelWarningSeverity = "info" | "warn" | "critical";
41
+
42
+ /** Severity values accepted on the wire, as a runtime tuple. */
43
+ export const MODEL_WARNING_SEVERITIES = ["info", "warn", "critical"] as const;
44
+
45
+ /** Longest accepted warning `kind` / `message`, in UTF-16 code units. */
46
+ export const MODEL_WARNING_KIND_MAX_LENGTH = 64;
47
+ export const MODEL_WARNING_MESSAGE_MAX_LENGTH = 500;
48
+
49
+ /**
50
+ * One display-only warning attached to a model row. `kind` is an open string
51
+ * with three defined values (`fallback_model`, `cache_reliability`,
52
+ * `model_refusal`) so a new category needs no migration. Persisted as
53
+ * `{ warnings: ModelWarning[] }` in `models.warning_metadata`; never read by
54
+ * routing.
55
+ */
56
+ export interface ModelWarning {
57
+ /** Stable machine-readable category; open vocabulary. */
58
+ readonly kind: string;
59
+ /** Operator-facing text. Provider-level claims use "may". */
60
+ readonly message: string;
61
+ /** Defaults to `warn` when absent. */
62
+ readonly severity?: ModelWarningSeverity;
63
+ /** ISO timestamp of the last edit, when known. */
64
+ readonly updatedAt?: string;
65
+ }
66
+
36
67
  /** Canonical static metadata for one provider model. */
37
68
  export interface ModelDefinition {
38
69
  readonly modelId: string;
@@ -118,6 +149,8 @@ export interface ModelDefinition {
118
149
  * turns a gap in our catalog into a claim about someone else's model.
119
150
  */
120
151
  readonly limitsEstimated?: boolean;
152
+ /** Display-only warnings carried from a static catalog into the seeded row. */
153
+ readonly warnings?: readonly ModelWarning[];
121
154
  }
122
155
 
123
156
  /**
@@ -205,6 +238,8 @@ export interface ModelHelperRow {
205
238
  readonly proOnly?: boolean;
206
239
  /** Explicit wire dialect override for this row. See `ModelDefinition.dialect`. */
207
240
  readonly dialect?: DialectId;
241
+ /** Display-only warnings to carry onto the built row. */
242
+ readonly warnings?: readonly ModelWarning[];
208
243
  }
209
244
 
210
245
  function defaultEndpoint(wireFamily: ModelDefinition["wireFamily"]): string {
@@ -309,6 +344,7 @@ export function defineModel(row: ModelHelperRow): ModelDefinition {
309
344
  ...(row.proOnly === undefined ? {} : { proOnly: row.proOnly }),
310
345
  ...(row.dialect === undefined ? {} : { dialect: row.dialect }),
311
346
  ...(limitsEstimated ? { limitsEstimated: true } : {}),
347
+ ...(row.warnings === undefined ? {} : { warnings: row.warnings }),
312
348
  };
313
349
  }
314
350
 
@@ -11,6 +11,7 @@ import type { CartethyiaDatabase } from "../../persistence/db-handle";
11
11
  import { providerAccounts } from "../../persistence/schema";
12
12
  import type { RedisClient } from "../../persistence/redis";
13
13
  import { log } from "../../observability/logger";
14
+ import { pushStructuredConsoleLog } from "../../observability/log-ring";
14
15
  import { fetchProviderQuota } from "./quota-support";
15
16
  import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
16
17
  import type { ProviderId } from "../provider-contracts";
@@ -261,6 +262,12 @@ const TARGET_COLUMNS = {
261
262
  * disabled account is not necessarily revoked — it may have been parked for a
262
263
  * reason unrelated to its credential — and skipping it would let a credential
263
264
  * die silently while the row still reads healthy.
265
+ *
266
+ * Invariant (area 04, D21): this sweep **never flips status**. The refresh it
267
+ * kicks off rewrites only credential/expiry columns; `recordAccountCheck` is
268
+ * active-only, so `disabled` rows are identical after the pass to before. The
269
+ * `origin: "sweep:quota"` tag on the token-refresh log lines (D26) is what
270
+ * lets an operator verify that in the Console Log.
264
271
  */
265
272
  export async function listQuotaRefreshTargets(
266
273
  db: CartethyiaDatabase,
@@ -343,6 +350,20 @@ async function runQuotaRefresh(
343
350
  let resolveError: string | undefined;
344
351
  try {
345
352
  credential = await deps.resolveCredential(target.providerId as ProviderId, target.accountId);
353
+ // D26: every OAuth credential this sweep resolves may have been refreshed
354
+ // inside the refresh-aware resolver. Label that refresh with its origin so
355
+ // the Console Log can prove the sweep touches disabled accounts without
356
+ // ever flipping their status. The refresh service itself also logs the
357
+ // actual token refresh under the `sweep` origin family; this line names
358
+ // the sweep here, at the source of the trigger.
359
+ if (credential !== "" && target.credentialKind === "oauth") {
360
+ pushStructuredConsoleLog("debug", "Quota sweep resolved OAuth credential; token may have been refreshed", {
361
+ event: "token_refresh",
362
+ accountId: target.accountId,
363
+ providerId: target.providerId,
364
+ details: { origin: "sweep:quota" },
365
+ });
366
+ }
346
367
  } catch (error) {
347
368
  resolveError = error instanceof Error ? error.message : "Failed to resolve credential";
348
369
  }
@@ -113,6 +113,15 @@ export interface IpAbuseStore {
113
113
  * bounded `admission_unavailable` with zero dispatches.
114
114
  */
115
115
  checkAndRecord(attempt: IpAbuseAttempt): Promise<IpAbuseOutcome>;
116
+ /**
117
+ * Manually bans an identity for `durationMs` (0/negative = permanent).
118
+ * Throws when the store is unreachable.
119
+ */
120
+ ban(identity: string, durationMs: number): Promise<void>;
121
+ /** Lifts a manual (or any) ban on an identity. No-op when not banned. */
122
+ unban(identity: string): Promise<void>;
123
+ /** Lists currently active bans with their expiry (`null` = permanent). */
124
+ listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]>;
116
125
  }
117
126
 
118
127
  interface IpAbuseProtectionServiceOptions {
@@ -293,6 +302,34 @@ export class InMemoryIpAbuseStore implements IpAbuseStore {
293
302
  return true;
294
303
  }
295
304
 
305
+ async ban(identity: string, durationMs: number): Promise<void> {
306
+ this.assertAvailable();
307
+ this.evictOldest(this.bans);
308
+ this.bans.set(identity, durationMs > 0 ? this.clock() + durationMs : Number.POSITIVE_INFINITY);
309
+ }
310
+
311
+ async unban(identity: string): Promise<void> {
312
+ this.assertAvailable();
313
+ this.bans.delete(identity);
314
+ }
315
+
316
+ async listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]> {
317
+ this.assertAvailable();
318
+ const now = this.clock();
319
+ const out: { identity: string; bannedUntil: number | null }[] = [];
320
+ for (const [identity, until] of this.bans) {
321
+ if (until <= now) {
322
+ this.bans.delete(identity);
323
+ continue;
324
+ }
325
+ out.push({
326
+ identity,
327
+ bannedUntil: Number.isFinite(until) ? until : null,
328
+ });
329
+ }
330
+ return out;
331
+ }
332
+
296
333
  async checkAndRecord(attempt: IpAbuseAttempt): Promise<IpAbuseOutcome> {
297
334
  this.assertAvailable();
298
335
  const { identity, route, now, limit, banThreshold, banDurationMs } = attempt;
@@ -582,5 +619,39 @@ export class RedisIpAbuseStore implements IpAbuseStore {
582
619
  );
583
620
  return decodeAbuseOutcome(raw);
584
621
  }
622
+
623
+ async ban(identity: string, durationMs: number): Promise<void> {
624
+ const marker = this.banMarkerKey(identity);
625
+ const now = Date.now();
626
+ if (durationMs > 0) {
627
+ await this.redis.set(marker, String(now + durationMs), "PX", durationMs);
628
+ } else {
629
+ // Permanent: no TTL; a large sentinel far in the future.
630
+ await this.redis.set(marker, String(Number.MAX_SAFE_INTEGER));
631
+ }
632
+ }
633
+
634
+ async unban(identity: string): Promise<void> {
635
+ await this.redis.del(this.banMarkerKey(identity));
636
+ }
637
+
638
+ async listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]> {
639
+ const keys = await this.redis.keys("cartethyia:ip:ban:*");
640
+ const now = Date.now();
641
+ const out: { identity: string; bannedUntil: number | null }[] = [];
642
+ for (const key of keys) {
643
+ const value = await this.redis.get(key);
644
+ const until = Number(value ?? 0);
645
+ if (!Number.isFinite(until) || until <= now) {
646
+ await this.redis.del(key);
647
+ continue;
648
+ }
649
+ out.push({
650
+ identity: key.slice("cartethyia:ip:ban:".length),
651
+ bannedUntil: until >= Number.MAX_SAFE_INTEGER - 1 ? null : until,
652
+ });
653
+ }
654
+ return out;
655
+ }
585
656
  }
586
657
 
@@ -263,6 +263,7 @@ export async function handleProviderProxyRequest(
263
263
  ...(conversationAffinity ? { conversationAffinity } : {}),
264
264
  ...(tenantSystemPromptText === undefined ? {} : { tenantSystemPrompt: tenantSystemPromptText }),
265
265
  ...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
266
+ ...(providerCapture ? { providerCapture } : {}),
266
267
  ...(deps.networkBindingFactory
267
268
  ? {
268
269
  outboundFetch: captureProviderExchange(
@@ -249,6 +249,7 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
249
249
  : {}),
250
250
  ...(leaseOwnership === undefined ? {} : { leaseOwnership }),
251
251
  ...(continuationScope === undefined ? {} : { continuationScope }),
252
+ ...(providerCapture ? { providerCapture } : {}),
252
253
  });
253
254
  // `ProviderAdapter.dispatch` is declared to return a non-nullable
254
255
  // `AsyncIterable`, so an adapter that returned `undefined` would be a
@@ -1,6 +1,6 @@
1
1
  import type { CanonicalEvent } from "../../protocol/canonical-model";
2
2
  import type { AttemptLeaseOwnership, ValidatedOutboundFetch, ValidatedOutboundHttp2Bidi } from "../../providers/provider-contracts";
3
- import type { ProviderContinuationScope } from "../../providers/provider-contracts";
3
+ import type { ProviderCaptureExchange, ProviderContinuationScope } from "../../providers/provider-contracts";
4
4
  import { GATEWAY_SECURITY_HEADERS } from "../../security/outbound-headers";
5
5
 
6
6
  const FORWARDED_REQUEST_HEADERS = new Set([
@@ -42,6 +42,8 @@ export function buildUpstreamDispatchContext(input: {
42
42
  readonly requestId?: string;
43
43
  readonly userAgent?: string;
44
44
  readonly outboundFetch?: ValidatedOutboundFetch;
45
+ /** Capture sink for provider exchanges not visible to the HTTP fetch wrapper. */
46
+ readonly providerCapture?: ProviderCaptureExchange;
45
47
  /** Validated attachment egress that must not be wrapped by provider payload capture. */
46
48
  readonly outboundAttachmentFetch?: ValidatedOutboundFetch;
47
49
  /** Validated bidirectional HTTP/2 egress bound to the same selected pool. */
@@ -100,6 +102,7 @@ export function buildUpstreamDispatchContext(input: {
100
102
  ...(userAgent ? { user_agent: userAgent } : {}),
101
103
  ...(Object.keys(input.headers).length > 0 ? { request_headers: input.headers } : {}),
102
104
  ...(outboundFetch ? { outbound_fetch: outboundFetch } : {}),
105
+ ...(input.providerCapture ? { provider_capture: input.providerCapture } : {}),
103
106
  ...(input.outboundAttachmentFetch
104
107
  ? { outbound_attachment_fetch: input.outboundAttachmentFetch }
105
108
  : {}),
@@ -14,6 +14,7 @@ import { resolveAccountQuotaCooldownMs } from "../config/cooldowns";
14
14
  * runs a few KB; 4000 keeps the whole thing while bounding a hostile body.
15
15
  */
16
16
  const MAX_UPSTREAM_ERROR_BYTES = 4000;
17
+ export { MAX_UPSTREAM_ERROR_BYTES };
17
18
  export {
18
19
  parseRetryAfter,
19
20
  parseRateLimitReset,
@@ -22,6 +23,32 @@ export {
22
23
  parseProviderResetDuration,
23
24
  parseAbsoluteResetTimestamp,
24
25
  } from "../protocol/upstream-parsing";
26
+ /**
27
+ * Extracts only the human-readable text from an upstream error body —
28
+ * `message`/`msg`/`error` fields — deliberately skipping `code`/identifier
29
+ * values. Used by `upstreamMessageAsIs` to decide whether the upstream
30
+ * actually supplied a message: a symbolic code like `"permission_error"` is
31
+ * an identifier, not prose, so it must not count as passthrough text.
32
+ */
33
+ export function extractUpstreamMessageText(body: unknown): string {
34
+ let raw = "";
35
+ if (typeof body === "string") raw = body;
36
+ else if (isRecord(body)) {
37
+ if (typeof body.error === "string" && body.error.trim()) raw = body.error;
38
+ else {
39
+ const envelope = isRecord(body.error) ? body.error : body;
40
+ const message = (envelope as Record<string, unknown>).message;
41
+ if (typeof message === "string" && message.trim()) raw = message;
42
+ else if (typeof body.msg === "string" && body.msg.trim()) raw = body.msg;
43
+ else if (typeof (envelope as Record<string, unknown>).msg === "string" && String((envelope as Record<string, unknown>).msg).trim())
44
+ raw = String((envelope as Record<string, unknown>).msg);
45
+ else if (typeof (envelope as Record<string, unknown>).error === "string" && String((envelope as Record<string, unknown>).error).trim())
46
+ raw = String((envelope as Record<string, unknown>).error);
47
+ }
48
+ }
49
+ return raw.replace(/[\r\n]+/g, " ").trim();
50
+ }
51
+
25
52
  /** Extracts a bounded, newline-free provider message from any error envelope. */
26
53
  export function extractUpstreamMessage(body: unknown): string {
27
54
  let raw = "";
@@ -696,6 +723,25 @@ const AUTH_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
696
723
  "insufficient_key_scope",
697
724
  "model_not_allowed_for_key",
698
725
  ]);
726
+
727
+ /**
728
+ * Qoder job-token verdicts that are NOT credential evidence.
729
+ *
730
+ * A PAT (`pt-…`) is exchanged for a short-lived job token (`jt-…`). When the
731
+ * job token expires mid-flight the upstream answers `{"code":"105","message":
732
+ * "Login expired"}` (403). The PAT is still valid — re-exchanging it mints a
733
+ * fresh job token — so treating this as `auth_invalidated` parked a healthy
734
+ * account as "Re-login required". It stays `authentication_failed` (retryable;
735
+ * the next attempt re-runs the PAT exchange) but without `credentialEvidence`,
736
+ * so the account is never disabled or cooled on it.
737
+ */
738
+ const QODER_JOB_TOKEN_EXPIRED: ReadonlySet<string> = new Set([
739
+ "105",
740
+ "login expired",
741
+ "login_expired",
742
+ "job token expired",
743
+ "jobtoken expired",
744
+ ]);
699
745
  const PLATFORM_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
700
746
  "server_error",
701
747
  "internal_error",
@@ -809,6 +855,16 @@ export function classifyUpstreamError(
809
855
  origin: "upstream",
810
856
  rateLimitScope: "provider",
811
857
  };
858
+ // Qoder job-token expiry (`105` / "Login expired") is recoverable by
859
+ // re-exchanging the PAT — never credential evidence (see
860
+ // `QODER_JOB_TOKEN_EXPIRED`).
861
+ if (normalized !== undefined && QODER_JOB_TOKEN_EXPIRED.has(normalized)) {
862
+ return {
863
+ code: "authentication_failed",
864
+ status: status === 403 || status === 401 ? status : 403,
865
+ origin: "upstream",
866
+ };
867
+ }
812
868
  if (normalized !== undefined && AUTH_ERROR_IDENTIFIERS.has(normalized)) {
813
869
  let authStatus: number;
814
870
  if (status === 401 || status === 403) authStatus = status;
@@ -919,17 +975,15 @@ export async function mapUpstreamHttpError(response: Response, providerId: strin
919
975
  : isContextLengthFailure(parsedBody)
920
976
  ? { code: "context_length_exceeded" as const, status: 413, origin: "upstream" as const }
921
977
  : classifyUpstreamError(response.status, identifier);
922
- const extracted = extractUpstreamMessage(parsedBody ?? text);
923
- let message =
924
- extracted.length > 0
925
- ? extracted
926
- : text.trim() || `Provider returned HTTP ${response.status}.`;
978
+ const upstreamText = upstreamMessageAsIs(parsedBody ?? text, "");
979
+ let message = upstreamText.message || `Provider returned HTTP ${response.status}.`;
927
980
  // A bare "Provider returned HTTP <5xx>" tells the operator nothing actionable;
928
981
  // platform-unavailable fallbacks get the same human sign-off every other
929
- // gateway error carries. The raw upstream body stays in `details.raw` for
930
- // diagnostics either way.
982
+ // gateway error carries — but only when the upstream supplied no text of its
983
+ // own. The raw upstream body stays in `details.raw` for diagnostics either way.
931
984
  if (
932
985
  classification.code === "platform_unavailable" &&
986
+ !upstreamText.hadUpstreamText &&
933
987
  message === `Provider returned HTTP ${response.status}.`
934
988
  ) {
935
989
  message = `The provider's service is temporarily unavailable (HTTP ${response.status}) (request {requestId}). Your request did not go through — wait a moment and try again. Hold tight ☁️`;
@@ -949,6 +1003,7 @@ export async function mapUpstreamHttpError(response: Response, providerId: strin
949
1003
  ...(reportedProviderCode ? { providerCode: reportedProviderCode } : {}),
950
1004
  ...(requestId ? { upstreamRequestId: requestId } : {}),
951
1005
  raw: text.slice(0, MAX_UPSTREAM_ERROR_BYTES),
1006
+ ...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
952
1007
  ...(retryAfterMs === null || retryAfterMs === undefined ? {} : { retryAfterMs }),
953
1008
  ...(queuedWaitMs !== undefined
954
1009
  ? { reason: "upstream-queued", cooldownMs: queuedWaitMs }
@@ -971,6 +1026,57 @@ export function extractHtmlTitle(html: string): string {
971
1026
  return html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 160);
972
1027
  }
973
1028
 
1029
+ /** The public bound on an upstream message surface. */
1030
+ export const MAX_UPSTREAM_MESSAGE_LENGTH = 500;
1031
+
1032
+ /**
1033
+ * Extracts the upstream's own text from an error body, "as-is".
1034
+ *
1035
+ * The upstream error passthrough contract: when a failure is unknown or
1036
+ * unparseable, the client should see whatever the upstream actually said,
1037
+ * not a generalized 502 sentence. Structured JSON envelopes already flow
1038
+ * through `extractUpstreamMessage`; this helper extends the same idea to the
1039
+ * shapes that used to lose the text entirely — HTML error pages (their
1040
+ * `<title>` is often the only readable thing) and plain-text bodies.
1041
+ *
1042
+ * The returned message is bounded to 500 chars (D44) and newline-collapsed,
1043
+ * matching `sanitizePublicDetail` per-string bound; `details.raw` keeps the
1044
+ * full 4000. `hadUpstreamText` distinguishes "the upstream said something"
1045
+ * from "we synthesized a fallback", so callers can keep their human sign-off
1046
+ * only when there is genuinely no upstream text.
1047
+ */
1048
+ export function upstreamMessageAsIs(
1049
+ body: unknown,
1050
+ fallback: string,
1051
+ ): { message: string; hadUpstreamText: boolean } {
1052
+ const raw = typeof body === "string" ? body : "";
1053
+ const looksLikeHtml = raw.includes("<") && raw.includes(">");
1054
+ // HTML error pages first: the structured extractor passes a string through
1055
+ // verbatim, which would surface raw markup instead of the readable `<title>`
1056
+ // (or a tag-stripped excerpt when the page has no title).
1057
+ if (looksLikeHtml) {
1058
+ const title = extractHtmlTitle(raw);
1059
+ if (title.length > 0) {
1060
+ return {
1061
+ message: title.replace(/[\r\n]+/g, " ").trim().slice(0, MAX_UPSTREAM_MESSAGE_LENGTH),
1062
+ hadUpstreamText: true,
1063
+ };
1064
+ }
1065
+ }
1066
+ const structured = extractUpstreamMessageText(body);
1067
+ if (structured.length > 0) {
1068
+ return { message: structured.slice(0, MAX_UPSTREAM_MESSAGE_LENGTH), hadUpstreamText: true };
1069
+ }
1070
+ const text = raw.trim();
1071
+ if (text.length > 0) {
1072
+ return {
1073
+ message: text.replace(/[\r\n]+/g, " ").trim().slice(0, MAX_UPSTREAM_MESSAGE_LENGTH),
1074
+ hadUpstreamText: true,
1075
+ };
1076
+ }
1077
+ return { message: fallback, hadUpstreamText: false };
1078
+ }
1079
+
974
1080
  /** Throws a typed transport error when an upstream returns an HTML error page. */
975
1081
  export async function throwIfHtmlResponse(response: Response): Promise<void> {
976
1082
  const contentType = response.headers.get("content-type") ?? "";
@@ -928,7 +928,31 @@ export class RoutingEngine {
928
928
  matching = groups.flat();
929
929
  }
930
930
  }
931
- if (matching.length === 0) throw modelNotFoundError(requestedModel, modelIds);
931
+ if (matching.length === 0) {
932
+ // A qualified `provider/model` reference whose model is not in the
933
+ // catalog still routes: clone the provider's first live candidate (any
934
+ // model) and retarget it at the requested model. This is the passthrough
935
+ // contract — a provider that accepts arbitrary model ids (BYOK, custom
936
+ // gateways, or a model the catalog has not fetched yet) must be able to
937
+ // serve them. The clone inherits the provider's endpoint, wire family,
938
+ // dialect, capabilities, tenant scoping and account/network-pool wiring;
939
+ // only the model id changes. A provider with zero candidates cannot be
940
+ // synthesized, so bare-model misses and empty providers still 404.
941
+ if (parsedModels.length === 1 && parsedModels[0]!.parsed.providerId !== null) {
942
+ const providerId = parsedModels[0]!.parsed.providerId;
943
+ const sibling = [...index.qualified.entries()]
944
+ .filter(([key]) => key.startsWith(`${providerId}/`))
945
+ .flatMap(([, group]) => group)
946
+ .find((candidate) => candidate.tenant_id == null || candidate.tenant_id === tid);
947
+ if (sibling !== undefined) {
948
+ matching = [{
949
+ ...sibling,
950
+ model_id: parsedModels[0]!.parsed.modelId,
951
+ }];
952
+ }
953
+ }
954
+ if (matching.length === 0) throw modelNotFoundError(requestedModel, modelIds);
955
+ }
932
956
  const owners = [...new Set(matching.map((candidate) => candidate.provider_id))];
933
957
  if (owners.length > 1 && !resolved.model.includes("/") && !combo)
934
958
  throw ambiguousModelError(requestedModel, owners);
@@ -17,6 +17,7 @@ import {
17
17
  import { CANONICAL_REASONING_EFFORTS, parseReasoningIntent, parseToolChoice } from "../dialects";
18
18
  import { groupedBlocks, startModel, stopData } from "./encode";
19
19
  import { finiteNumber, stringValue } from "../../../protocol/codecs";
20
+ import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "../../../protocol/request/messages";
20
21
  import { usageToMessagesWire } from "../../../providers/provider-usage";
21
22
 
22
23
  export class MessagesAdapter implements SurfaceAdapter {
@@ -39,7 +40,7 @@ export class MessagesAdapter implements SurfaceAdapter {
39
40
  const headers = options.headers ?? supplied.headers;
40
41
  if (!stringValue(body.model)) throw new Error("Messages request requires model");
41
42
  if (!Array.isArray(body.messages)) throw new Error("Messages request requires messages[]");
42
- const maxTokens = body.max_tokens ?? body.maxTokens;
43
+ const maxTokens = body.max_tokens ?? body.maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS;
43
44
  if (!finiteNumber(maxTokens) || !Number.isInteger(maxTokens) || maxTokens <= 0)
44
45
  throw new Error("Messages request requires positive integer max_tokens");
45
46
  const declaredSystem = systemBlocks(body.system);