cartethyia 2.7.9 → 2.8.1

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 (496) hide show
  1. package/.env.example +6 -5
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
  31. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
  44. package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
  45. package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
  61. package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
  63. package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
  73. package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
  77. package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
  79. package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
  87. package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
  89. package/dist/dashboard/assets/providers/sensenova.png +0 -0
  90. package/dist/dashboard/assets/providers/zro.svg +3 -0
  91. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  92. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  93. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  94. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
  95. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  96. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
  97. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
  99. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
  101. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
  103. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
  105. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  108. package/dist/dashboard/index.html +2 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -53
  111. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  112. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  113. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  114. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  115. package/package.json +1 -1
  116. package/src/boot/app.ts +11 -14
  117. package/src/boot/dependencies.ts +5 -7
  118. package/src/boot/flush.ts +20 -0
  119. package/src/boot/lifecycle.ts +7 -5
  120. package/src/config/providers.ts +6 -5
  121. package/src/config/routing.ts +26 -0
  122. package/src/config/spec-metadata.ts +72 -0
  123. package/src/config/spec.ts +28 -8
  124. package/src/console/api-keys/contracts.ts +12 -1
  125. package/src/console/api-keys/routes.ts +40 -28
  126. package/src/console/auth/account-reset.ts +7 -9
  127. package/src/console/backup/contracts.ts +4 -0
  128. package/src/console/backup/export-endpoint.ts +7 -5
  129. package/src/console/backup/nine-router.ts +71 -10
  130. package/src/console/backup/service.ts +8 -1
  131. package/src/console/cli-tools/service.ts +6 -12
  132. package/src/console/console-router-guards.ts +23 -16
  133. package/src/console/console-router.ts +1 -1
  134. package/src/console/domain-registration.ts +7 -4
  135. package/src/console/inferhub/client.ts +1 -1
  136. package/src/console/inferhub/normalize.ts +24 -28
  137. package/src/console/inferhub/types.ts +3 -0
  138. package/src/console/ip-bans/routes.ts +7 -13
  139. package/src/console/observability/live.ts +2 -2
  140. package/src/console/observability/store.ts +0 -53
  141. package/src/console/providers/catalog/contracts.ts +9 -3
  142. package/src/console/providers/catalog/model-operations.ts +2 -3
  143. package/src/console/providers/catalog/provider-operations.ts +1 -1
  144. package/src/console/providers/catalog/routes.ts +3 -2
  145. package/src/console/providers/catalog/store.ts +1914 -1902
  146. package/src/console/providers/detail/routes.ts +1 -0
  147. package/src/console/providers/detail/store.ts +4 -0
  148. package/src/console/providers/oauth/callback-listener.ts +30 -11
  149. package/src/console/providers/oauth/device-egress.ts +123 -0
  150. package/src/console/providers/oauth/routes.ts +123 -16
  151. package/src/console/quota/account-quota-global.ts +14 -24
  152. package/src/console/quota/account-quota-shared.ts +49 -0
  153. package/src/console/quota/account-quota-tenant.ts +11 -23
  154. package/src/console/quota/account-test-egress.ts +2 -2
  155. package/src/console/quota/daily-growth-job.ts +1 -1
  156. package/src/console/quota/quota-view.ts +19 -3
  157. package/src/console/routing/pools/contracts.ts +1 -1
  158. package/src/console/routing/pools/operations.ts +32 -47
  159. package/src/console/routing/pools/probe-verdict.ts +2 -2
  160. package/src/console/routing/pools/relay-deploy.ts +1 -1
  161. package/src/console/routing/pools/store.ts +4 -4
  162. package/src/console/shared/errors.ts +33 -6
  163. package/src/main.ts +2 -3
  164. package/src/network/client.ts +67 -0
  165. package/src/network/egress/bidi-session.ts +322 -0
  166. package/src/network/egress/dial.ts +201 -0
  167. package/src/network/egress/factory.ts +191 -0
  168. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  169. package/src/network/egress/tls-profile.ts +123 -0
  170. package/src/network/egress/ws.ts +175 -0
  171. package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
  172. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  173. package/src/network/pool/agent.ts +2 -2
  174. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
  175. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  176. package/src/network/pool/resolver.ts +10 -849
  177. package/src/network/pool/selector.ts +184 -41
  178. package/src/network/ssrf.ts +7 -7
  179. package/src/network/{retry.ts → util/retry.ts} +1 -1
  180. package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
  181. package/src/observability/event-log.ts +7 -361
  182. package/src/observability/performance-metrics.ts +9 -0
  183. package/src/observability/runtime-metrics.ts +14 -9
  184. package/src/observability/telemetry-buffer.ts +16 -26
  185. package/src/observability/telemetry-reconciliation.ts +9 -37
  186. package/src/observability/telemetry-row.ts +125 -0
  187. package/src/persistence/db-mode.ts +4 -1
  188. package/src/persistence/dialect.ts +18 -0
  189. package/src/persistence/migrate.ts +6 -3
  190. package/src/persistence/schema.ts +17 -0
  191. package/src/persistence/ttl-cache.ts +21 -3
  192. package/src/protocol/codecs.ts +59 -2
  193. package/src/protocol/dsml.ts +34 -86
  194. package/src/protocol/markup-stream-parser.ts +195 -0
  195. package/src/protocol/reasoning-text.ts +19 -0
  196. package/src/protocol/request/chat.ts +10 -10
  197. package/src/protocol/request/gemini.ts +8 -2
  198. package/src/protocol/request/messages.ts +34 -22
  199. package/src/protocol/response/chat.ts +12 -20
  200. package/src/protocol/response/messages.ts +34 -7
  201. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  202. package/src/protocol/response/responses.ts +18 -2
  203. package/src/protocol/stop-reason.ts +23 -1
  204. package/src/protocol/think-split.ts +112 -0
  205. package/src/protocol/translation/dialect/qwen.ts +26 -58
  206. package/src/protocol/translation/quirks.ts +15 -0
  207. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  208. package/src/providers/adapters/compatible-adapter.ts +76 -12
  209. package/src/providers/authentication/oauth-client.ts +40 -6
  210. package/src/providers/authentication/oauth-device-flow.ts +105 -11
  211. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  212. package/src/providers/authentication/oauth-refresh-service.ts +766 -758
  213. package/src/providers/configured-provider.ts +148 -0
  214. package/src/providers/default-registry.ts +137 -36
  215. package/src/providers/discovery/discovery-types.ts +7 -0
  216. package/src/providers/discovery/probe-phases.ts +20 -6
  217. package/src/providers/discovery/probing-service.ts +22 -2
  218. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  219. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  220. package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
  221. package/src/providers/integrations/buddy/account.ts +969 -0
  222. package/src/providers/integrations/buddy/capabilities.ts +12 -12
  223. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  224. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
  225. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  226. package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
  227. package/src/providers/integrations/buddy/headers.ts +311 -0
  228. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  229. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  230. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  231. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  232. package/src/providers/integrations/cline/cline.ts +5 -6
  233. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  234. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  235. package/src/providers/integrations/clixad/provider.ts +145 -0
  236. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  237. package/src/providers/integrations/commandcode/quota.ts +22 -28
  238. package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
  239. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  240. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  241. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  242. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  243. package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
  244. package/src/providers/integrations/cursor/cursor.ts +574 -110
  245. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  246. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  247. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  248. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  249. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  250. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  251. package/src/providers/integrations/deepai/provider.ts +42 -0
  252. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  253. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  254. package/src/providers/integrations/factory/factory-oauth.ts +33 -51
  255. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  256. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  257. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  258. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  259. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  260. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  261. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  262. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  263. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  264. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  265. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  266. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  267. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  268. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  269. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  270. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  271. package/src/providers/integrations/freepi/freepi.ts +80 -54
  272. package/src/providers/integrations/github/github-discovery.ts +30 -13
  273. package/src/providers/integrations/github/github-oauth.ts +217 -44
  274. package/src/providers/integrations/github/github-quota.ts +22 -5
  275. package/src/providers/integrations/github/github.ts +40 -32
  276. package/src/providers/integrations/grok/grok-oauth.ts +52 -49
  277. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  278. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  279. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  280. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
  281. package/src/providers/integrations/hermes/index.ts +2 -0
  282. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  283. package/src/providers/integrations/kilo/kilo.ts +1 -1
  284. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  285. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  286. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  287. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  288. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  289. package/src/providers/integrations/kiro/kiro.ts +15 -19
  290. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  291. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  292. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  293. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  294. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  295. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  296. package/src/providers/integrations/openrouter/provider.ts +1 -1
  297. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  298. package/src/providers/integrations/perch/perch-protocol.ts +0 -6
  299. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  300. package/src/providers/integrations/perch/perch.ts +6 -16
  301. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  302. package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
  303. package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
  304. package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
  305. package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
  306. package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
  307. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  308. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  309. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  310. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  311. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  312. package/src/providers/integrations/qoder/qoder.ts +3 -26
  313. package/src/providers/integrations/sensenova/provider.ts +144 -0
  314. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  315. package/src/providers/integrations/trae/trae-quota.ts +21 -29
  316. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  317. package/src/providers/integrations/trae/trae.ts +7 -24
  318. package/src/providers/integrations/vercel/provider.ts +117 -0
  319. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  320. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  321. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  322. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  323. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  324. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  325. package/src/providers/integrations/zro/zro.ts +205 -0
  326. package/src/providers/operations/account-error-classification.ts +199 -307
  327. package/src/providers/operations/account-health-service.ts +82 -1
  328. package/src/providers/operations/account-refresh-status.ts +36 -37
  329. package/src/providers/operations/account-tier.ts +15 -16
  330. package/src/providers/operations/client-version-resolver.ts +1 -1
  331. package/src/providers/operations/client-versions.ts +100 -81
  332. package/src/providers/operations/cooldown-rules.ts +11 -16
  333. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  334. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  335. package/src/providers/operations/provider-catalog-service.ts +1 -1
  336. package/src/providers/operations/provider-credential-service.ts +118 -51
  337. package/src/providers/operations/provider-rules/github.ts +48 -0
  338. package/src/providers/operations/provider-rules/grok.ts +33 -0
  339. package/src/providers/operations/provider-rules/inception.ts +49 -0
  340. package/src/providers/operations/provider-rules/index.ts +32 -0
  341. package/src/providers/operations/provider-rules/perch.ts +37 -0
  342. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  343. package/src/providers/operations/provider-rules/trae.ts +81 -0
  344. package/src/providers/operations/session-resolution.ts +17 -1
  345. package/src/providers/provider-contracts.ts +6 -4
  346. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  347. package/src/providers/provider-metadata.ts +70 -10
  348. package/src/providers/provider-probe.ts +15 -1
  349. package/src/providers/provider-registry.ts +42 -33
  350. package/src/providers/quota/quota-contracts.ts +2 -0
  351. package/src/providers/quota/quota-limit-windows.ts +16 -5
  352. package/src/providers/quota/quota-support.ts +1 -1
  353. package/src/providers/quota/quota-window-parser.ts +90 -18
  354. package/src/providers/quota/refresh.ts +100 -21
  355. package/src/security/api-key-auth-cache.ts +1 -1
  356. package/src/security/api-key-auth.ts +6 -2
  357. package/src/security/ip-boundary.ts +1 -1
  358. package/src/transport/dispatch/attempt-finalize.ts +15 -14
  359. package/src/transport/dispatch/attempt-loop.ts +73 -19
  360. package/src/transport/dispatch/generation.ts +2 -8
  361. package/src/transport/dispatch/handler-deps.ts +1 -1
  362. package/src/transport/dispatch/leases.ts +1 -1
  363. package/src/transport/dispatch/native-shared.ts +25 -0
  364. package/src/transport/dispatch/proxy-request.ts +8 -46
  365. package/src/transport/dispatch/responses-compact.ts +2 -8
  366. package/src/transport/dispatch/retry-policy.ts +1 -1
  367. package/src/transport/dispatch/streaming/finalize.ts +10 -7
  368. package/src/transport/dispatch/streaming/stream.ts +9 -37
  369. package/src/transport/dispatch/systemone.ts +2 -8
  370. package/src/transport/dispatch/token-count.ts +2 -6
  371. package/src/transport/dispatch/upstream.ts +63 -0
  372. package/src/transport/drain-endpoint.ts +31 -4
  373. package/src/transport/failure-policy.ts +16 -30
  374. package/src/transport/guards/body-policy.ts +7 -1
  375. package/src/transport/guards/error-lifecycle.ts +13 -1
  376. package/src/transport/guards/request-context.ts +22 -14
  377. package/src/transport/request/preparer.ts +81 -71
  378. package/src/transport/routing/bounded-lru.ts +14 -0
  379. package/src/transport/routing/drain-reserve.ts +129 -0
  380. package/src/transport/routing/plan-gate.ts +7 -1
  381. package/src/transport/routing/round-robin.ts +60 -0
  382. package/src/transport/routing/route-catalog.ts +123 -111
  383. package/src/transport/routing/route-model.ts +51 -9
  384. package/src/transport/routing/router.ts +393 -32
  385. package/src/transport/shutdown-notice.ts +1 -1
  386. package/src/transport/surface/adapters.ts +11 -2
  387. package/src/transport/surface/chat/adapter.ts +2 -2
  388. package/src/transport/surface/chat/encode.ts +47 -4
  389. package/src/transport/surface/chat/parse.ts +2 -12
  390. package/src/transport/surface/completion.ts +44 -33
  391. package/src/transport/surface/content-parts.ts +21 -0
  392. package/src/transport/surface/messages/adapter.ts +7 -1
  393. package/src/transport/surface/messages/encode.ts +11 -3
  394. package/src/transport/surface/messages/parse.ts +2 -1
  395. package/src/transport/surface/messages/stream.ts +33 -17
  396. package/src/transport/surface/responses/encode.ts +22 -5
  397. package/src/transport/surface/responses/parse.ts +3 -11
  398. package/src/workers/buddy-growth-tasks.ts +8 -8
  399. package/src/workers/daily-checkin.ts +4 -4
  400. package/src/workers/quota-refresh-sweep.ts +1 -1
  401. package/src/workers/zcode-gift-claim.ts +1 -1
  402. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  403. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  404. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  405. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  406. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  407. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  408. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  409. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  410. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  411. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  412. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  413. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  414. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  415. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  416. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  417. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  418. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  419. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
  420. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  421. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  422. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  423. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  424. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  425. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  426. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  427. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  428. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  429. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  430. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  431. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  432. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  433. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  434. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  435. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  436. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  437. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  438. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  439. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  440. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  441. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  442. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  443. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  444. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  445. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  446. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  447. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  448. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  449. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  450. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  451. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  452. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  453. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  454. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  455. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  456. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  457. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  458. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  459. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  460. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  461. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  462. package/src/network/pool-health.ts +0 -210
  463. package/src/providers/bundled-discovery-providers.ts +0 -98
  464. package/src/providers/configured-openai-providers.ts +0 -120
  465. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  466. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  467. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  468. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  469. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  470. package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
  471. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  472. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  473. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  474. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  475. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  476. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  477. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
  478. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  479. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
  480. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  481. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  482. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
  483. package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
  484. package/src/providers/operations/model-discovery-cache.ts +0 -34
  485. package/src/providers/operations/probe-fetch.ts +0 -15
  486. package/src/providers/operations/provider-version-cache.ts +0 -30
  487. package/src/providers/provider-capabilities.ts +0 -33
  488. package/src/providers/provider-credential-envelope.ts +0 -43
  489. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
  490. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
  491. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  492. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  493. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  494. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  495. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  496. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
@@ -0,0 +1,125 @@
1
+ // Shared telemetry insert-row mapping: the single column spelling for both the
2
+ // write path (telemetry-buffer.ts) and the reconciliation backfill
3
+ // (telemetry-reconciliation.ts). Before this module existed the two paths kept
4
+ // hand-maintained mirrors that drifted — the backfill dropped creditUsed,
5
+ // resolveMs, tokensPerSec, firstContentDeltaAtMs, lastEventAtMs,
6
+ // payloadCaptureState, and degenerateStop, so a recovered row read as if those
7
+ // events never happened. One mapper means a column change lands in both paths.
8
+ import { telemetryEvents } from "../persistence/schema";
9
+ import { clientNameFromUserAgent } from "./client-name";
10
+
11
+ /**
12
+ * A normalized telemetry event, shaped like the raw `TelemetryEventRecord` the
13
+ * reconciliation job parses off disk. The write path normalizes its
14
+ * `TelemetryEventInput` (nested `usage`, token-count coercion) into this shape
15
+ * before calling {@link telemetryInsertRow}; the reconciliation path passes its
16
+ * parsed record directly.
17
+ */
18
+ export interface TelemetryInsertRowSource {
19
+ readonly id: string;
20
+ readonly tenantId: string;
21
+ readonly requestId: string;
22
+ readonly createdAt: Date | string;
23
+ readonly sourceSurface: string | null;
24
+ readonly requestedModel: string | null;
25
+ readonly reportedModel: string | null;
26
+ readonly requestedEffort: string | null;
27
+ readonly providerId: string | null;
28
+ readonly accountId: string | null;
29
+ readonly networkPoolId: string | null;
30
+ readonly endpoint: string | null;
31
+ readonly apiKeyId: string | null;
32
+ readonly userAgent: string | null;
33
+ readonly clientIp: string | null;
34
+ readonly latencyMs: number | null;
35
+ readonly ttfbMs: number | null;
36
+ readonly stream: boolean | null;
37
+ readonly status: string | null;
38
+ readonly httpStatus: number | null;
39
+ readonly errorOrigin: string | null;
40
+ readonly errorCategory: string | null;
41
+ readonly inputTokens: number | null;
42
+ readonly cachedInputTokens: number | null;
43
+ readonly outputTokens: number | null;
44
+ readonly reasoningTokens: number | null;
45
+ readonly estimatedCostUsd: number | null;
46
+ readonly creditUsed: number | null;
47
+ readonly resolveMs: number | null;
48
+ readonly tokensPerSec: number | null;
49
+ readonly firstContentDeltaAtMs: number | null;
50
+ readonly lastEventAtMs: number | null;
51
+ /** Why the drawer payloads are empty, or `captured`; absent when unrecorded. */
52
+ readonly payloadCaptureState?: string | null;
53
+ /** `true` for a degenerate-stop request; absent/null for everything else. */
54
+ readonly degenerateStop?: boolean;
55
+ }
56
+
57
+ /** Coerces a token count to the integer the schema columns store; `undefined` for an unavailable count. */
58
+ export function tokenCountToInt(value: number | "unavailable" | undefined): number | undefined {
59
+ return typeof value === "number" ? Math.round(value) : undefined;
60
+ }
61
+
62
+ /**
63
+ * Maps a normalized event to its `telemetry_events` insert row.
64
+ *
65
+ * Every column below has a live producer on the write path, and the
66
+ * reconciliation backfill relies on this exact spelling to recover the same
67
+ * columns from a raw line. Contract details that are load-bearing:
68
+ *
69
+ * - `id = requestId` is what makes the unique index a working idempotency
70
+ * boundary: a retry of the same request carries the same id.
71
+ * - `estimatedCostUsd` persists NULL when the route is unpriced, never the
72
+ * text `"null"` — the analytics `partial` flag counts completed rows with no
73
+ * persisted cost.
74
+ * - `creditUsed`/`tokensPerSec` use `!= null` so a measured zero is preserved
75
+ * (numeric columns are string-typed in Drizzle).
76
+ * - `clientName` is derived here, once, via `clientNameFromUserAgent`, so the
77
+ * write path and the backfill group the same client the same way.
78
+ */
79
+ export function telemetryInsertRow(event: TelemetryInsertRowSource): typeof telemetryEvents.$inferInsert {
80
+ return {
81
+ id: event.requestId,
82
+ tenantId: event.tenantId,
83
+ requestId: event.requestId,
84
+ // Stamped here, not by the reader. A row without it has no real time, and a
85
+ // reader that invented one made every such row look like it happened just now.
86
+ createdAt: new Date(event.createdAt),
87
+ sourceSurface: event.sourceSurface as typeof telemetryEvents.$inferInsert.sourceSurface,
88
+ requestedModel: event.requestedModel,
89
+ reportedModel: event.reportedModel,
90
+ requestedEffort: event.requestedEffort,
91
+ endpoint: event.endpoint,
92
+ apiKeyId: event.apiKeyId,
93
+ userAgent: event.userAgent,
94
+ // Canonical client grouping, stamped once so SQL breakdowns group by it.
95
+ clientName: clientNameFromUserAgent(event.userAgent),
96
+ clientIp: event.clientIp,
97
+ providerId: event.providerId,
98
+ accountId: event.accountId,
99
+ networkPoolId: event.networkPoolId,
100
+ latencyMs: event.latencyMs,
101
+ ttfbMs: event.ttfbMs,
102
+ stream: event.stream,
103
+ httpStatus: event.httpStatus,
104
+ status: event.status as typeof telemetryEvents.$inferInsert.status,
105
+ errorCategory: event.errorCategory,
106
+ errorOrigin: event.errorOrigin,
107
+ inputTokens: event.inputTokens,
108
+ cachedInputTokens: event.cachedInputTokens,
109
+ outputTokens: event.outputTokens,
110
+ reasoningTokens: event.reasoningTokens,
111
+ // `estimated_cost` is `null` when the route is unpriced; persist NULL so
112
+ // the analytics `partial` flag can count it. `String(null)` would store the
113
+ // text "null", which the numeric column rejects.
114
+ estimatedCostUsd: event.estimatedCostUsd === null ? null : String(event.estimatedCostUsd),
115
+ // Provider-billed credit (Tencent buddy-meter `credit`, Qoder `credits`);
116
+ // NULL when the upstream reported no credit field.
117
+ creditUsed: event.creditUsed != null ? String(event.creditUsed) : null,
118
+ resolveMs: event.resolveMs,
119
+ tokensPerSec: event.tokensPerSec != null ? String(event.tokensPerSec) : null,
120
+ firstContentDeltaAtMs: event.firstContentDeltaAtMs,
121
+ lastEventAtMs: event.lastEventAtMs,
122
+ payloadCaptureState: event.payloadCaptureState ?? null,
123
+ degenerateStop: event.degenerateStop ?? null,
124
+ };
125
+ }
@@ -48,7 +48,10 @@ export function resolveDbMode(env: NodeJS.ProcessEnv = process.env): DbMode {
48
48
  */
49
49
  export function resolveDataDir(env: NodeJS.ProcessEnv = process.env): string {
50
50
  const override = env.CARTETHYIA_DATA_DIR?.trim();
51
- if (override) return override;
51
+ // Guard against a literal `"undefined"` value: a launcher that stringifies
52
+ // a missing env var (`String(undefined)`) would otherwise make the gateway
53
+ // write its state into a folder named `undefined/` in the CWD.
54
+ if (override && override !== "undefined") return override;
52
55
  if (process.platform === "win32") {
53
56
  const roaming = env.APPDATA?.trim();
54
57
  if (roaming) return join(roaming, "Cartethyia");
@@ -16,6 +16,7 @@
16
16
  import { sql, type SQL } from "drizzle-orm";
17
17
  import { SQLiteBunSession } from "drizzle-orm/bun-sqlite";
18
18
  import { resolveDbMode } from "./db-mode";
19
+ import { providerAccounts } from "./schema";
19
20
 
20
21
  /** True when the running backend is the embedded SQLite database. */
21
22
  export function sqliteDialect(): boolean {
@@ -67,6 +68,23 @@ export function isSqliteDatabase(database: unknown): boolean {
67
68
  return hasSqliteDialect(database);
68
69
  }
69
70
 
71
+ /**
72
+ * The `EXISTS` subquery for "the column's cooldown object has an entry whose
73
+ * ISO deadline is still in the future". The JSON check is dialect-specific:
74
+ * PostgreSQL uses `jsonb_each_text` (with a shape guard), SQLite `json_each`
75
+ * on its `text` column (D07). Timestamps are stored as ISO strings on both
76
+ * backends, so the lexical comparison against `nowIso` is stable.
77
+ *
78
+ * Callers keep their own scoping around this fragment — a tenant path must
79
+ * never touch `tenant_id IS NULL` rows, and the global path must only touch
80
+ * them — so only the shared JSON subquery lives here.
81
+ */
82
+ export function liveModelCooldownExistsSql(database: unknown, nowIso: string): SQL {
83
+ return isSqliteDatabase(database)
84
+ ? sql`EXISTS (SELECT 1 FROM json_each(${providerAccounts.modelCooldowns}) AS entry WHERE json_valid(${providerAccounts.modelCooldowns}) AND json_type(${providerAccounts.modelCooldowns}) = 'object' AND entry.value > ${nowIso})`
85
+ : sql`EXISTS (SELECT 1 FROM jsonb_each_text(${providerAccounts.modelCooldowns}) AS entry WHERE jsonb_typeof(${providerAccounts.modelCooldowns}) = 'object' AND entry.value > ${nowIso})`;
86
+ }
87
+
70
88
  /**
71
89
  * The per-model cooldown object with every entry whose timestamp has passed
72
90
  * removed, or `'{}'` when nothing remains.
@@ -25,11 +25,14 @@ export function resolveMigrationsFolder(): string {
25
25
  return folder;
26
26
  }
27
27
 
28
- export function migrationFiles(folder: string): readonly string[] {
28
+ export function migrationFileIds(folder: string): readonly string[] {
29
29
  return readdirSync(folder)
30
30
  .filter((file) => /^\d{4}_.+\.sql$/.test(file))
31
- .sort()
32
- .map((file) => resolve(folder, file));
31
+ .sort();
32
+ }
33
+
34
+ export function migrationFiles(folder: string): readonly string[] {
35
+ return migrationFileIds(folder).map((file) => resolve(folder, file));
33
36
  }
34
37
 
35
38
  export function migrationIdFor(file: string): string {
@@ -309,6 +309,14 @@ export const providerAccounts = pgTable("provider_accounts", {
309
309
  * to an account that may well be refused.
310
310
  */
311
311
  quotaPlan: text("quota_plan"),
312
+ /**
313
+ * When `quota_plan` was last written by a successful quota read.
314
+ *
315
+ * The plan column is a bare string with no timestamp, so routing cannot tell
316
+ * a fresh plan from one recorded weeks ago. With the freshness horizon, a
317
+ * stale plan is downgraded to `unknown` instead of being trusted blindly.
318
+ */
319
+ quotaPlanFetchedAt: timestamp("quota_plan_fetched_at", { withTimezone: true }),
312
320
  /**
313
321
  * The last successful quota read for this account, as a JSON snapshot.
314
322
  *
@@ -808,6 +816,12 @@ export const providerRoutingSettings = pgTable(
808
816
  // dispatch automatically selects among every active network pool the
809
817
  // tenant owns (least-loaded/non-cooldown) — never an admin-pinned pool.
810
818
  skipProxy: boolean("skip_proxy").notNull().default(false),
819
+ // How device-code OAuth flows (start + poll) choose their egress:
820
+ // 'random' (default) routes them through a randomly selected active
821
+ // network pool so the upstream sees a rotating IP (avoids "multiple codes
822
+ // from one IP" detection for Cursor/Grok/device-code providers); 'direct'
823
+ // always dials direct egress.
824
+ deviceFlowProxy: text("device_flow_proxy").$type<DeviceFlowProxyMode>().notNull().default("random"),
811
825
  },
812
826
  (table) => ({
813
827
  tenantProviderUnique: uniqueIndex("provider_routing_settings_tenant_provider_idx").on(
@@ -827,6 +841,9 @@ export const poolRoutingStrategy = pgEnum("pool_routing_strategy", [
827
841
  ]);
828
842
  export type PoolRoutingStrategy = (typeof poolRoutingStrategy.enumValues)[number];
829
843
 
844
+ /** Egress mode for device-code OAuth flows: random active pool, or direct. */
845
+ export type DeviceFlowProxyMode = "random" | "direct";
846
+
830
847
  /**
831
848
  * Per-tenant selection strategy across the tenant's active network pools —
832
849
  * the pool-group counterpart to `provider_routing_settings`. `least_loaded`
@@ -1,4 +1,4 @@
1
- import { dedupeRequest } from "../network/deduplication";
1
+ import { dedupeRequest } from "../network/util/dedupe";
2
2
  /**
3
3
  * Bounded TTL cache with automatic eviction.
4
4
  *
@@ -11,6 +11,12 @@ interface TtlCacheOptions {
11
11
  readonly ttlMs?: number;
12
12
  readonly maxEntries?: number;
13
13
  readonly now?: () => number;
14
+ /**
15
+ * LRU eviction order. When set, re-setting an existing key moves it to the
16
+ * newest position (delete-then-set), so oldest-first eviction becomes
17
+ * least-recently-used-first. Off by default: plain FIFO insertion order.
18
+ */
19
+ readonly lru?: boolean;
14
20
  }
15
21
 
16
22
  interface TtlCacheEntry<T> {
@@ -27,11 +33,13 @@ export class TtlCache<T> {
27
33
  private readonly ttlMs: number;
28
34
  private readonly maxEntries: number;
29
35
  private readonly now: () => number;
36
+ private readonly lru: boolean;
30
37
 
31
38
  constructor(opts: TtlCacheOptions = {}) {
32
39
  this.ttlMs = opts.ttlMs ?? 5_000;
33
40
  this.maxEntries = opts.maxEntries ?? 128;
34
41
  this.now = opts.now ?? Date.now;
42
+ this.lru = opts.lru ?? false;
35
43
  }
36
44
 
37
45
  /**
@@ -54,10 +62,21 @@ export class TtlCache<T> {
54
62
  */
55
63
  set(key: string, value: T): void {
56
64
  const now = this.now();
65
+ if (this.lru) this.cache.delete(key); // move to newest position
57
66
  this.cache.set(key, { value, at: now });
58
67
  this.evictIfNeeded(now);
59
68
  }
60
69
 
70
+ /**
71
+ * Peek at a stored value without checking the TTL or deleting on expiry.
72
+ * Returns only undefined when the key is absent. Callers that need the
73
+ * possibly-expired value (e.g. a stale fallback after a failed refetch)
74
+ * must read it before `get`, which removes expired entries.
75
+ */
76
+ peek(key: string): T | undefined {
77
+ return this.cache.get(key)?.value;
78
+ }
79
+
61
80
  /**
62
81
  * Delete a specific key.
63
82
  */
@@ -144,8 +163,7 @@ interface TtlCacheFamilyOptions {
144
163
  /**
145
164
  * A set of {@link TtlCache} instances keyed by TTL, created on first use.
146
165
  *
147
- * Two provider-side caches (`operations/provider-version-cache.ts` and
148
- * `operations/model-discovery-cache.ts`) had each hand-rolled the same
166
+ * Two provider-side caches in `operations/provider-caches.ts` had each hand-rolled the same
149
167
  * `Map<number, TtlCache<T>>` + `cacheFor(ttlMs)` memo plus the same
150
168
  * `for (const cache of caches.values()) cache.clear()` reset. Tests pass
151
169
  * `ttlMs: 0` to force a reload, which is why the per-TTL split exists at all —
@@ -14,7 +14,7 @@ import type { CanonicalStopReason, CanonicalTerminalEvent, UsageRecord } from ".
14
14
  import { type WireFamily } from "./canonical-model";
15
15
  import type { GenerationControls } from "./canonical-model";
16
16
  import { BASE_PROTECTED_HEADERS, HEADER_CONTROL, HEADER_TOKEN } from "../security/outbound-headers";
17
- import { unwrapProviderToken } from "../providers/provider-credential-envelope";
17
+ import { unwrapProviderToken } from "../providers/provider-credentials";
18
18
 
19
19
  const TEXT_ENCODER = new TextEncoder();
20
20
  const TEXT_DECODER = new TextDecoder();
@@ -91,6 +91,63 @@ export function hash32(input: string): number {
91
91
 
92
92
  const CLAUDE_TOOL_PREFIX = "_";
93
93
 
94
+ /**
95
+ * Per-request tool-name rename map, mirroring the two-way alias pattern the
96
+ * OpenCode free-tier fingerprint uses: a caller tool whose name collides with
97
+ * an upstream-reserved name (or with the built-in prefix) travels under an
98
+ * alias, and the response side restores the caller's original name.
99
+ *
100
+ * The map is a bag of `{ sent, original }` pairs so a provider can rename at
101
+ * build time and restore at decode time without guessing from a fixed prefix
102
+ * (a fixed prefix collides when a caller already uses it — `_bash` from a
103
+ * caller named `bash` vs a caller already named `_bash`).
104
+ */
105
+ export interface ToolNameAlias {
106
+ /** Name sent to the upstream wire. */
107
+ readonly sent: string;
108
+ /** Caller's original name; restore to this on the way back. */
109
+ readonly original: string;
110
+ }
111
+
112
+ /** Restore a sent name to the caller's original, when it was renamed. */
113
+ export function restoreAliasedToolName(
114
+ sent: string,
115
+ aliases: ReadonlyArray<ToolNameAlias>,
116
+ ): string {
117
+ for (const alias of aliases) {
118
+ if (alias.sent === sent) return alias.original;
119
+ }
120
+ return sent;
121
+ }
122
+
123
+ /**
124
+ * Registry of per-request tool renames, keyed by the canonical request object
125
+ * (the same object the builder saw and the decoder receives). This is the
126
+ * transport-agnostic way to pair `encodeWireRequest` with `decodeWireResponse`
127
+ * without threading a new parameter through every adapter: a request records
128
+ * its aliases at build time, and any decoder that still has the request can
129
+ * restore the caller's original tool names exactly.
130
+ *
131
+ * The WeakMap keeps no lifetime: entries die with the request object.
132
+ */
133
+ const toolAliasesByRequest = new WeakMap<object, readonly ToolNameAlias[]>();
134
+
135
+ /** Record the rename map a request produced. Idempotent per request. */
136
+ export function recordToolAliases(request: object, aliases: readonly ToolNameAlias[]): void {
137
+ if (aliases.length === 0) return;
138
+ toolAliasesByRequest.set(request, aliases);
139
+ }
140
+
141
+ /** Read the rename map a request recorded, if any. */
142
+ export function toolAliasesFor(request: object): readonly ToolNameAlias[] {
143
+ return toolAliasesByRequest.get(request) ?? [];
144
+ }
145
+
146
+ /** Restore a tool name exactly from the request's recorded renames. */
147
+ export function restoreToolNameFor(request: object, sent: string): string {
148
+ return restoreAliasedToolName(sent, toolAliasesFor(request));
149
+ }
150
+
94
151
  export type ClaudeWireObject = Record<string, unknown>;
95
152
 
96
153
  /**
@@ -366,7 +423,7 @@ function sanitizeSchemaWalk(value: unknown, walk: RewriteWalk): unknown {
366
423
 
367
424
  // OAuth tool-name prefixing
368
425
 
369
- const ANTHROPIC_BUILTIN_TOOL_NAMES = new Set([
426
+ export const ANTHROPIC_BUILTIN_TOOL_NAMES = new Set([
370
427
  "web_search",
371
428
  "web_fetch",
372
429
  "code_execution",
@@ -8,6 +8,7 @@
8
8
  * - CodeBuddy emits strict blocks (`<|DSML| calls>…</|DSML| calls>`)
9
9
  * that must be parsed exactly, with declared-tool allowlisting.
10
10
  */
11
+ import { MarkupStreamParser } from "./markup-stream-parser";
11
12
  export interface DsmlToolCall {
12
13
  readonly id: string;
13
14
  readonly name: string;
@@ -74,102 +75,49 @@ interface DsmlParseResult {
74
75
  * With no allowlist the generic name rule applies; with one, undeclared names
75
76
  * reject the whole block. The caller feeds chunks with `push` and drains the
76
77
  * tail with `flush`.
78
+ *
79
+ * The stateful prefix-buffer loop is the shared {@link MarkupStreamParser}
80
+ * scaffold; this class supplies the DSML grammar as a thin configuration.
77
81
  */
78
82
  export class DsmlStreamParser {
79
- readonly #separators: readonly string[];
80
- readonly #blockNames: readonly string[];
81
- readonly #openers: readonly string[];
82
- readonly #allowedNames: ReadonlySet<string>;
83
- readonly #params: "json" | "string";
84
- readonly #sortKeys: boolean;
85
- readonly #maxBlockBytes: number;
86
- readonly #id: (call: { readonly name: string }, index: number, seq: number) => string;
87
- #pending = "";
88
- #callSequence = 0;
83
+ readonly #parser: MarkupStreamParser<DsmlToolCall>;
89
84
 
90
85
  constructor(options: DsmlParseOptions = {}) {
91
- this.#separators = options.separators ?? DEFAULT_SEPARATORS;
92
- this.#blockNames = options.blockNames ?? DEFAULT_BLOCK_NAMES;
93
- this.#openers = openers(this.#separators, this.#blockNames);
94
- this.#allowedNames = options.allowedNames ?? new Set();
95
- this.#params = options.params ?? "json";
96
- this.#sortKeys = options.sortKeys ?? true;
97
- this.#maxBlockBytes = options.maxBlockBytes ?? DEFAULT_MAX_BLOCK_BYTES;
98
- this.#id = options.id ?? sequentialId;
86
+ const separators = options.separators ?? DEFAULT_SEPARATORS;
87
+ const blockNames = options.blockNames ?? DEFAULT_BLOCK_NAMES;
88
+ const allowedNames = options.allowedNames ?? new Set();
89
+ const params = options.params ?? "json";
90
+ const sortKeys = options.sortKeys ?? true;
91
+ const maxBlockBytes = options.maxBlockBytes ?? DEFAULT_MAX_BLOCK_BYTES;
92
+ const id = options.id ?? sequentialId;
93
+ this.#parser = new MarkupStreamParser<DsmlToolCall>({
94
+ openers: openers(separators, blockNames),
95
+ classifyOpener: (_pending, _index, opener) => {
96
+ const separator = separators.find((value) => opener.startsWith(`<${value}DSML`));
97
+ const blockName = blockNames.find((value) => opener.endsWith(` ${value}>`));
98
+ if (separator === undefined || blockName === undefined) return { kind: "literal" };
99
+ const close = `</${separator}DSML${separator} ${blockName}>`;
100
+ return {
101
+ kind: "found",
102
+ closer: close,
103
+ parse: (block) =>
104
+ parseStrictBlock(block, opener, close, separator, allowedNames, params, sortKeys),
105
+ callId: (call, index, seq) => ({ ...call, id: id(call, index, seq) }),
106
+ };
107
+ },
108
+ onUnclosed: (pending, openIndex) =>
109
+ pending.length - openIndex > maxBlockBytes
110
+ ? { kind: "passThrough" }
111
+ : "hold",
112
+ });
99
113
  }
100
114
 
101
115
  push(chunk: string): DsmlParseResult {
102
- this.#pending += chunk;
103
- let cursor = 0;
104
- let visible = "";
105
- const calls: DsmlToolCall[] = [];
106
- for (;;) {
107
- let openIndex = -1;
108
- let opener = "";
109
- for (const candidate of this.#openers) {
110
- const index = this.#pending.indexOf(candidate, cursor);
111
- if (index >= 0 && (openIndex < 0 || index < openIndex)) {
112
- openIndex = index;
113
- opener = candidate;
114
- }
115
- }
116
- if (openIndex < 0) {
117
- const safeEnd = this.#settledEnd(cursor);
118
- visible += this.#pending.slice(cursor, safeEnd);
119
- cursor = safeEnd;
120
- break;
121
- }
122
- visible += this.#pending.slice(cursor, openIndex);
123
- const separator = this.#separators.find((value) => opener.startsWith(`<${value}DSML`));
124
- const blockName = this.#blockNames.find((value) => opener.endsWith(` ${value}>`));
125
- if (separator === undefined || blockName === undefined) {
126
- visible += "<";
127
- cursor = openIndex + 1;
128
- continue;
129
- }
130
- const close = `</${separator}DSML${separator} ${blockName}>`;
131
- const closeIndex = this.#pending.indexOf(close, openIndex + opener.length);
132
- if (closeIndex < 0) {
133
- // An unclosed block past the cap is not markup — pass it through as
134
- // text instead of buffering it indefinitely. Same fail-open as
135
- // `feed()` in the reference bridge's `MAX_BLOCK_BYTES` guard.
136
- if (this.#pending.length - openIndex > this.#maxBlockBytes) {
137
- visible += this.#pending.slice(openIndex);
138
- cursor = this.#pending.length;
139
- break;
140
- }
141
- cursor = openIndex;
142
- break;
143
- }
144
- const end = closeIndex + close.length;
145
- const block = this.#pending.slice(openIndex, end);
146
- const parsed = parseStrictBlock(block, opener, close, separator, this.#allowedNames, this.#params, this.#sortKeys);
147
- if (parsed === undefined) visible += block;
148
- else {
149
- parsed.forEach((call, index) => {
150
- calls.push({ ...call, id: this.#id(call, index, this.#callSequence++) });
151
- });
152
- }
153
- cursor = end;
154
- }
155
- this.#pending = this.#pending.slice(cursor);
156
- return { text: visible, calls };
116
+ return this.#parser.push(chunk);
157
117
  }
158
118
 
159
119
  flush(): DsmlParseResult {
160
- const text = this.#pending;
161
- this.#pending = "";
162
- return { text, calls: [] };
163
- }
164
-
165
- #settledEnd(start: number): number {
166
- const remaining = this.#pending.slice(start);
167
- const maxOpener = this.#openers.reduce((max, opener) => Math.max(max, opener.length - 1), 0);
168
- for (let length = Math.min(maxOpener, remaining.length); length > 0; length -= 1) {
169
- const suffix = remaining.slice(-length);
170
- if (this.#openers.some((opener) => opener.startsWith(suffix))) return this.#pending.length - length;
171
- }
172
- return this.#pending.length;
120
+ return this.#parser.flush();
173
121
  }
174
122
  }
175
123