cartethyia 2.7.0 → 2.7.2

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 (330) hide show
  1. package/.env.example +107 -106
  2. package/README.md +25 -2
  3. package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
  17. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
  18. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
  26. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
  30. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
  31. package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
  32. package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
  37. package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
  38. package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
  43. package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
  44. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  45. package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
  46. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
  48. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
  50. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
  52. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
  54. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
  60. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
  62. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  65. package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
  66. package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
  67. package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
  68. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  69. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
  70. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
  74. package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
  76. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
  78. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  79. package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
  80. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
  82. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
  84. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
  86. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  89. package/dist/dashboard/assets/providers/inception.svg +5 -0
  90. package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
  94. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
  96. package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
  98. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
  100. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
  102. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
  104. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  107. package/dist/dashboard/index.html +1 -1
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +54 -53
  110. package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
  111. package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
  112. package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
  113. package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
  114. package/package.json +96 -96
  115. package/scripts/cli/console-menu.ts +5 -2
  116. package/scripts/cli/tray.ts +21 -6
  117. package/src/boot/app.ts +684 -714
  118. package/src/boot/dependencies.ts +65 -62
  119. package/src/config/cooldowns.ts +16 -0
  120. package/src/config/providers.ts +13 -1
  121. package/src/config/resources.ts +70 -70
  122. package/src/config/security.ts +37 -37
  123. package/src/config/spec.ts +371 -358
  124. package/src/console/cli-tools/contracts.ts +10 -4
  125. package/src/console/console-router.ts +233 -225
  126. package/src/console/domain-registration.ts +398 -381
  127. package/src/console/providers/catalog/catalog-projections.ts +2 -6
  128. package/src/console/providers/catalog/contracts.ts +19 -0
  129. package/src/console/providers/catalog/provider-operations.ts +38 -0
  130. package/src/console/providers/catalog/routes.ts +14 -0
  131. package/src/console/providers/catalog/store-helpers.ts +326 -0
  132. package/src/console/providers/catalog/store.ts +1292 -1563
  133. package/src/console/quota/account-quota-global.ts +2 -2
  134. package/src/console/quota/account-quota-shared.ts +1 -1
  135. package/src/console/quota/account-quota-tenant.ts +35 -12
  136. package/src/console/quota/quota-view.ts +2 -2
  137. package/src/console/routing/pools/contracts.ts +41 -1
  138. package/src/console/routing/pools/operations.ts +183 -4
  139. package/src/console/routing/pools/relay-deploy.ts +17 -4
  140. package/src/console/routing/pools/routes.ts +30 -0
  141. package/src/console/routing/pools/store.ts +98 -8
  142. package/src/main.ts +183 -167
  143. package/src/network/outbound-fetch.ts +124 -208
  144. package/src/network/pool/agent.ts +15 -0
  145. package/src/network/pool/pool-state.ts +7 -0
  146. package/src/network/pool/selector.ts +110 -2
  147. package/src/network/pool-cooldown-store.ts +252 -0
  148. package/src/network/pool-health-machine.ts +5 -3
  149. package/src/network/pool-health.ts +36 -9
  150. package/src/observability/event-query.ts +57 -0
  151. package/src/observability/metrics.ts +467 -467
  152. package/src/observability/runtime-metrics.ts +269 -269
  153. package/src/persistence/health-event-flush.ts +2 -0
  154. package/src/persistence/schema.ts +19 -0
  155. package/src/persistence/usage-flush.ts +8 -3
  156. package/src/protocol/canonical-model.ts +1 -1
  157. package/src/protocol/dsml.ts +377 -0
  158. package/src/protocol/gateway-error.ts +232 -232
  159. package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
  160. package/src/protocol/request/chat.ts +7 -7
  161. package/src/protocol/request/codex.ts +2 -2
  162. package/src/protocol/request/gemini.ts +1 -1
  163. package/src/protocol/request/messages.ts +6 -6
  164. package/src/protocol/request/responses.ts +7 -7
  165. package/src/protocol/response/dsml.ts +12 -133
  166. package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
  167. package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
  168. package/src/{transport → protocol}/translation/capabilities.ts +2 -2
  169. package/src/{transport → protocol}/translation/normalize.ts +1 -1
  170. package/src/{transport → protocol}/translation/quirks.ts +1 -1
  171. package/src/{transport → protocol}/translation/search.ts +1 -1
  172. package/src/{transport → protocol}/translation/thinking.ts +23 -1
  173. package/src/{transport → protocol}/translation/tool-id.ts +1 -1
  174. package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
  175. package/src/protocol/upstream-parsing.ts +176 -0
  176. package/src/providers/adapters/compatible-adapter.ts +11 -2
  177. package/src/providers/authentication/oauth-refresh-service.ts +9 -5
  178. package/src/providers/default-registry.ts +22 -5
  179. package/src/providers/discovery/probe-phases.ts +16 -3
  180. package/src/providers/discovery/probing-service.ts +4 -1
  181. package/src/providers/integrations/anthropic.ts +1 -1
  182. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
  184. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
  185. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
  186. package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
  187. package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
  188. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
  189. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
  190. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
  191. package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
  192. package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
  193. package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
  194. package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
  195. package/src/providers/integrations/claude/claude.ts +1 -1
  196. package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
  197. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  198. package/src/providers/integrations/codex/codex.ts +1 -1
  199. package/src/providers/integrations/factory/factory.ts +1 -1
  200. package/src/providers/integrations/github/github.ts +1 -1
  201. package/src/providers/integrations/grok/grok.ts +1 -1
  202. package/src/providers/integrations/inception/provider.ts +91 -0
  203. package/src/providers/integrations/inferhub.ts +1 -1
  204. package/src/providers/integrations/kiro/kiro-request.ts +1 -1
  205. package/src/providers/integrations/mistral.ts +193 -19
  206. package/src/providers/integrations/ollamacloud-models.ts +50 -0
  207. package/src/providers/integrations/opencode/fingerprint.ts +19 -2
  208. package/src/providers/integrations/opencode/prompt.ts +111 -0
  209. package/src/providers/integrations/opencode/provider.ts +101 -23
  210. package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
  211. package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
  212. package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
  213. package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
  214. package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
  215. package/src/providers/integrations/qoder/qoder.ts +315 -400
  216. package/src/providers/integrations/shared/install-id.ts +2 -1
  217. package/src/providers/integrations/trae/trae-cache.ts +110 -0
  218. package/src/providers/integrations/trae/trae-oauth.ts +29 -4
  219. package/src/providers/integrations/trae/trae-protocol.ts +2 -1
  220. package/src/providers/integrations/trae/trae.ts +106 -23
  221. package/src/providers/operations/account-error-classification.ts +1684 -0
  222. package/src/providers/operations/account-health-service.ts +27 -1503
  223. package/src/providers/operations/account-reset-service.ts +2 -0
  224. package/src/providers/operations/provider-catalog-service.ts +11 -1
  225. package/src/providers/provider-contracts.ts +7 -4
  226. package/src/providers/provider-metadata.ts +25 -4
  227. package/src/providers/provider-probe.ts +2 -2
  228. package/src/{console → providers}/quota/cache.ts +1 -1
  229. package/src/{console → providers}/quota/refresh.ts +5 -5
  230. package/src/security/cli-client-fingerprint.ts +9 -2
  231. package/src/transport/dispatch/attempt-finalize.ts +114 -12
  232. package/src/transport/dispatch/attempt-loop.ts +3 -0
  233. package/src/transport/dispatch/empty-stream.ts +9 -2
  234. package/src/transport/dispatch/generation.ts +37 -55
  235. package/src/transport/dispatch/handler-deps.ts +32 -0
  236. package/src/transport/dispatch/leases.ts +9 -4
  237. package/src/transport/dispatch/proxy-request.ts +6 -30
  238. package/src/transport/dispatch/responses-compact.ts +22 -43
  239. package/src/transport/dispatch/streaming/finalize.ts +3 -0
  240. package/src/transport/dispatch/streaming/stream.ts +0 -8
  241. package/src/transport/dispatch/systemone.ts +22 -42
  242. package/src/transport/dispatch/token-count.ts +23 -39
  243. package/src/transport/dispatch/upstream.ts +1 -3
  244. package/src/transport/dispatch/websearch.ts +22 -41
  245. package/src/transport/failure-policy.ts +92 -178
  246. package/src/transport/guards/abuse-guard.ts +10 -0
  247. package/src/transport/guards/auth-guard.ts +109 -109
  248. package/src/transport/guards/error-lifecycle.ts +1 -1
  249. package/src/transport/guards/pipeline.ts +220 -220
  250. package/src/transport/guards/request-context.ts +245 -238
  251. package/src/transport/request/degradation.ts +20 -1
  252. package/src/transport/request/inflight.ts +4 -114
  253. package/src/transport/request/preparer.ts +6 -6
  254. package/src/transport/request/state.ts +66 -38
  255. package/src/transport/request/tenant-preferences.ts +1 -1
  256. package/src/transport/routing/route-catalog.ts +75 -43
  257. package/src/transport/routing/router.ts +30 -2
  258. package/src/transport/surface/messages/parse.ts +1 -1
  259. package/src/transport/surface/responses/encode.ts +2 -3
  260. package/src/transport/surface/responses/parse.ts +49 -7
  261. package/src/types/undici.d.ts +33 -0
  262. package/src/workers/buddy-growth-tasks.ts +346 -425
  263. package/src/workers/daily-checkin.ts +17 -6
  264. package/src/workers/quota-refresh-sweep.ts +2 -2
  265. package/src/workers/scheduler.ts +17 -1
  266. package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
  267. package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
  268. package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
  269. package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
  270. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
  271. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
  272. package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
  273. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
  275. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
  276. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
  277. package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
  278. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
  279. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
  280. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
  281. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
  282. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
  283. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  284. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
  285. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
  286. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
  287. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
  288. package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
  289. package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
  290. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
  291. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
  292. package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
  293. package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
  294. package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
  295. package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
  296. package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
  297. package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
  298. package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
  299. package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
  300. package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
  301. package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
  302. package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
  303. package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
  304. package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
  305. package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
  306. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  307. package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
  308. package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
  309. package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
  310. package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
  311. package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
  312. package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
  313. package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
  314. package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
  315. package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
  316. package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
  317. package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
  318. package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
  319. package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
  320. package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
  321. package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
  322. package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
  323. package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
  324. package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
  325. package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
  326. package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
  327. package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
  328. package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
  329. /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
  330. /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
@@ -1,98 +1,27 @@
1
1
  /**
2
2
  * CodeBuddy-family DeepSeek sometimes emits native tool markup as assistant text.
3
3
  * Complete declared calls are recovered; incomplete and undeclared blocks stay text.
4
+ *
5
+ * The mechanics live in `../dsml` with the CodeBuddy dialect as default; this
6
+ * module keeps the historical public surface (`DsmlToolCallParser`,
7
+ * `parseDsmlToolCalls`) stable for this wire family.
4
8
  */
5
- const SEPARATORS = ["||", "|", "|"] as const;
6
- const BLOCK_NAMES = ["calls", "function_calls"] as const;
7
- const OPENERS = SEPARATORS.flatMap((separator) => BLOCK_NAMES.map((name) => `<${separator}DSML${separator} ${name}>`));
8
- const TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$/;
9
+ import {
10
+ DsmlStreamParser,
11
+ type DsmlToolCall,
12
+ } from "../dsml";
9
13
 
10
- /** Escapes regex metacharacters so a `|` separator is literal, not alternation. */
11
- function escapeRegExp(value: string): string {
12
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
- }
14
-
15
- export interface DsmlToolCall {
16
- readonly id: string;
17
- readonly name: string;
18
- readonly arguments: string;
19
- }
14
+ export type { DsmlToolCall } from "../dsml";
20
15
 
21
16
  export interface DsmlRecovery {
22
17
  readonly text: string;
23
18
  readonly calls: readonly DsmlToolCall[];
24
19
  }
25
20
 
26
- export class DsmlToolCallParser {
27
- readonly #allowedNames: ReadonlySet<string>;
28
- #pending = "";
29
- #callSequence = 0;
30
-
21
+ /** Streaming strict-prefix parser with the CodeBuddy dialect defaults. */
22
+ export class DsmlToolCallParser extends DsmlStreamParser {
31
23
  constructor(allowedNames: ReadonlySet<string>) {
32
- this.#allowedNames = allowedNames;
33
- }
34
-
35
- push(chunk: string): DsmlRecovery {
36
- this.#pending += chunk;
37
- let cursor = 0;
38
- let visible = "";
39
- const calls: DsmlToolCall[] = [];
40
- for (;;) {
41
- let openIndex = -1;
42
- let opener = "";
43
- for (const candidate of OPENERS) {
44
- const index = this.#pending.indexOf(candidate, cursor);
45
- if (index >= 0 && (openIndex < 0 || index < openIndex)) {
46
- openIndex = index;
47
- opener = candidate;
48
- }
49
- }
50
- if (openIndex < 0) {
51
- const safeEnd = this.#settledEnd(cursor);
52
- visible += this.#pending.slice(cursor, safeEnd);
53
- cursor = safeEnd;
54
- break;
55
- }
56
- visible += this.#pending.slice(cursor, openIndex);
57
- const separator = SEPARATORS.find((value) => opener.startsWith(`<${value}DSML`));
58
- const blockName = BLOCK_NAMES.find((value) => opener.endsWith(` ${value}>`));
59
- if (separator === undefined || blockName === undefined) {
60
- visible += "<";
61
- cursor = openIndex + 1;
62
- continue;
63
- }
64
- const close = `</${separator}DSML${separator} ${blockName}>`;
65
- const closeIndex = this.#pending.indexOf(close, openIndex + opener.length);
66
- if (closeIndex < 0) {
67
- cursor = openIndex;
68
- break;
69
- }
70
- const end = closeIndex + close.length;
71
- const block = this.#pending.slice(openIndex, end);
72
- const parsed = parseBlock(block, opener, close, separator, this.#allowedNames);
73
- if (parsed === undefined) visible += block;
74
- else {
75
- for (const call of parsed) calls.push({ ...call, id: `call_dsml_${this.#callSequence++}_${call.name}` });
76
- }
77
- cursor = end;
78
- }
79
- this.#pending = this.#pending.slice(cursor);
80
- return { text: visible, calls };
81
- }
82
-
83
- flush(): DsmlRecovery {
84
- const text = this.#pending;
85
- this.#pending = "";
86
- return { text, calls: [] };
87
- }
88
-
89
- #settledEnd(start: number): number {
90
- const remaining = this.#pending.slice(start);
91
- for (let length = Math.min(OPENERS.reduce((max, opener) => Math.max(max, opener.length - 1), 0), remaining.length); length > 0; length -= 1) {
92
- const suffix = remaining.slice(-length);
93
- if (OPENERS.some((opener) => opener.startsWith(suffix))) return this.#pending.length - length;
94
- }
95
- return this.#pending.length;
24
+ super({ allowedNames });
96
25
  }
97
26
  }
98
27
 
@@ -107,53 +36,3 @@ export function parseDsmlToolCalls(
107
36
  const recovered = [...first.calls, ...last.calls];
108
37
  return recovered.length === 0 ? undefined : { text: `${first.text}${last.text}`.trim(), calls: recovered };
109
38
  }
110
-
111
- function parseBlock(
112
- block: string,
113
- opener: string,
114
- close: string,
115
- separator: string,
116
- allowedNames: ReadonlySet<string>,
117
- ): Omit<DsmlToolCall, "id">[] | undefined {
118
- const inner = block.slice(opener.length, -close.length);
119
- const pipe = escapeRegExp(separator);
120
- const invokePattern = new RegExp(`<${pipe}DSML${pipe}\\s+invoke\\s+name="([^"]*)"\\s*>`, "g");
121
- const calls: Omit<DsmlToolCall, "id">[] = [];
122
- // The allowlist is strict only when the caller knows the declared tools: a
123
- // caller without one (a direct parse) keeps the generic name rule, while a
124
- // dispatch that declared its tools must never execute a name it did not offer.
125
- const strict = allowedNames.size > 0;
126
- let cursor = 0;
127
- for (const invoke of inner.matchAll(invokePattern)) {
128
- const name = invoke[1] ?? "";
129
- if (!TOOL_NAME.test(name) || (strict && !allowedNames.has(name)) || invoke.index === undefined) return undefined;
130
- if (inner.slice(cursor, invoke.index).trim().length > 0) return undefined;
131
- const bodyStart = invoke.index + invoke[0].length;
132
- const invokeClose = `</${separator}DSML${separator} invoke>`;
133
- const invokeEnd = inner.indexOf(invokeClose, bodyStart);
134
- if (invokeEnd < 0) return undefined;
135
- const parameterPattern = new RegExp(`<${pipe}DSML${pipe}\\s+parameter\\s+name="([^"]*)"(?:\\s+string="(true|false)")?\\s*>`, "g");
136
- const body = inner.slice(bodyStart, invokeEnd);
137
- const args: Record<string, unknown> = {};
138
- let parameterCursor = 0;
139
- for (const parameter of body.matchAll(parameterPattern)) {
140
- const key = parameter[1] ?? "";
141
- if (key.length === 0 || parameter.index === undefined || body.slice(parameterCursor, parameter.index).trim().length > 0) return undefined;
142
- const valueStart = parameter.index + parameter[0].length;
143
- const parameterClose = `</${separator}DSML${separator} parameter>`;
144
- const valueEnd = body.indexOf(parameterClose, valueStart);
145
- if (valueEnd < 0) return undefined;
146
- const raw = body.slice(valueStart, valueEnd);
147
- if (parameter[2] === "true") args[key] = raw;
148
- else {
149
- try { args[key] = JSON.parse(raw); }
150
- catch { args[key] = raw; }
151
- }
152
- parameterCursor = valueEnd + parameterClose.length;
153
- }
154
- if (body.slice(parameterCursor).trim().length > 0) return undefined;
155
- calls.push({ name, arguments: JSON.stringify(args) });
156
- cursor = invokeEnd + invokeClose.length;
157
- }
158
- return calls.length > 0 && inner.slice(cursor).trim().length === 0 ? calls : undefined;
159
- }
@@ -1,5 +1,5 @@
1
- import { resolveImageSource, splitDataUrl } from "../../protocol/codecs";
2
- import type { CanonicalRequest, ContentPart } from "../../protocol/canonical-model";
1
+ import { resolveImageSource, splitDataUrl } from "../codecs";
2
+ import type { CanonicalRequest, ContentPart } from "../canonical-model";
3
3
  import { log } from "../../observability/logger";
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- import type { CacheHint } from "../../protocol/canonical-model";
1
+ import type { CacheHint } from "../canonical-model";
2
2
 
3
3
  /**
4
4
  * Provider-specific cache control translation from canonical cache hints.
@@ -1,12 +1,12 @@
1
1
  // Capability contracts, wire-family matrices, projection, and preflight.
2
- import { capabilityUnsupported, GatewayError } from "../../protocol/gateway-error";
2
+ import { capabilityUnsupported, GatewayError } from "../gateway-error";
3
3
  import {
4
4
  summarizeParts,
5
5
  type CacheHint,
6
6
  type CanonicalRequest,
7
7
  type GenerationControls,
8
8
  type WireFamily,
9
- } from "../../protocol/canonical-model";
9
+ } from "../canonical-model";
10
10
 
11
11
  /** Capabilities declared by one resolved provider/model/route candidate. */
12
12
  export interface RouteCapabilities {
@@ -1,6 +1,6 @@
1
1
  // Provider-neutral generation-controls normalization, used only after
2
2
  // capability preflight.
3
- import type { GenerationControls } from "../../protocol/canonical-model";
3
+ import type { GenerationControls } from "../canonical-model";
4
4
 
5
5
  /** Normalizes the output-token control family onto `max_output_tokens`. */
6
6
  export function normalizeGenerationControls(controls: GenerationControls): GenerationControls {
@@ -9,7 +9,7 @@
9
9
  // Add a row ONLY when a concrete upstream 400/422 proves the quirk; do not
10
10
  // speculate. The table is applied on the dispatch path before wire translation.
11
11
 
12
- import type { CanonicalRequest, GenerationControls } from "../../protocol/canonical-model";
12
+ import type { CanonicalRequest, GenerationControls } from "../canonical-model";
13
13
  import {
14
14
  THINKING_STRIPPED_SAMPLING_CONTROLS,
15
15
  isThinkingEnabled,
@@ -1,5 +1,5 @@
1
1
  // Hosted web-search tool identity.
2
- import type { ToolDefinition } from "../../protocol/canonical-model";
2
+ import type { ToolDefinition } from "../canonical-model";
3
3
 
4
4
  /**
5
5
  * Returns whether a declaration is a provider-hosted search tool.
@@ -5,13 +5,14 @@
5
5
  // so the native config (`thinking_type` + `budget_tokens`) is dropped while the
6
6
  // request-level effort intent (OpenAI `reasoning_effort`) survives — that field
7
7
  // is a property of the request, not of the turn.
8
- import type { CanonicalRequest, ReasoningIntent, WireFamily } from "../../protocol/canonical-model";
8
+ import type { CanonicalRequest, ReasoningIntent, WireFamily } from "../canonical-model";
9
9
 
10
10
  /**
11
11
  * Canonical reasoning effort ladder, ordered least to most intensive.
12
12
  * Provides a unified effort scale across OpenAI, Anthropic, and open-weight models.
13
13
  */
14
14
  export const REASONING_EFFORT_LADDER = [
15
+ "instant",
15
16
  "minimal",
16
17
  "low",
17
18
  "medium",
@@ -54,6 +55,7 @@ export interface ParsedThinkingSuffix {
54
55
  */
55
56
  export const LEVEL_TO_BUDGET: Readonly<Record<ReasoningEffortLevel | "none", number>> = {
56
57
  none: 0,
58
+ instant: 256,
57
59
  minimal: 512,
58
60
  low: 1_024,
59
61
  medium: 8_192,
@@ -241,6 +243,20 @@ export const BASE_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
241
243
  */
242
244
  const MIMO_V26_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = ["low", "medium", "high"];
243
245
 
246
+ /**
247
+ * Mercury (Inception Labs) — the chat ladder is `{instant, low, medium,
248
+ * high}`: `minimal`, `xhigh`, and `max` each answer a 400
249
+ * `reasoning_effort must be one of: 'instant', 'low', 'medium', 'high'`.
250
+ * `instant` is not on the canonical ladder, so it can only appear here; the
251
+ * clamp steps down to `high` for any higher request.
252
+ */
253
+ const INCEPTION_SUPPORTED_EFFORTS: readonly ReasoningEffortLevel[] = [
254
+ "instant",
255
+ "low",
256
+ "medium",
257
+ "high",
258
+ ];
259
+
244
260
  /**
245
261
  * `low..max`: the OpenAI 5.6/6/daybreak generation and the new-gen Claude
246
262
  * adaptive models (`fable`/`mythos`/`opus-5`/`sonnet-5`/`opus-4-7`/`opus-4-8`)
@@ -320,6 +336,12 @@ export function resolveSupportedReasoningEfforts(
320
336
  if (id.includes("mimo-v2.6")) {
321
337
  return MIMO_V26_SUPPORTED_EFFORTS;
322
338
  }
339
+ // Checked before the generic default: Mercury's chat endpoint answers every
340
+ // effort outside {instant, low, medium, high} with a 400 that names the
341
+ // field (recorded live from request 40b60777).
342
+ if (id.includes("mercury") && wireFamily === "chat") {
343
+ return INCEPTION_SUPPORTED_EFFORTS;
344
+ }
323
345
  // Claude catalog ladders: `output_config.effort` rides the
324
346
  // Messages wire, so an out-of-ladder tier is an upstream rejection risk.
325
347
  // Dot-tolerant (`cb/claude-opus-4.6`) because gateway-prefixed ids keep the
@@ -6,7 +6,7 @@
6
6
  // strict wire encoder runs. The same original→sanitized mapping is applied to
7
7
  // both the `toolCall` and its matching `toolResult` so pairing is preserved.
8
8
 
9
- import type { CanonicalRequest } from "../../protocol/canonical-model";
9
+ import type { CanonicalRequest } from "../canonical-model";
10
10
 
11
11
  /** Anthropic tool ID contract: alphanumerics, underscore, hyphen only. */
12
12
  export const TOOL_ID_PATTERN: RegExp = /^[a-zA-Z0-9_-]+$/;
@@ -4,8 +4,8 @@
4
4
  // winning route's capabilities and before projection — keeps the request
5
5
  // dispatchable instead of failing upstream with a 400 the client cannot fix.
6
6
 
7
- import type { CanonicalMessage, CanonicalRequest } from "../../protocol/canonical-model";
8
- import { canContainToolResult } from "../../protocol/canonical-model";
7
+ import type { CanonicalMessage, CanonicalRequest } from "../canonical-model";
8
+ import { canContainToolResult } from "../canonical-model";
9
9
 
10
10
  const MISSING_TOOL_OUTPUT = "<missing tool output>";
11
11
 
@@ -0,0 +1,176 @@
1
+ const MAX_COOLDOWN_MS = 24 * 3600 * 1000;
2
+
3
+ /** Parses Retry-After seconds or an HTTP date, bounded to one day. */
4
+ export function parseRetryAfter(value: string | null | undefined): number | null {
5
+ if (!value) return null;
6
+ const trimmed = value.trim();
7
+ const numeric = Number(trimmed);
8
+ if (Number.isFinite(numeric) && numeric > 0) return Math.min(MAX_COOLDOWN_MS, numeric * 1000);
9
+ const parsed = Date.parse(trimmed);
10
+ if (!Number.isFinite(parsed)) return null;
11
+ const delay = parsed - Date.now();
12
+ return delay > 0 ? Math.min(MAX_COOLDOWN_MS, delay) : null;
13
+ }
14
+
15
+ /**
16
+ * Parses x-ratelimit-reset as epoch seconds or a relative delay.
17
+ *
18
+ * Returns `null` — not `0` — when the instant has already passed. This matters
19
+ * because `parseUpstreamBackoff` chains these parsers with `??`, which falls
20
+ * through only on null: a `0` from an elapsed `x-ratelimit-reset` (a cached error
21
+ * body, clock skew, a stale edge response) stopped the chain before the
22
+ * lower-priority headers that carried the real wait. The `0` was then written to
23
+ * `details.retryAfterMs` and read by `classifyAccountError` as `headerCooldown`,
24
+ * which outranks the provider's own message text — so both were discarded and the
25
+ * account fell back to the generic default cooldown while the provider had parked
26
+ * it for the rest of the window.
27
+ *
28
+ * `parseRetryAfter` above already returns `null` for an elapsed date; matching it
29
+ * is what removes the asymmetry.
30
+ */
31
+ export function parseRateLimitReset(value: string | null | undefined): number | null {
32
+ if (!value) return null;
33
+ const numeric = Number(value.trim());
34
+ if (!Number.isFinite(numeric) || numeric <= 0) return null;
35
+ const target = numeric >= 1_000_000_000 ? numeric * 1000 : Date.now() + numeric * 1000;
36
+ const remaining = target - Date.now();
37
+ return remaining > 0 ? Math.min(MAX_COOLDOWN_MS, remaining) : null;
38
+ }
39
+
40
+ /** Parses a millisecond-denominated reset header (`retry-after-ms`, `x-ratelimit-reset-ms`). */
41
+ export function parseResetAfterMs(value: string | null | undefined): number | null {
42
+ if (!value) return null;
43
+ const numeric = Number(value.trim());
44
+ if (!Number.isFinite(numeric) || numeric <= 0) return null;
45
+ return Math.min(MAX_COOLDOWN_MS, Math.round(numeric));
46
+ }
47
+
48
+ /**
49
+ * Reads upstream backoff evidence in reference priority order: millisecond
50
+ * variants first (`retry-after-ms`, then `x-ratelimit-reset-ms`), then the
51
+ * classic `retry-after` / `x-ratelimit-reset` pair.
52
+ */
53
+ export function parseUpstreamBackoff(headers: {
54
+ get(name: string): string | null;
55
+ }): number | null {
56
+ return (
57
+ parseResetAfterMs(headers.get("retry-after-ms")) ??
58
+ parseRetryAfter(headers.get("retry-after")) ??
59
+ parseResetAfterMs(headers.get("x-ratelimit-reset-ms")) ??
60
+ parseRateLimitReset(headers.get("x-ratelimit-reset")) ??
61
+ parseRateLimitReset(headers.get("x-ratelimit-reset-requests")) ??
62
+ parseRateLimitReset(headers.get("retry-after"))
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Extracts bounded reset durations from provider messages when headers omit
68
+ * them.
69
+ *
70
+ * Two shapes are recognized, because providers state resets both ways:
71
+ *
72
+ * - **Relative** — "quota will reset in 3 hours", "try again in 30s".
73
+ * - **Absolute** — "your usage will reset at 2026-09-24 02:12:51 UTC+8"
74
+ * (WorkBuddy/CodeBuddy `6004`). An absolute stamp is *not* a duration, and
75
+ * the relative-only pattern silently missed it, so the account fell back to
76
+ * the generic 15-minute default while the provider had actually parked it
77
+ * for the rest of the window — the account kept re-entering rotation and
78
+ * failing every request inside the stated reset window.
79
+ */
80
+ /**
81
+ * One `amount unit` pair of a duration phrase: `4h`, `13m`, `2 hours`, `30s`.
82
+ *
83
+ * The unit ends on `(?![a-z])`, not `\b`: a compact compound like `2h30m` puts
84
+ * a digit straight after `h`, which is a word character, so a `\b` would refuse
85
+ * to end the pair and the whole phrase would parse as null. `m(?!s|o)` is what
86
+ * stops a bare `m` from swallowing `ms` (milliseconds) or `mo` (months),
87
+ * neither of which is a reset duration.
88
+ */
89
+ const DURATION_PAIR_SOURCE = String.raw`(\d+(?:\.\d+)?)[\s-]*(hours?|hrs?|h|minutes?|mins?|m(?!s|o)|seconds?|secs?|s)(?![a-z])`;
90
+
91
+ function durationUnitMs(unit: string): number {
92
+ const first = unit.toLowerCase()[0];
93
+ return first === "h" ? 3600_000 : first === "m" ? 60_000 : 1_000;
94
+ }
95
+
96
+ /**
97
+ * Sums the `amount unit` pairs at the head of a duration phrase.
98
+ *
99
+ * Providers state a reset as one compound duration at least as often as a
100
+ * single unit: a daily-limit 429 reads "Try again in 4h 13m". A single-pair
101
+ * parser read only `4h` and dropped the 13 minutes, so the stored retry
102
+ * deadline was earlier than the window the provider had actually stated and the
103
+ * account re-entered rotation before it. Pairs are accepted only while they
104
+ * continue the phrase — separated by whitespace, a comma, or `and` — so a
105
+ * number that merely follows later in the sentence cannot be absorbed.
106
+ */
107
+ function parseCompoundDuration(text: string): number | null {
108
+ const pairs = new RegExp(DURATION_PAIR_SOURCE, "gi");
109
+ let totalMs = 0;
110
+ let lastEnd = 0;
111
+ let matched = false;
112
+ for (let pair = pairs.exec(text); pair !== null; pair = pairs.exec(text)) {
113
+ const gap = text.slice(lastEnd, pair.index);
114
+ const continues = matched ? /^[\s,]*(?:and\s+)?$/i.test(gap) : /^\s*$/.test(gap);
115
+ if (!continues) break;
116
+ const amount = Number(pair[1]);
117
+ if (!Number.isFinite(amount) || amount <= 0) break;
118
+ totalMs += amount * durationUnitMs(pair[2] ?? "");
119
+ matched = true;
120
+ lastEnd = pair.index + pair[0].length;
121
+ }
122
+ return matched ? Math.round(totalMs) : null;
123
+ }
124
+
125
+ export function parseProviderResetDuration(message: string): number | null {
126
+ const trigger =
127
+ /(?:quota will reset|resets?|try again|retry(?:ing)?)\s+(?:in|after|over)\s+(?:a\s+)?(?:rolling\s+)?/i.exec(
128
+ message,
129
+ );
130
+ if (trigger) {
131
+ const parsed = parseCompoundDuration(message.slice(trigger.index + trigger[0].length));
132
+ if (parsed !== null) return Math.min(MAX_COOLDOWN_MS, parsed);
133
+ }
134
+ return parseAbsoluteResetTimestamp(message);
135
+ }
136
+
137
+ /**
138
+ * Reads an absolute reset instant out of a provider message, e.g.
139
+ * `will reset at 2026-09-24 02:12:51 UTC+8`. Returns the delay from now,
140
+ * bounded to one day, or null when no future timestamp is present.
141
+ *
142
+ * `Date.parse` cannot read the space-separated `YYYY-MM-DD HH:MM:SS` form with
143
+ * a bare `UTC±H` offset, so the parts are normalized explicitly rather than
144
+ * relying on engine-specific leniency.
145
+ */
146
+ export function parseAbsoluteResetTimestamp(message: string): number | null {
147
+ const match = /(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})\s*(?:UTC|GMT)?\s*([+-]\d{1,2})?(?::?(\d{2}))?/i.exec(
148
+ message,
149
+ );
150
+ if (!match) return null;
151
+ const [, year, month, day, hour, minute, second, offsetHours, offsetMinutes] = match;
152
+ if (!year || !month || !day || !hour || !minute || !second) return null;
153
+ // A bare `UTC+8` means a whole-hour offset; `+08:30`-style halves are rare
154
+ // but cheap to honor. No offset at all is read as UTC (providers that omit
155
+ // it always label the stamp "UTC" in practice).
156
+ const sign = offsetHours?.startsWith("-") ? -1 : 1;
157
+ const offsetTotalMinutes =
158
+ offsetHours === undefined
159
+ ? 0
160
+ : sign * (Math.abs(Number(offsetHours)) * 60 + Number(offsetMinutes ?? 0));
161
+ const utcMs = Date.UTC(
162
+ Number(year),
163
+ Number(month) - 1,
164
+ Number(day),
165
+ Number(hour),
166
+ Number(minute),
167
+ Number(second),
168
+ );
169
+ if (!Number.isFinite(utcMs)) return null;
170
+ const targetMs = utcMs - offsetTotalMinutes * 60_000;
171
+ const delay = targetMs - Date.now();
172
+ // A past stamp is not a usable reset (clock skew, stale cached error); fall
173
+ // through to the caller's default instead of returning a negative delay.
174
+ if (!Number.isFinite(delay) || delay <= 0) return null;
175
+ return Math.min(MAX_COOLDOWN_MS, Math.round(delay));
176
+ }
@@ -3,6 +3,7 @@ import type { CanonicalEvent, CanonicalRequest, WireFamily } from "../../protoco
3
3
  import { isRecord } from "../../protocol/codecs";
4
4
  import {
5
5
  classifyUpstreamError,
6
+ isContextLengthFailure,
6
7
  throwIfHtmlResponse,
7
8
  mapUpstreamHttpError,
8
9
  extractUpstreamMessage,
@@ -408,12 +409,20 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
408
409
  value >= 400 &&
409
410
  value < 600,
410
411
  );
411
- const classification = classifyUpstreamError(providerStatus, identifier);
412
+ // A context-window overflow arrives here on the streaming path with a
413
+ // numeric code that says nothing (`11115`) and the real cause in
414
+ // `extError.message`. The status table alone classifies it as a generic
415
+ // refusal, so the one thing a client acts on — the measured prompt size
416
+ // against the window — never reached it. The prose/identifier check
417
+ // mirrors the non-streaming path's pre-table branch.
418
+ const classification = isContextLengthFailure(json)
419
+ ? { code: "context_length_exceeded" as const, status: 413, origin: "upstream" as const, credentialEvidence: false, rateLimitScope: undefined }
420
+ : classifyUpstreamError(providerStatus, identifier);
412
421
  const message = extractUpstreamMessage(json) || "Upstream reported an error";
413
422
  throw new GatewayError(
414
423
  classification.code,
415
424
  classification.status,
416
- message.slice(0, 300),
425
+ message,
417
426
  {
418
427
  providerId: this.provider_id,
419
428
  ...(providerStatus === undefined ? {} : { providerStatus }),
@@ -294,17 +294,21 @@ async function disableAccount(
294
294
  * "static token" toggle sets — the sweep skips it and its status stops reading
295
295
  * as a broken/re-auth account. Not lease-fenced: a non-destructive annotation
296
296
  * that any process may write, and the credential is not touched.
297
+ *
298
+ * This never changes `status`: a `disabled` account stays `disabled` (only an
299
+ * operator or an explicit recovery may re-enable it), while an `active` one
300
+ * stays `active`. The static marker and the error clearing are orthogonal to
301
+ * the account's health state.
297
302
  */
298
303
  async function markStaticToken(db: CartethyiaDatabase, accountId: string): Promise<void> {
299
304
  await db
300
305
  .update(providerAccounts)
301
306
  .set({
302
307
  staticToken: true,
303
- // A static token is a normal, usable credential, not an error state: the
304
- // account is `active` and dispatchable, so any error left by the failed
305
- // refresh would otherwise keep the row reading "Re-login required" while
306
- // it is in fact serving. Mirrors the console's static-token toggle.
307
- status: "active",
308
+ // A static token is a normal, usable credential, not an error state: any
309
+ // error left by the failed refresh would otherwise keep the row reading
310
+ // "Re-login required" while it is in fact serving. Mirrors the console's
311
+ // static-token toggle.
308
312
  cooldownUntil: null,
309
313
  lastError: null,
310
314
  lastErrorCategory: null,
@@ -366,10 +366,11 @@ export const PROVIDER_CAPABILITIES = {
366
366
  // catalog row behind it.
367
367
  // Dispatch base, not the catalog base: `/zen/v1/models` publishes the
368
368
  // listing while `/inference/v1/models` answers 404, and a byte-exact replay
369
- // against `/inference/v1` returned 200 where `/zen/v1` did not.
369
+ // against `/inference/v1` returned 200 where `/zen/v1` did not. The wire
370
+ // paths are the 2.1 family-segment shapes on the `/inference` origin.
370
371
  endpointPathsByWireFamily: {
371
- chat: "/inference/v1/chat/completions",
372
- responses: "/inference/v1/responses",
372
+ chat: "/inference/openai/v1/chat/completions",
373
+ responses: "/inference/openai/v1/responses",
373
374
  },
374
375
  loadAdapter: async () => createApiKeyAdapter((await import("./integrations/opencode/provider")).OPENCODE_FREE_SPEC),
375
376
  loadModels: async () => (await import("./integrations/opencode/provider")).OPENCODE_FREE_MODELS,
@@ -386,8 +387,8 @@ export const PROVIDER_CAPABILITIES = {
386
387
  },
387
388
  "opencode-zen": {
388
389
  endpointPathsByWireFamily: {
389
- chat: "/inference/v1/chat/completions",
390
- responses: "/inference/v1/responses",
390
+ chat: "/inference/openai/v1/chat/completions",
391
+ responses: "/inference/openai/v1/responses",
391
392
  },
392
393
  loadAdapter: async () => createApiKeyAdapter((await import("./integrations/opencode/provider")).OPENCODE_ZEN_SPEC),
393
394
  loadModels: async () => (await import("./integrations/opencode/provider")).OPENCODE_ZEN_MODELS,
@@ -554,6 +555,21 @@ export const PROVIDER_CAPABILITIES = {
554
555
  });
555
556
  },
556
557
  },
558
+ inception: {
559
+ // Chat only: Mercury 3 / 2.5 / 2 answer the OpenAI-compatible chat wire.
560
+ // The base is version-rooted (`/v1`), so the path is declared below it.
561
+ endpointPathsByWireFamily: { chat: "/chat/completions" },
562
+ loadAdapter: async () => createApiKeyAdapter((await import("./integrations/inception/provider")).INCEPTION_SPEC),
563
+ loadModels: async () => (await import("./integrations/inception/provider")).INCEPTION_MODELS,
564
+ // The listing is bearer-authenticated and the whole provider is pinned to
565
+ // its free allowance, so every fetched row is stamped free-tier.
566
+ loadModelDiscovery: async () => async ({ baseUrl, credential, fetcher }) =>
567
+ (await import("./integrations/inception/provider")).discoverInceptionFreeModels({
568
+ baseUrl,
569
+ ...(typeof credential === "string" && credential.length > 0 ? { credential } : {}),
570
+ ...(fetcher === undefined ? {} : { fetcher }),
571
+ }),
572
+ },
557
573
  openrouter: {
558
574
  loadAdapter: async () => createApiKeyAdapter((await import("./integrations/openrouter/provider")).OPENROUTER_SPEC),
559
575
  // One dynamic import for both halves: this registry lazy-loads every
@@ -698,6 +714,7 @@ export const PROVIDER_CAPABILITIES = {
698
714
  responses: "/responses",
699
715
  },
700
716
  loadAdapter: async () => createApiKeyAdapter(GENERIC_API_KEY_SPECS.ollamacloud),
717
+ loadModels: async () => (await import("./integrations/ollamacloud-models")).OLLAMACLOUD_MODELS,
701
718
  loadQuotaCollector: quotaCapability(() => import("./integrations/ollamacloud-quota"), "fetchOllamaQuota"),
702
719
  loadModelDiscovery: openAIModelDiscovery("ollamacloud", { headers: (credential) => ({ authorization: `Bearer ${credential}` }) }),
703
720
  },