cartethyia 2.7.9 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/.env.example +3 -2
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  30. package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
  31. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  44. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
  47. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
  49. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
  51. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
  53. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
  61. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  62. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  63. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
  65. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
  67. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
  69. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
  71. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
  73. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
  75. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
  77. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
  79. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
  81. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
  83. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
  85. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
  87. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/zro.svg +3 -0
  89. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  90. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  91. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  92. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
  93. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  94. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
  95. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  96. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
  97. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  98. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
  99. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  100. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
  101. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  102. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
  103. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  104. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
  105. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/dashboard/index.html.gz +0 -0
  108. package/dist/dashboard/precompressed-assets.json +54 -53
  109. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  110. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  111. package/package.json +1 -1
  112. package/src/boot/app.ts +10 -13
  113. package/src/boot/dependencies.ts +1 -1
  114. package/src/boot/flush.ts +20 -0
  115. package/src/boot/lifecycle.ts +7 -5
  116. package/src/config/routing.ts +26 -0
  117. package/src/config/spec-metadata.ts +72 -0
  118. package/src/config/spec.ts +20 -0
  119. package/src/console/api-keys/contracts.ts +12 -1
  120. package/src/console/api-keys/routes.ts +40 -28
  121. package/src/console/auth/account-reset.ts +7 -9
  122. package/src/console/backup/contracts.ts +4 -0
  123. package/src/console/backup/export-endpoint.ts +7 -5
  124. package/src/console/cli-tools/service.ts +6 -12
  125. package/src/console/console-router-guards.ts +23 -16
  126. package/src/console/domain-registration.ts +0 -2
  127. package/src/console/inferhub/normalize.ts +24 -28
  128. package/src/console/inferhub/types.ts +3 -0
  129. package/src/console/ip-bans/routes.ts +7 -13
  130. package/src/console/observability/store.ts +0 -53
  131. package/src/console/providers/catalog/contracts.ts +2 -2
  132. package/src/console/providers/catalog/discover-constants.ts +6 -0
  133. package/src/console/providers/catalog/routes.ts +1 -0
  134. package/src/console/providers/catalog/store.ts +21 -16
  135. package/src/console/quota/account-quota-global.ts +14 -24
  136. package/src/console/quota/account-quota-shared.ts +49 -0
  137. package/src/console/quota/account-quota-tenant.ts +6 -18
  138. package/src/console/quota/quota-view.ts +19 -3
  139. package/src/console/routing/pools/operations.ts +31 -46
  140. package/src/console/shared/errors.ts +33 -6
  141. package/src/main.ts +2 -3
  142. package/src/network/pool/byte-accounting.ts +3 -2
  143. package/src/network/pool/selector.ts +182 -39
  144. package/src/network/pool-cooldown-store.ts +89 -14
  145. package/src/network/pool-health.ts +49 -61
  146. package/src/network/rate-limit-verdict.ts +1 -1
  147. package/src/network/ssrf.ts +6 -6
  148. package/src/observability/event-log.ts +7 -361
  149. package/src/observability/performance-metrics.ts +9 -0
  150. package/src/observability/runtime-metrics.ts +14 -9
  151. package/src/observability/telemetry-buffer.ts +15 -25
  152. package/src/observability/telemetry-reconciliation.ts +9 -37
  153. package/src/observability/telemetry-row.ts +125 -0
  154. package/src/persistence/db-mode.ts +4 -1
  155. package/src/persistence/dialect.ts +18 -0
  156. package/src/persistence/migrate.ts +6 -3
  157. package/src/persistence/schema.ts +8 -0
  158. package/src/persistence/ttl-cache.ts +20 -2
  159. package/src/protocol/codecs.ts +1 -1
  160. package/src/protocol/dsml.ts +34 -86
  161. package/src/protocol/markup-stream-parser.ts +195 -0
  162. package/src/protocol/reasoning-text.ts +19 -0
  163. package/src/protocol/request/chat.ts +10 -10
  164. package/src/protocol/request/gemini.ts +8 -2
  165. package/src/protocol/request/messages.ts +3 -4
  166. package/src/protocol/response/chat.ts +12 -20
  167. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  168. package/src/protocol/response/responses.ts +18 -2
  169. package/src/protocol/stop-reason.ts +23 -1
  170. package/src/protocol/think-split.ts +112 -0
  171. package/src/protocol/translation/dialect/qwen.ts +26 -58
  172. package/src/providers/adapters/compatible-adapter.ts +71 -12
  173. package/src/providers/authentication/oauth-client.ts +1 -1
  174. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  175. package/src/providers/authentication/oauth-refresh-service.ts +30 -49
  176. package/src/providers/configured-provider.ts +148 -0
  177. package/src/providers/default-registry.ts +61 -36
  178. package/src/providers/discovery/probe-phases.ts +14 -4
  179. package/src/providers/discovery/probing-service.ts +15 -2
  180. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  181. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  182. package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
  184. package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
  185. package/src/providers/integrations/buddy/capabilities.ts +2 -2
  186. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
  187. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
  188. package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
  189. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  190. package/src/providers/integrations/cline/cline.ts +2 -2
  191. package/src/providers/integrations/commandcode/quota.ts +22 -28
  192. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  193. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  194. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  195. package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
  196. package/src/providers/integrations/cursor/cursor.ts +89 -40
  197. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  198. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  199. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  200. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  201. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  202. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  203. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  204. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  205. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  206. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  207. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  208. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  209. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  210. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  211. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  212. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  213. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  214. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
  215. package/src/providers/integrations/hermes/index.ts +2 -0
  216. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  217. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  218. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  219. package/src/providers/integrations/kiro/kiro.ts +15 -19
  220. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  221. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  222. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  223. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  224. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  225. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  226. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  227. package/src/providers/integrations/perch/perch.ts +3 -13
  228. package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
  229. package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
  230. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  231. package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
  232. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  233. package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
  234. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
  235. package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
  236. package/src/providers/integrations/qoder/qoder.ts +0 -1
  237. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  238. package/src/providers/integrations/trae/trae-quota.ts +6 -28
  239. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  240. package/src/providers/integrations/trae/trae.ts +7 -24
  241. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  242. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  243. package/src/providers/integrations/zro/zro.ts +204 -0
  244. package/src/providers/operations/account-error-classification.ts +199 -307
  245. package/src/providers/operations/account-health-service.ts +82 -1
  246. package/src/providers/operations/account-refresh-status.ts +22 -37
  247. package/src/providers/operations/account-tier.ts +15 -16
  248. package/src/providers/operations/client-version-resolver.ts +1 -1
  249. package/src/providers/operations/client-versions.ts +5 -51
  250. package/src/providers/operations/cooldown-rules.ts +11 -16
  251. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  252. package/src/providers/operations/provider-catalog-service.ts +1 -1
  253. package/src/providers/operations/provider-credential-service.ts +102 -32
  254. package/src/providers/operations/provider-rules/github.ts +48 -0
  255. package/src/providers/operations/provider-rules/grok.ts +33 -0
  256. package/src/providers/operations/provider-rules/inception.ts +49 -0
  257. package/src/providers/operations/provider-rules/index.ts +32 -0
  258. package/src/providers/operations/provider-rules/perch.ts +37 -0
  259. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  260. package/src/providers/operations/provider-rules/trae.ts +81 -0
  261. package/src/providers/provider-contracts.ts +4 -2
  262. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  263. package/src/providers/provider-metadata.ts +40 -10
  264. package/src/providers/provider-probe.ts +15 -1
  265. package/src/providers/provider-registry.ts +42 -33
  266. package/src/providers/quota/quota-limit-windows.ts +16 -5
  267. package/src/providers/quota/quota-support.ts +1 -1
  268. package/src/providers/quota/quota-window-parser.ts +90 -18
  269. package/src/providers/quota/refresh.ts +75 -18
  270. package/src/security/api-key-auth-cache.ts +1 -1
  271. package/src/security/api-key-auth.ts +6 -2
  272. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  273. package/src/transport/dispatch/attempt-loop.ts +71 -17
  274. package/src/transport/dispatch/generation.ts +2 -8
  275. package/src/transport/dispatch/native-shared.ts +25 -0
  276. package/src/transport/dispatch/proxy-request.ts +8 -46
  277. package/src/transport/dispatch/responses-compact.ts +2 -8
  278. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  279. package/src/transport/dispatch/streaming/stream.ts +8 -36
  280. package/src/transport/dispatch/systemone.ts +2 -8
  281. package/src/transport/dispatch/token-count.ts +2 -6
  282. package/src/transport/dispatch/upstream.ts +63 -0
  283. package/src/transport/drain-endpoint.ts +31 -4
  284. package/src/transport/failure-policy.ts +16 -30
  285. package/src/transport/guards/body-policy.ts +7 -1
  286. package/src/transport/guards/error-lifecycle.ts +13 -1
  287. package/src/transport/guards/request-context.ts +22 -14
  288. package/src/transport/request/preparer.ts +81 -71
  289. package/src/transport/routing/bounded-lru.ts +14 -0
  290. package/src/transport/routing/drain-reserve.ts +129 -0
  291. package/src/transport/routing/plan-gate.ts +7 -1
  292. package/src/transport/routing/round-robin.ts +60 -0
  293. package/src/transport/routing/route-catalog.ts +123 -111
  294. package/src/transport/routing/route-model.ts +51 -9
  295. package/src/transport/routing/router.ts +393 -32
  296. package/src/transport/surface/adapters.ts +11 -2
  297. package/src/transport/surface/chat/adapter.ts +2 -2
  298. package/src/transport/surface/chat/encode.ts +47 -4
  299. package/src/transport/surface/chat/parse.ts +2 -12
  300. package/src/transport/surface/completion.ts +44 -33
  301. package/src/transport/surface/content-parts.ts +21 -0
  302. package/src/transport/surface/messages/adapter.ts +7 -1
  303. package/src/transport/surface/messages/encode.ts +11 -3
  304. package/src/transport/surface/messages/parse.ts +2 -1
  305. package/src/transport/surface/messages/stream.ts +33 -17
  306. package/src/transport/surface/responses/encode.ts +22 -5
  307. package/src/transport/surface/responses/parse.ts +3 -11
  308. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  309. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  311. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  312. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  313. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  314. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  315. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  316. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  320. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  321. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  322. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  324. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  325. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  326. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  327. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  328. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  330. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  333. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  334. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  336. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  337. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  338. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  339. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  340. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  341. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  342. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  343. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  344. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  347. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  348. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  349. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  354. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  355. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  356. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  357. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  358. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  359. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  360. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  361. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  362. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  363. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  364. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  365. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  366. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  367. package/src/providers/bundled-discovery-providers.ts +0 -98
  368. package/src/providers/configured-openai-providers.ts +0 -120
  369. package/src/providers/operations/model-discovery-cache.ts +0 -34
  370. package/src/providers/operations/probe-fetch.ts +0 -15
  371. package/src/providers/operations/provider-version-cache.ts +0 -30
  372. package/src/providers/provider-capabilities.ts +0 -33
  373. package/src/providers/provider-credential-envelope.ts +0 -43
  374. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
  375. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -3,6 +3,7 @@ import type { CanonicalRequest, WireFamily } from "../protocol/canonical-model";
3
3
  import { GatewayError } from "../protocol/gateway-error";
4
4
  import { OpenAICompatibleAdapter, withBearerAuthentication } from "./adapters/compatible-adapter";
5
5
  import { providerBaseUrl } from "./provider-metadata";
6
+ import type { ProviderModule } from "./provider-registry";
6
7
 
7
8
  export function ensurePayloadModel(
8
9
  payload: Record<string, unknown>,
@@ -15,6 +16,7 @@ export function ensurePayloadModel(
15
16
  payload["model"] = model;
16
17
  return model;
17
18
  }
19
+
18
20
  /**
19
21
  * Declarative description of one bearer-authenticated OpenAI-compatible provider.
20
22
  *
@@ -98,3 +100,149 @@ export function createApiKeyAdapter(
98
100
  }),
99
101
  );
100
102
  }
103
+
104
+ function apiKeySpec(
105
+ providerId: ProviderId,
106
+ paths: Readonly<Partial<Record<"chat" | "responses", string>>>,
107
+ ): ApiKeyProviderSpec {
108
+ return {
109
+ provider_id: providerId,
110
+ endpoint_paths_by_wire_family: paths,
111
+ };
112
+ }
113
+
114
+ const BEARER_PATHS = { chat: "/chat/completions", responses: "/responses" } as const;
115
+
116
+ /**
117
+ * Builtin providers whose entire adapter contract is "bearer auth against the
118
+ * provider's `BUNDLED_PROVIDER_METADATA` base URL, OpenAI-compatible chat +
119
+ * responses, with endpoint paths declared below the base's version root".
120
+ */
121
+ export const GENERIC_API_KEY_SPECS = {
122
+ larprouter: apiKeySpec("larprouter", BEARER_PATHS),
123
+ mistral: apiKeySpec("mistral", BEARER_PATHS),
124
+ fireworks: apiKeySpec("fireworks", BEARER_PATHS),
125
+ nvidia: apiKeySpec("nvidia", BEARER_PATHS),
126
+ gmi: apiKeySpec("gmi", BEARER_PATHS),
127
+ ollamacloud: apiKeySpec("ollamacloud", BEARER_PATHS),
128
+ together: apiKeySpec("together", BEARER_PATHS),
129
+ siliconflow: apiKeySpec("siliconflow", BEARER_PATHS),
130
+ siliconflowcn: apiKeySpec("siliconflowcn", BEARER_PATHS),
131
+ nanogpt: apiKeySpec("nanogpt", BEARER_PATHS),
132
+ baseten: apiKeySpec("baseten", BEARER_PATHS),
133
+ synthetic: apiKeySpec("synthetic", BEARER_PATHS),
134
+ stepfun: apiKeySpec("stepfun", BEARER_PATHS),
135
+ sakana: apiKeySpec("sakana", BEARER_PATHS),
136
+ helmcode: apiKeySpec("helmcode", BEARER_PATHS),
137
+ coralbricks: apiKeySpec("coralbricks", BEARER_PATHS),
138
+ aiand: apiKeySpec("aiand", BEARER_PATHS),
139
+ wafer: apiKeySpec("wafer", BEARER_PATHS),
140
+ coreweave: apiKeySpec("coreweave", BEARER_PATHS),
141
+ dashscope: apiKeySpec("dashscope", BEARER_PATHS),
142
+ "qwen-portal": apiKeySpec("qwen-portal", BEARER_PATHS),
143
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
144
+
145
+ export type GenericApiKeyProviderId = keyof typeof GENERIC_API_KEY_SPECS;
146
+
147
+ export const GENERIC_API_KEY_PROVIDER_IDS = Object.keys(
148
+ GENERIC_API_KEY_SPECS,
149
+ ) as readonly GenericApiKeyProviderId[];
150
+
151
+ /**
152
+ * Ids whose base URL already ends at a version segment.
153
+ */
154
+ export const VERSIONED_ROOT_API_KEY_SPECS = {
155
+ groq: apiKeySpec("groq", BEARER_PATHS),
156
+ deepinfra: apiKeySpec("deepinfra", BEARER_PATHS),
157
+ novita: apiKeySpec("novita", BEARER_PATHS),
158
+ venice: apiKeySpec("venice", BEARER_PATHS),
159
+ qianfan: apiKeySpec("qianfan", BEARER_PATHS),
160
+ zhipucoding: apiKeySpec("zhipucoding", BEARER_PATHS),
161
+ firepass: apiKeySpec("firepass", BEARER_PATHS),
162
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
163
+
164
+ export type VersionedRootApiKeyProviderId =
165
+ keyof typeof VERSIONED_ROOT_API_KEY_SPECS;
166
+
167
+ export const VERSIONED_ROOT_API_KEY_PROVIDER_IDS = Object.keys(
168
+ VERSIONED_ROOT_API_KEY_SPECS,
169
+ ) as readonly VersionedRootApiKeyProviderId[];
170
+
171
+ type DiscoveryBackedCapabilities = Pick<
172
+ ProviderModule,
173
+ "endpointPathsByWireFamily" | "loadAdapter" | "loadModelDiscovery"
174
+ >;
175
+
176
+ type ModelDiscoveryFactory = NonNullable<ProviderModule["loadModelDiscovery"]>;
177
+ export type OpenAIModelDiscoveryFactory = (
178
+ providerId: string,
179
+ options?: {
180
+ readonly headers?: (credential: string) => Readonly<Record<string, string>>;
181
+ },
182
+ ) => ModelDiscoveryFactory;
183
+
184
+ const DISCOVERY_BACKED_PROVIDER_SPECS = {
185
+ groq: VERSIONED_ROOT_API_KEY_SPECS.groq,
186
+ larprouter: GENERIC_API_KEY_SPECS.larprouter,
187
+ together: GENERIC_API_KEY_SPECS.together,
188
+ deepinfra: VERSIONED_ROOT_API_KEY_SPECS.deepinfra,
189
+ siliconflow: GENERIC_API_KEY_SPECS.siliconflow,
190
+ siliconflowcn: GENERIC_API_KEY_SPECS.siliconflowcn,
191
+ novita: VERSIONED_ROOT_API_KEY_SPECS.novita,
192
+ venice: VERSIONED_ROOT_API_KEY_SPECS.venice,
193
+ nanogpt: GENERIC_API_KEY_SPECS.nanogpt,
194
+ baseten: GENERIC_API_KEY_SPECS.baseten,
195
+ synthetic: GENERIC_API_KEY_SPECS.synthetic,
196
+ stepfun: GENERIC_API_KEY_SPECS.stepfun,
197
+ sakana: GENERIC_API_KEY_SPECS.sakana,
198
+ helmcode: GENERIC_API_KEY_SPECS.helmcode,
199
+ coralbricks: GENERIC_API_KEY_SPECS.coralbricks,
200
+ aiand: GENERIC_API_KEY_SPECS.aiand,
201
+ wafer: GENERIC_API_KEY_SPECS.wafer,
202
+ firepass: VERSIONED_ROOT_API_KEY_SPECS.firepass,
203
+ zhipucoding: VERSIONED_ROOT_API_KEY_SPECS.zhipucoding,
204
+ dashscope: GENERIC_API_KEY_SPECS.dashscope,
205
+ qianfan: VERSIONED_ROOT_API_KEY_SPECS.qianfan,
206
+ } satisfies Readonly<Record<string, ApiKeyProviderSpec>>;
207
+
208
+ type DiscoveryBackedProviderId = keyof typeof DISCOVERY_BACKED_PROVIDER_SPECS;
209
+
210
+ /** Canonical discovery-backed provider ids, in declaration order. */
211
+ export const DISCOVERY_BACKED_PROVIDER_IDS = Object.keys(
212
+ DISCOVERY_BACKED_PROVIDER_SPECS,
213
+ ) as readonly DiscoveryBackedProviderId[];
214
+
215
+ export function discoveryBackedCapabilities(
216
+ providerId: string,
217
+ spec: ApiKeyProviderSpec,
218
+ openAIModelDiscovery: OpenAIModelDiscoveryFactory,
219
+ ): DiscoveryBackedCapabilities {
220
+ return {
221
+ ...(spec.endpoint_paths_by_wire_family === undefined
222
+ ? {}
223
+ : { endpointPathsByWireFamily: spec.endpoint_paths_by_wire_family }),
224
+ loadAdapter: async () => createApiKeyAdapter(spec),
225
+ loadModelDiscovery: openAIModelDiscovery(providerId, {
226
+ headers: (credential) => ({ authorization: `Bearer ${credential}` }),
227
+ }),
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Creates the standard provider capability table using the registry's shared
233
+ * discovery loader, keeping its lazy import and authentication behavior intact.
234
+ */
235
+ export function createDiscoveryBackedProviderCapabilities(
236
+ openAIModelDiscovery: OpenAIModelDiscoveryFactory,
237
+ ): Readonly<Record<DiscoveryBackedProviderId, DiscoveryBackedCapabilities>> {
238
+ const capabilities = {} as Record<DiscoveryBackedProviderId, DiscoveryBackedCapabilities>;
239
+ for (const providerId of DISCOVERY_BACKED_PROVIDER_IDS) {
240
+ capabilities[providerId] = discoveryBackedCapabilities(
241
+ providerId,
242
+ DISCOVERY_BACKED_PROVIDER_SPECS[providerId],
243
+ openAIModelDiscovery,
244
+ );
245
+ }
246
+ return capabilities;
247
+ }
248
+
@@ -4,13 +4,20 @@
4
4
  // drag every integration into memory at process start.
5
5
  import {
6
6
  GENERIC_API_KEY_SPECS,
7
- } from "./configured-openai-providers";
8
- import { createDiscoveryBackedProviderCapabilities, discoveryBackedCapabilities } from "./bundled-discovery-providers";
9
- import { createApiKeyAdapter } from "./configured-provider";
10
- import type { ApiKeyProviderSpec } from "./configured-provider";
11
- import { oauthCapability, quotaCapability, type ProviderModuleCapabilities } from "./provider-capabilities";
7
+ createDiscoveryBackedProviderCapabilities,
8
+ discoveryBackedCapabilities,
9
+ createApiKeyAdapter,
10
+ type ApiKeyProviderSpec,
11
+ } from "./configured-provider";
12
12
  import { BUDDY_PROVIDER_CAPABILITIES } from "./integrations/buddy/capabilities";
13
- import { ProviderRegistry, toRegistration, type ProviderModule } from "./provider-registry";
13
+ import {
14
+ ProviderRegistry,
15
+ toRegistration,
16
+ oauthCapability,
17
+ quotaCapability,
18
+ type ProviderModule,
19
+ type ProviderModuleCapabilities,
20
+ } from "./provider-registry";
14
21
  import { BUNDLED_PROVIDER_METADATA, PROVIDER_COMPATIBILITY_PROFILES, providerBaseUrl, providerRequiresAccount, providerUpstreamHost, providerDefaultWireFamily, type BundledProviderId } from "./provider-metadata";
15
22
  import { createAnthropicCompatibleAdapter } from "./integrations/anthropic";
16
23
  import { defineModel } from "./provider-model-definition";
@@ -31,6 +38,29 @@ function configuredProvider(
31
38
  };
32
39
  }
33
40
 
41
+ /**
42
+ * Extracts a bearer token from a JSON credential object when the stored secret
43
+ * is a JSON account export, falling back to the raw secret otherwise. Several
44
+ * discovery paths accept both a bare token and a JSON export, and each used to
45
+ * re-implement this same parse + key-pick; this keeps the reading in one place.
46
+ *
47
+ * Preserves the exact fallback semantics: an unparseable secret, or a JSON
48
+ * object with none of the recognized token keys, yields the raw secret.
49
+ */
50
+ function extractJsonCredentialToken(secret: string): string {
51
+ if (!secret.startsWith("{")) return secret;
52
+ try {
53
+ const parsed = JSON.parse(secret) as Record<string, unknown>;
54
+ const found = ["accessToken", "access_token", "token"].find(
55
+ (key) => typeof parsed[key] === "string" && (parsed[key] as string).length > 0,
56
+ );
57
+ if (found !== undefined) return parsed[found] as string;
58
+ } catch {
59
+ // Use the raw secret as the token.
60
+ }
61
+ return secret;
62
+ }
63
+
34
64
  /**
35
65
  * Qoder's live model list for one realm.
36
66
  *
@@ -60,18 +90,7 @@ function traeDiscovery(
60
90
  if (secret.length === 0 || fetcher === undefined) {
61
91
  return realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
62
92
  }
63
- let token = secret;
64
- if (secret.startsWith("{")) {
65
- try {
66
- const parsed = JSON.parse(secret) as Record<string, unknown>;
67
- const found = ["accessToken", "access_token", "token"].find(
68
- (key) => typeof parsed[key] === "string" && (parsed[key] as string).length > 0,
69
- );
70
- if (found !== undefined) token = parsed[found] as string;
71
- } catch {
72
- // Use the raw secret as the token.
73
- }
74
- }
93
+ const token = extractJsonCredentialToken(secret);
75
94
  const region = request.authState?.["region"];
76
95
  const rows = await discoverTraeModels({
77
96
  realm,
@@ -545,18 +564,7 @@ export const PROVIDER_CAPABILITIES = {
545
564
  const secret = request.credential.trim();
546
565
  const fetcher = request.fetcher as unknown as typeof fetch | undefined;
547
566
  if (secret.length === 0 || fetcher === undefined) return FREEPI_MODELS;
548
- let token = secret;
549
- if (secret.startsWith("{")) {
550
- try {
551
- const parsed = JSON.parse(secret) as Record<string, unknown>;
552
- const found = ["accessToken", "access_token", "token"].find(
553
- (key) => typeof parsed[key] === "string" && (parsed[key] as string).length > 0,
554
- );
555
- if (found !== undefined) token = parsed[found] as string;
556
- } catch {
557
- // Use the raw secret as the token.
558
- }
559
- }
567
+ const token = extractJsonCredentialToken(secret);
560
568
  const rows = await discoverFreepiModels({ token, fetcher });
561
569
  if (rows.length === 0) return FREEPI_MODELS;
562
570
  return rows;
@@ -670,9 +678,7 @@ export const PROVIDER_CAPABILITIES = {
670
678
  // dressed ZCode client request (see the integration). The credential is a
671
679
  // ZCode login JWT — pasted, or minted by the sign-in flow below.
672
680
  loadAdapter: async () =>
673
- createApiKeyAdapter(
674
- (await import("./integrations/zcode-start-plan/provider")).ZCODE_START_PLAN_SPEC,
675
- ),
681
+ (await import("./integrations/zcode-start-plan/provider")).createZcodeStartPlanAdapter(),
676
682
  loadModels: async () =>
677
683
  (await import("./integrations/zcode-start-plan/provider")).ZCODE_START_PLAN_MODELS,
678
684
  // The device flow stores the issued ZCode login JWT, not a Coding Plan key.
@@ -783,10 +789,29 @@ export const PROVIDER_CAPABILITIES = {
783
789
  responses: "/responses",
784
790
  },
785
791
  loadAdapter: async () => createApiKeyAdapter(GENERIC_API_KEY_SPECS.ollamacloud),
786
- loadModels: async () => (await import("./integrations/ollamacloud-models")).OLLAMACLOUD_MODELS,
787
- loadQuotaCollector: quotaCapability(() => import("./integrations/ollamacloud-quota"), "fetchOllamaQuota"),
792
+ loadModels: async () => (await import("./integrations/ollamacloud")).OLLAMACLOUD_MODELS,
793
+ loadQuotaCollector: quotaCapability(() => import("./integrations/ollamacloud"), "fetchOllamaQuota"),
788
794
  loadModelDiscovery: openAIModelDiscovery("ollamacloud", { headers: (credential) => ({ authorization: `Bearer ${credential}` }) }),
789
795
  },
796
+ freebuff: {
797
+ // Freebuff chat is OpenAI-compatible on `/api/v1/chat/completions`, but the
798
+ // adapter runs a stateful preflight (session admission + agent-runs START)
799
+ // and a device-signature header set — see the integration.
800
+ endpointPathsByWireFamily: { chat: "/api/v1/chat/completions" },
801
+ loadAdapter: async () => (await import("./integrations/freebuff/freebuff")).createFreebuffAdapter(),
802
+ loadModels: async () => (await import("./integrations/freebuff/freebuff-catalog")).FREEBUFF_MODELS,
803
+ loadAuthentication: oauthCapability(() => import("./integrations/freebuff/freebuff-oauth"), "freebuffOAuthClient"),
804
+ loadQuotaCollector: quotaCapability(() => import("./integrations/freebuff/freebuff-quota"), "fetchFreebuffQuota"),
805
+ loadModelDiscovery: async () =>
806
+ (await import("./integrations/freebuff/freebuff-discovery")).freebuffModelDiscovery(),
807
+ },
808
+ zro: {
809
+ endpointPathsByWireFamily: { messages: "/v1/messages" },
810
+ loadAdapter: async () => (await import("./integrations/zro/zro")).createZroAdapter(),
811
+ loadModels: async () => (await import("./integrations/zro/zro")).ZRO_MODELS,
812
+ loadQuotaCollector: quotaCapability(() => import("./integrations/zro/zro"), "fetchZroQuota"),
813
+ loadModelDiscovery: async () => (await import("./integrations/zro/zro")).zroModelDiscovery()(),
814
+ },
790
815
 
791
816
  ...createDiscoveryBackedProviderCapabilities(openAIModelDiscovery),
792
817
 
@@ -835,7 +860,7 @@ export const PROVIDER_CAPABILITIES = {
835
860
  // the provider usable before anyone syncs.
836
861
  dashscope: {
837
862
  ...discoveryBackedCapabilities("dashscope", GENERIC_API_KEY_SPECS.dashscope, openAIModelDiscovery),
838
- loadModels: async () => (await import("./integrations/dashscope-models")).DASHSCOPE_MODELS,
863
+ loadModels: async () => (await import("./integrations/alibabacp/dashscope-models")).DASHSCOPE_MODELS,
839
864
  },
840
865
 
841
866
  // Anthropic-Messages providers reached with a plain key. The wire path is
@@ -472,8 +472,9 @@ export async function selectProbeAccount(args: {
472
472
  * back to the generic OpenAI-compatible adapter so probe/dispatch still works.
473
473
  * The low-level compatible adapter is used directly to avoid importing concrete
474
474
  * integrations from discovery (which would violate the provider-architecture
475
- * layering test). `loadOne` returning `undefined` means "no bundled adapter";
476
- * a throw is a registry failure and propagates rather than being reported as
475
+ * layering test). `resolve(..., { force: true })` rebuilds the bundled adapter
476
+ * against the current registration; `undefined` means "no bundled adapter"; a
477
+ * throw is a registry failure and propagates rather than being reported as
477
478
  * "no adapter registered".
478
479
  */
479
480
  export async function resolveProbeAdapter(args: {
@@ -482,7 +483,7 @@ export async function resolveProbeAdapter(args: {
482
483
  readonly providerWireRow: ProbeProviderRow | undefined;
483
484
  }): Promise<ProviderAdapter | undefined> {
484
485
  const { providerRegistry, providerId, providerWireRow } = args;
485
- let adapter = await providerRegistry.loadOne(providerId);
486
+ let adapter = await providerRegistry.resolve(providerId, { force: true });
486
487
  if (!adapter) {
487
488
  try {
488
489
  if (providerWireRow?.baseUrl) {
@@ -665,8 +666,16 @@ export function buildProbeCanonicalRequest(args: {
665
666
  * probe.
666
667
  */
667
668
  readonly providerSystemPrompt?: string | undefined;
669
+ /**
670
+ * A provider-specific default probe prompt, used when the operator sent no
671
+ * prompt. Lets a provider whose policy gate flags repeated identical probes
672
+ * (Freebuff: `prompt_policy_refused` / "same message sent over and over")
673
+ * receive a minimal, per-model-varying probe instead of the shared identity
674
+ * question every provider otherwise sends.
675
+ */
676
+ readonly defaultPromptOverride?: string | undefined;
668
677
  }): CanonicalRequest {
669
- const { modelId, request, probeReasoning, sourceSurface, maxOutputTokens, providerSystemPrompt } = args;
678
+ const { modelId, request, probeReasoning, sourceSurface, maxOutputTokens, providerSystemPrompt, defaultPromptOverride } = args;
670
679
  const fast = maxOutputTokens === PROBE_FAST_MAX_OUTPUT_TOKENS;
671
680
  return {
672
681
  model: modelId,
@@ -681,6 +690,7 @@ export function buildProbeCanonicalRequest(args: {
681
690
  kind: "text",
682
691
  text:
683
692
  request.prompt ??
693
+ defaultPromptOverride ??
684
694
  (fast ? PROBE_FAST_DEFAULT_PROMPT : "What model are you, and what is your knowledge cutoff date? Answer in one sentence."),
685
695
  },
686
696
  ],
@@ -56,7 +56,7 @@ import {
56
56
  wireContextFrom,
57
57
  type ProbeProviderRow,
58
58
  } from "./probe-phases";
59
- import { createProbeFetch } from "../operations/probe-fetch";
59
+ import { createProbeFetch } from "../provider-probe";
60
60
  import { authHeaders } from "../adapters/compatible-adapter";
61
61
  import {
62
62
  byokAuthHeaderShape,
@@ -263,7 +263,20 @@ export class ProviderProbingService {
263
263
  ...(preferences.maxOutputTokens === undefined
264
264
  ? {}
265
265
  : { maxOutputTokens: preferences.maxOutputTokens }),
266
- ...(typeof adapter?.systemPrompt === "function"
266
+ // Freebuff's admission policy gate flags repeated identical probes as
267
+ // automated farming (`prompt_policy_refused` / "same message sent over
268
+ // and over"). Its probe must be minimal and always distinct per model —
269
+ // no worker system template, no shared identity question. `ping` is the
270
+ // same minimal greeting the fast-probe path already uses; the model id
271
+ // keeps every probe distinct so the "same message over and over" gate
272
+ // cannot flag a batch run.
273
+ ...(providerId === "freebuff"
274
+ ? {
275
+ defaultPromptOverride: `ping ${modelId}`,
276
+ providerSystemPrompt: undefined,
277
+ }
278
+ : {}),
279
+ ...(typeof adapter?.systemPrompt === "function" && providerId !== "freebuff"
267
280
  ? { providerSystemPrompt: adapter.systemPrompt() }
268
281
  : {}),
269
282
  });
@@ -19,8 +19,7 @@
19
19
  */
20
20
  import { defineModel } from "../../provider-model-definition";
21
21
  import type { ModelDefinition } from "../../provider-model-definition";
22
-
23
- export const ALIBABACP_PROVIDER_ID = "alibabacp" as const;
22
+ import { ALIBABACP_PROVIDER_ID } from "./alibabacp";
24
23
 
25
24
  /** Chat path below the region's `/compatible-mode/v1` base. */
26
25
  const CHAT_ENDPOINT = "/chat/completions";
@@ -17,8 +17,8 @@
17
17
  * does not publish yet arrives through the sync control instead of being
18
18
  * asserted ahead of the upstream.
19
19
  */
20
- import { defineModel } from "../provider-model-definition";
21
- import type { ModelDefinition } from "../provider-model-definition";
20
+ import { defineModel } from "../../provider-model-definition";
21
+ import type { ModelDefinition } from "../../provider-model-definition";
22
22
 
23
23
  export const DASHSCOPE_PROVIDER_ID = "dashscope" as const;
24
24
 
@@ -400,7 +400,11 @@ export function dropAntigravityUnsignedReasoning(
400
400
  if (message.role !== "assistant") return message;
401
401
  const content = message.content.filter((part) => {
402
402
  if (part.kind !== "reasoning") return true;
403
- if (part.signature !== undefined) return true;
403
+ // A present-but-empty signature is still unsigned: upstream rejects a
404
+ // `thinking` part whose `signature` is missing *or* empty with
405
+ // `400 messages.N.content.0.thinking.signature: Field required`. Only a
406
+ // non-empty signature is a real one.
407
+ if (typeof part.signature === "string" && part.signature.length > 0) return true;
404
408
  changed = true;
405
409
  return false;
406
410
  });
@@ -21,6 +21,7 @@ import { createHash } from "node:crypto";
21
21
  import { completeRequiredSchema } from "../../../protocol/codecs";
22
22
  import type { CanonicalRequest } from "../../../protocol/canonical-model";
23
23
  import { isRecord } from "../../../protocol/codecs";
24
+ import { GatewayError } from "../../../protocol/gateway-error";
24
25
  import { applyDeepSeekReasoning, applyOpenAIReasoning, backfillDeepSeekReasoningContent, clampedEffortOutputFloor, ensureChatReasoningReplayPresence } from "../../../protocol/reasoning";
25
26
  import { BUDDY_CN_EFFORTS, BUDDY_INTL_EFFORTS, BUDDY_SHARED_RAW } from "./buddy-catalog-shared";
26
27
  // Imported for the CN roster only: a CN-only id has no row in the shared
@@ -55,6 +56,123 @@ function normalizedToolName(value: string, index: number, used: Set<string>): st
55
56
  return uniqueToolName(base || `tool_${index + 1}`, used);
56
57
  }
57
58
 
59
+ /**
60
+ * Schema keywords whose values are schemas (recursed as schema objects).
61
+ * Mirrors the donor gateway's `upstream_compat._SCHEMA_MAPS/_ARRAYS/CHILDREN`.
62
+ */
63
+ const RE2_SCHEMA_MAPS: ReadonlySet<string> = new Set([
64
+ "properties",
65
+ "patternProperties",
66
+ "$defs",
67
+ "definitions",
68
+ "dependentSchemas",
69
+ "dependencies",
70
+ ]);
71
+ const RE2_SCHEMA_ARRAYS: ReadonlySet<string> = new Set([
72
+ "allOf",
73
+ "anyOf",
74
+ "oneOf",
75
+ "prefixItems",
76
+ "items",
77
+ ]);
78
+ const RE2_SCHEMA_CHILDREN: ReadonlySet<string> = new Set([
79
+ "items",
80
+ "additionalItems",
81
+ "contains",
82
+ "unevaluatedItems",
83
+ "additionalProperties",
84
+ "unevaluatedProperties",
85
+ "propertyNames",
86
+ "not",
87
+ "if",
88
+ "then",
89
+ "else",
90
+ "contentSchema",
91
+ ]);
92
+
93
+ /**
94
+ * Removes unescaped backslashes before underscores in one regex pattern. The
95
+ * CodeBuddy upstream validates tool schemas with RE2, which does not support
96
+ * `\_` escapes — a client schema that serializes `\_` (a common JSON
97
+ * representation of a literal underscore) is rejected with a 400. The donor
98
+ * gateway normalizes this before dispatch; a backslash that escapes a
99
+ * backslash is preserved.
100
+ */
101
+ function normalizeRe2Pattern(value: string): string {
102
+ let out = "";
103
+ for (let i = 0; i < value.length; i++) {
104
+ const char = value[i];
105
+ if (char === "\\" && value[i + 1] === "\\") {
106
+ // Escaped backslash: keep the pair so a following `_` stays escaped.
107
+ out += "\\\\";
108
+ i += 1;
109
+ continue;
110
+ }
111
+ if (char === "\\" && value[i + 1] === "_") {
112
+ // `\_` — the upstream's RE2 rejects it; drop the backslash.
113
+ out += "_";
114
+ i += 1;
115
+ continue;
116
+ }
117
+ out += char;
118
+ }
119
+ return out;
120
+ }
121
+
122
+ /** Normalizes RE2-incompatible underscore escapes in schema pattern positions. */
123
+ function normalizeRe2SchemaPatterns(schema: unknown): void {
124
+ if (!isRecord(schema)) return;
125
+ const pattern = schema["pattern"];
126
+ if (typeof pattern === "string") schema["pattern"] = normalizeRe2Pattern(pattern);
127
+ const patternProperties = schema["patternProperties"];
128
+ if (isRecord(patternProperties)) {
129
+ const normalized: Record<string, unknown> = {};
130
+ for (const [key, value] of Object.entries(patternProperties)) {
131
+ const key2 = normalizeRe2Pattern(key);
132
+ if (key2 in normalized) {
133
+ normalized[key2] = { allOf: [normalized[key2], value] };
134
+ } else {
135
+ normalized[key2] = value;
136
+ }
137
+ }
138
+ schema["patternProperties"] = normalized;
139
+ }
140
+ for (const key of RE2_SCHEMA_MAPS) {
141
+ const map = schema[key];
142
+ if (!isRecord(map)) continue;
143
+ for (const child of Object.values(map)) normalizeRe2SchemaPatterns(child);
144
+ }
145
+ for (const key of RE2_SCHEMA_ARRAYS) {
146
+ const list = schema[key];
147
+ if (!Array.isArray(list)) continue;
148
+ for (const child of list) normalizeRe2SchemaPatterns(child);
149
+ }
150
+ for (const key of RE2_SCHEMA_CHILDREN) {
151
+ normalizeRe2SchemaPatterns(schema[key]);
152
+ }
153
+ }
154
+
155
+ /** JSON-serializes a value to cheaply detect a backslash-before-underscore. */
156
+ function hasUnderscoreEscape(value: unknown): boolean {
157
+ try {
158
+ return JSON.stringify(value).includes("\\_");
159
+ } catch {
160
+ return true;
161
+ }
162
+ }
163
+
164
+ /** Returns the declaration with RE2-safe schema patterns, or the same object. */
165
+ function normalizeRe2Declaration(declaration: Record<string, unknown>): Record<string, unknown> {
166
+ const parameters = declaration["parameters"];
167
+ if (!hasUnderscoreEscape(parameters)) return declaration;
168
+ // Deep-copy only when a rewrite is needed; the shared object must not be
169
+ // mutated for every dispatch.
170
+ const out: Record<string, unknown> = { ...declaration };
171
+ out["parameters"] = structuredClone(parameters);
172
+ normalizeRe2SchemaPatterns(out["parameters"]);
173
+ return out;
174
+ }
175
+
58
176
  /**
59
177
  * Buddy's Chat Completions wire has no `web_search_preview` hosted tool; its
60
178
  * search is an ordinary function named `web_search` with a `{query}` schema.
@@ -102,29 +220,28 @@ export function normalizeBuddyToolNames(payload: Record<string, unknown>): void
102
220
  const name = normalizedToolName(sourceName, index, used);
103
221
  used.add(name);
104
222
  if (!replacements.has(sourceName)) replacements.set(sourceName, name);
105
- if (fn !== undefined)
106
- return {
107
- ...rawTool,
108
- function: {
109
- ...fn,
110
- name,
111
- ...(fn["parameters"] === undefined ? {} : { parameters: completeRequiredSchema(fn["parameters"]) }),
112
- },
223
+ if (fn !== undefined) {
224
+ const fnOut = {
225
+ ...fn,
226
+ name,
227
+ ...(fn["parameters"] === undefined ? {} : { parameters: completeRequiredSchema(fn["parameters"]) }),
113
228
  };
114
- if (custom !== undefined)
115
- return {
116
- ...rawTool,
117
- custom: {
118
- ...custom,
119
- name,
120
- ...(custom["format"] === undefined ? {} : { format: completeRequiredSchema(custom["format"]) }),
121
- },
229
+ return normalizeRe2Declaration(fnOut);
230
+ }
231
+ if (custom !== undefined) {
232
+ const customOut = {
233
+ ...custom,
234
+ name,
235
+ ...(custom["format"] === undefined ? {} : { format: completeRequiredSchema(custom["format"]) }),
122
236
  };
123
- return {
237
+ return { ...rawTool, custom: normalizeRe2Declaration(customOut) };
238
+ }
239
+ const bareOut = {
124
240
  ...rawTool,
125
241
  name,
126
242
  ...(rawTool["parameters"] === undefined ? {} : { parameters: completeRequiredSchema(rawTool["parameters"]) }),
127
243
  };
244
+ return normalizeRe2Declaration(bareOut);
128
245
  });
129
246
  const messages = payload["messages"];
130
247
  if (Array.isArray(messages)) {
@@ -138,6 +255,13 @@ export function normalizeBuddyToolNames(payload: Record<string, unknown>): void
138
255
  }
139
256
  }
140
257
  }
258
+ const responseFormat = payload["response_format"];
259
+ if (isRecord(responseFormat) && hasUnderscoreEscape(responseFormat)) {
260
+ const copied = structuredClone(responseFormat) as Record<string, unknown>;
261
+ const jsonSchema = copied["json_schema"];
262
+ if (isRecord(jsonSchema)) normalizeRe2SchemaPatterns(jsonSchema["schema"]);
263
+ payload["response_format"] = copied;
264
+ }
141
265
  const choice = payload["tool_choice"];
142
266
  if (!isRecord(choice)) return;
143
267
  if (choice["type"] === "allowed_tools" && Array.isArray(choice["tools"])) {
@@ -477,6 +601,10 @@ export function buddyPrePayloadCommon(
477
601
  normalizeBuddyImageUrl(payload);
478
602
  // developer role → system (buddy whitelist rejects developer, 11128)
479
603
  normalizeBuddyRoles(payload);
604
+ // Validate the *caller's* thinking controls before the DeepSeek contract may
605
+ // inject its own `thinking` (an injected `{type:"enabled"}` has no budget and
606
+ // must not be rejected as a malformed client shape).
607
+ validateBuddyThinking(payload);
480
608
  applyOpenAIReasoning(payload, request);
481
609
  applyDeepSeekReasoning(payload);
482
610
  // sol family defaults to high reasoning like reference thinking.go defaultDeepSeekEffort — sol without effort gets 500/invoke error otherwise
@@ -516,6 +644,34 @@ export function buddyPrePayloadCommon(
516
644
  delete payload["agent_prompt"];
517
645
  }
518
646
 
647
+ /**
648
+ * Validates the reasoning controls the CodeBuddy Chat wire accepts, mirroring
649
+ * the donor gateway's `map_reasoning_controls`. The upstream rejects the whole
650
+ * request for shapes it does not model, so a client that sends an unsupported
651
+ * `thinking` shape is failed here with a 400 that names the field — instead of
652
+ * an opaque upstream error.
653
+ */
654
+ function validateBuddyThinking(payload: Record<string, unknown>): void {
655
+ const thinking = isRecord(payload["thinking"]) ? payload["thinking"] : undefined;
656
+ if (thinking === undefined) return;
657
+ const mode = typeof thinking["type"] === "string" ? (thinking["type"] as string) : "";
658
+ if (mode !== "enabled" && mode !== "adaptive" && mode !== "disabled") {
659
+ throw new GatewayError("invalid_request", 400, `CodeBuddy thinking.type must be enabled, adaptive or disabled (got ${JSON.stringify(mode)}) (request {requestId}). Check the shape of what you sent — then resend 🪞`);
660
+ }
661
+ if (mode === "enabled") {
662
+ const budget = thinking["budget_tokens"];
663
+ if (typeof budget !== "number" || !Number.isInteger(budget) || budget < 1024) {
664
+ throw new GatewayError("invalid_request", 400, "CodeBuddy thinking.budget_tokens must be an integer >= 1024 (request {requestId}). Check the shape of what you sent — then resend 🪞");
665
+ }
666
+ } else if (thinking["budget_tokens"] !== undefined) {
667
+ throw new GatewayError("invalid_request", 400, "CodeBuddy thinking.budget_tokens requires thinking.type=enabled (request {requestId}). Check the shape of what you sent — then resend 🪞");
668
+ }
669
+ const display = thinking["display"];
670
+ if (display !== undefined && display !== null && display !== "summarized") {
671
+ throw new GatewayError("invalid_request", 400, "CodeBuddy thinking.display supports only summarized (request {requestId}). Check the shape of what you sent — then resend 🪞");
672
+ }
673
+ }
674
+
519
675
  function injectSolReasoningDefault(payload: Record<string, unknown>): void {
520
676
  const model = typeof payload["model"] === "string" ? (payload["model"] as string) : "";
521
677
  const isSol = model.toLowerCase().includes("sol");