cartethyia 2.6.7 → 2.7.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 (335) hide show
  1. package/.env.example +1 -7
  2. package/README.md +25 -2
  3. package/dist/dashboard/assets/{BannedPage-Dskhyp0l.js → BannedPage-D-J75hBM.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage--SJhk7qQ.js → CliToolDetailPage-CVe6RaBH.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-C203VT-c.js → CliToolsPage-Dd8ZSamp.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BcS7kGG1.js → CombosPage-QoJ6foxo.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-uujbBYwC.js → CustomizationPage-CKUFTcW4.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
  17. package/dist/dashboard/assets/LoginPage-DubdQcvX.js +1 -0
  18. package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-CA1kLs45.js → ModelPicker-BH8Tehdi.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +4 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.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-Bwotu8UJ.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +9 -0
  30. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
  31. package/dist/dashboard/assets/{QuotaPage-BueSWX5D.js → QuotaPage-ATpaxrbl.js} +1 -1
  32. package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
  33. package/dist/dashboard/assets/SettingsPage-CETophpx.js +1 -0
  34. package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-BdgECjqq.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
  37. package/dist/dashboard/assets/{SortableList-DrKeMcGN.js → SortableList-CAK-EN9z.js} +1 -1
  38. package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-qjuYtccc.js → StudioPage-B7W2_RhT.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-6T4EFDjW.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
  43. package/dist/dashboard/assets/{api-keys-C23S9sgp.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-DU6qrMA_.js → assert-67xXH5rf.js} +1 -1
  48. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-Bkz_-RrM.js → badge-DC2L43Mj.js} +1 -1
  50. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-C-7y3VKV.js → banner-BBVcb0J3.js} +1 -1
  52. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-BOPBuBzD.js → card-Cca1XC6G.js} +1 -1
  54. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-BaFUuFFF.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-e-Mlk0FH.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-Iec_ywbD.js → dialog-DFvWhwTa.js} +1 -1
  60. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-Bs8MIIHc.js → drawer-CFM2sMX1.js} +1 -1
  62. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-DOddwoc0.js → entry-BLBLxX0h.js} +3 -3
  64. package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
  65. package/dist/dashboard/assets/{entry-DcobaKFu.js → entry-Bqy8RFvM.js} +1 -1
  66. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  67. package/dist/dashboard/assets/{entry-BRszm4_Z.js → entry-_xHo5x6r.js} +1 -1
  68. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  69. package/dist/dashboard/assets/formatters-mpBPqAQ8.js +1 -0
  70. package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-BuDsrsPn.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-BeLRhsmt.js → index-D6QzEC_n.js} +2 -2
  74. package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-DToMICwl.js → inline-BFUcvllj.js} +1 -1
  76. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-WlyHWeG2.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-DaJ5baJr.js → layout-CBIyT6Rt.js} +1 -1
  80. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-B3oSK13z.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-ta6GMb-8.js → live-D0mm--ME.js} +1 -1
  84. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  85. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +1 -0
  86. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-g_fduiNw.js → mutation-sMD965oO.js} +1 -1
  88. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  89. package/dist/dashboard/assets/{numbers-Dulqgys5.js → numbers-BO9dKaFr.js} +1 -1
  90. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  91. package/dist/dashboard/assets/providers/inception.svg +5 -0
  92. package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
  93. package/dist/dashboard/assets/{query-keys-UETqfytp.js → query-keys-CfL_Zdoh.js} +1 -1
  94. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  95. package/dist/dashboard/assets/{routing-BPUo-W8d.js → routing-BnIpi1IJ.js} +1 -1
  96. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  97. package/dist/dashboard/assets/{select-BfZ3SCTM.js → select-Cj5Nw43g.js} +1 -1
  98. package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
  99. package/dist/dashboard/assets/settings-BAFhDgX8.js +1 -0
  100. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  101. package/dist/dashboard/assets/{stack-B1w5BM0u.js → stack-EMrOMt4I.js} +1 -1
  102. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  103. package/dist/dashboard/assets/{state-DhZBfWgx.js → state-CWoo7ty5.js} +1 -1
  104. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  105. package/dist/dashboard/assets/{switch-yD7D99Lg.js → switch-CCjT6IkF.js} +1 -1
  106. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  107. package/dist/dashboard/assets/{useMutation-EDFRkn1i.js → useMutation-DjkLHUcE.js} +1 -1
  108. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  109. package/dist/dashboard/index.html +1 -1
  110. package/dist/dashboard/index.html.gz +0 -0
  111. package/dist/dashboard/precompressed-assets.json +55 -54
  112. package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
  113. package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
  114. package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
  115. package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
  116. package/package.json +1 -1
  117. package/scripts/cli/console-menu.ts +5 -2
  118. package/scripts/cli/tray.ts +21 -6
  119. package/src/boot/app.ts +24 -62
  120. package/src/boot/dependencies.ts +732 -750
  121. package/src/config/cooldowns.ts +16 -0
  122. package/src/config/providers.ts +13 -1
  123. package/src/config/resources.ts +13 -80
  124. package/src/config/security.ts +2 -38
  125. package/src/config/spec.ts +16 -23
  126. package/src/console/console-router.ts +7 -7
  127. package/src/console/domain-registration.ts +19 -12
  128. package/src/console/providers/catalog/catalog-projections.ts +2 -6
  129. package/src/console/providers/catalog/contracts.ts +19 -0
  130. package/src/console/providers/catalog/provider-operations.ts +38 -0
  131. package/src/console/providers/catalog/routes.ts +14 -0
  132. package/src/console/providers/catalog/store-helpers.ts +326 -0
  133. package/src/console/providers/catalog/store.ts +1292 -1563
  134. package/src/console/quota/account-quota-global.ts +2 -2
  135. package/src/console/quota/account-quota-shared.ts +1 -1
  136. package/src/console/quota/account-quota-tenant.ts +35 -12
  137. package/src/console/quota/quota-view.ts +2 -2
  138. package/src/console/remote-support/contracts.ts +3 -1
  139. package/src/console/remote-support/service.ts +115 -6
  140. package/src/console/routing/pools/contracts.ts +41 -1
  141. package/src/console/routing/pools/operations.ts +183 -4
  142. package/src/console/routing/pools/relay-deploy.ts +17 -4
  143. package/src/console/routing/pools/routes.ts +30 -0
  144. package/src/console/routing/pools/store.ts +98 -8
  145. package/src/main.ts +16 -2
  146. package/src/network/outbound-fetch.ts +226 -365
  147. package/src/network/pool/agent.ts +8 -0
  148. package/src/network/pool/pool-state.ts +7 -0
  149. package/src/network/pool/selector.ts +110 -2
  150. package/src/network/pool-cooldown-store.ts +252 -0
  151. package/src/network/pool-health-machine.ts +5 -3
  152. package/src/network/pool-health.ts +36 -9
  153. package/src/observability/event-query.ts +57 -0
  154. package/src/observability/metrics.ts +15 -5
  155. package/src/observability/runtime-metrics.ts +30 -15
  156. package/src/persistence/health-event-flush.ts +2 -0
  157. package/src/persistence/schema.ts +19 -0
  158. package/src/persistence/usage-flush.ts +8 -3
  159. package/src/protocol/canonical-model.ts +1 -1
  160. package/src/protocol/dsml.ts +377 -0
  161. package/src/protocol/gateway-error.ts +0 -1
  162. package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
  163. package/src/protocol/request/chat.ts +7 -7
  164. package/src/protocol/request/codex.ts +2 -2
  165. package/src/protocol/request/gemini.ts +1 -1
  166. package/src/protocol/request/messages.ts +6 -6
  167. package/src/protocol/request/responses.ts +7 -7
  168. package/src/protocol/response/dsml.ts +12 -133
  169. package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
  170. package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
  171. package/src/{transport → protocol}/translation/capabilities.ts +2 -2
  172. package/src/{transport → protocol}/translation/normalize.ts +1 -1
  173. package/src/{transport → protocol}/translation/quirks.ts +1 -1
  174. package/src/{transport → protocol}/translation/search.ts +1 -1
  175. package/src/{transport → protocol}/translation/thinking.ts +23 -1
  176. package/src/{transport → protocol}/translation/tool-id.ts +1 -1
  177. package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
  178. package/src/protocol/upstream-parsing.ts +176 -0
  179. package/src/providers/adapters/compatible-adapter.ts +11 -2
  180. package/src/providers/authentication/oauth-refresh-service.ts +9 -5
  181. package/src/providers/default-registry.ts +21 -5
  182. package/src/providers/discovery/probe-phases.ts +16 -3
  183. package/src/providers/discovery/probing-service.ts +4 -1
  184. package/src/providers/integrations/anthropic.ts +1 -1
  185. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
  186. package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
  187. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
  188. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
  189. package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
  190. package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
  191. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
  192. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
  193. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
  194. package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
  195. package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
  196. package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
  197. package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
  198. package/src/providers/integrations/claude/claude.ts +1 -1
  199. package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
  200. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  201. package/src/providers/integrations/codex/codex.ts +1 -1
  202. package/src/providers/integrations/commandcode/commandcode.ts +134 -104
  203. package/src/providers/integrations/commandcode/discovery.ts +6 -5
  204. package/src/providers/integrations/factory/factory.ts +1 -1
  205. package/src/providers/integrations/github/github.ts +1 -1
  206. package/src/providers/integrations/grok/grok.ts +1 -1
  207. package/src/providers/integrations/inception/provider.ts +91 -0
  208. package/src/providers/integrations/inferhub.ts +1 -1
  209. package/src/providers/integrations/kiro/kiro-request.ts +1 -1
  210. package/src/providers/integrations/mistral.ts +193 -19
  211. package/src/providers/integrations/opencode/fingerprint.ts +19 -2
  212. package/src/providers/integrations/opencode/prompt.ts +111 -0
  213. package/src/providers/integrations/opencode/provider.ts +101 -23
  214. package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
  215. package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
  216. package/src/providers/integrations/qoder/qoder.ts +1641 -884
  217. package/src/providers/integrations/shared/install-id.ts +2 -1
  218. package/src/providers/integrations/trae/trae-cache.ts +110 -0
  219. package/src/providers/integrations/trae/trae-oauth.ts +29 -4
  220. package/src/providers/integrations/trae/trae-protocol.ts +2 -1
  221. package/src/providers/integrations/trae/trae.ts +106 -23
  222. package/src/providers/operations/account-error-classification.ts +1660 -0
  223. package/src/providers/operations/account-health-service.ts +621 -2097
  224. package/src/providers/operations/account-reset-service.ts +2 -0
  225. package/src/providers/provider-contracts.ts +0 -2
  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/abuse.ts +17 -0
  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 +224 -217
  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 +59 -178
  246. package/src/transport/guards/abuse-guard.ts +10 -0
  247. package/src/transport/guards/auth-guard.ts +0 -18
  248. package/src/transport/guards/error-lifecycle.ts +1 -1
  249. package/src/transport/guards/pipeline.ts +0 -6
  250. package/src/transport/guards/request-context.ts +18 -91
  251. package/src/transport/request/degradation.ts +20 -1
  252. package/src/transport/request/inflight.ts +4 -95
  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 +41 -31
  257. package/src/transport/routing/router.ts +2 -2
  258. package/src/transport/surface/messages/parse.ts +1 -1
  259. package/src/workers/buddy-growth-tasks.ts +346 -425
  260. package/src/workers/daily-checkin.ts +17 -6
  261. package/src/workers/quota-refresh-sweep.ts +2 -2
  262. package/src/workers/scheduler.ts +17 -1
  263. package/dist/dashboard/assets/BannedPage-Dskhyp0l.js.gz +0 -0
  264. package/dist/dashboard/assets/CliToolDetailPage--SJhk7qQ.js.gz +0 -0
  265. package/dist/dashboard/assets/CliToolsPage-C203VT-c.js.gz +0 -0
  266. package/dist/dashboard/assets/CombosPage-BcS7kGG1.js.gz +0 -0
  267. package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js +0 -1
  268. package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js.gz +0 -0
  269. package/dist/dashboard/assets/CustomizationPage-uujbBYwC.js.gz +0 -0
  270. package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js +0 -1
  271. package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js.gz +0 -0
  272. package/dist/dashboard/assets/LoginPage-DoOUthbG.js +0 -1
  273. package/dist/dashboard/assets/LoginPage-DoOUthbG.js.gz +0 -0
  274. package/dist/dashboard/assets/ModelPicker-CA1kLs45.js.gz +0 -0
  275. package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js +0 -1
  276. package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js.gz +0 -0
  277. package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js +0 -3
  278. package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js.gz +0 -0
  279. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
  280. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  281. package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js +0 -1
  282. package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js.gz +0 -0
  283. package/dist/dashboard/assets/ProxyPage-B9mBTweh.js +0 -7
  284. package/dist/dashboard/assets/ProxyPage-B9mBTweh.js.gz +0 -0
  285. package/dist/dashboard/assets/QuotaPage-BueSWX5D.js.gz +0 -0
  286. package/dist/dashboard/assets/SettingsPage-B0x00MVY.js +0 -1
  287. package/dist/dashboard/assets/SettingsPage-B0x00MVY.js.gz +0 -0
  288. package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js +0 -1
  289. package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js.gz +0 -0
  290. package/dist/dashboard/assets/SortableList-DrKeMcGN.js.gz +0 -0
  291. package/dist/dashboard/assets/StudioPage-qjuYtccc.js.gz +0 -0
  292. package/dist/dashboard/assets/UsagePage-Be7aNviQ.js +0 -56
  293. package/dist/dashboard/assets/UsagePage-Be7aNviQ.js.gz +0 -0
  294. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  295. package/dist/dashboard/assets/app-BUSiYL6F.css +0 -2
  296. package/dist/dashboard/assets/app-BUSiYL6F.css.gz +0 -0
  297. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  298. package/dist/dashboard/assets/badge-Bkz_-RrM.js.gz +0 -0
  299. package/dist/dashboard/assets/banner-C-7y3VKV.js.gz +0 -0
  300. package/dist/dashboard/assets/card-BOPBuBzD.js.gz +0 -0
  301. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  302. package/dist/dashboard/assets/clipboard-button-e-Mlk0FH.js.gz +0 -0
  303. package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
  304. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  305. package/dist/dashboard/assets/dialog-Iec_ywbD.js.gz +0 -0
  306. package/dist/dashboard/assets/drawer-Bs8MIIHc.js.gz +0 -0
  307. package/dist/dashboard/assets/entry-BRszm4_Z.js.gz +0 -0
  308. package/dist/dashboard/assets/entry-DOddwoc0.js.gz +0 -0
  309. package/dist/dashboard/assets/entry-DcobaKFu.js.gz +0 -0
  310. package/dist/dashboard/assets/github-badge-BuDsrsPn.js.gz +0 -0
  311. package/dist/dashboard/assets/index-BeLRhsmt.js.gz +0 -0
  312. package/dist/dashboard/assets/inline-DToMICwl.js.gz +0 -0
  313. package/dist/dashboard/assets/input-WlyHWeG2.js.gz +0 -0
  314. package/dist/dashboard/assets/layout-DaJ5baJr.js.gz +0 -0
  315. package/dist/dashboard/assets/list-row-B3oSK13z.js.gz +0 -0
  316. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  317. package/dist/dashboard/assets/model-bans-BmYTmbAk.js +0 -1
  318. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  319. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  320. package/dist/dashboard/assets/numbers-Dulqgys5.js.gz +0 -0
  321. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  322. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  323. package/dist/dashboard/assets/select-BfZ3SCTM.js.gz +0 -0
  324. package/dist/dashboard/assets/settings--3hxSyry.js +0 -1
  325. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  326. package/dist/dashboard/assets/stack-B1w5BM0u.js.gz +0 -0
  327. package/dist/dashboard/assets/state-DhZBfWgx.js.gz +0 -0
  328. package/dist/dashboard/assets/switch-yD7D99Lg.js.gz +0 -0
  329. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  330. package/src/console/admin/model-abuse/contracts.ts +0 -38
  331. package/src/console/admin/model-abuse/routes.ts +0 -97
  332. package/src/security/model-abuse.ts +0 -704
  333. package/src/transport/guards/concurrency-guard.ts +0 -125
  334. /package/dist/dashboard/assets/{app-MdVfwraB.js → app-BaWd0puM.js} +0 -0
  335. /package/dist/dashboard/assets/{app-MdVfwraB.js.gz → app-BaWd0puM.js.gz} +0 -0
@@ -168,6 +168,21 @@ export function resolveAccountDisabledRecoveryMs(): number {
168
168
  return _disabledRecovery;
169
169
  }
170
170
 
171
+ let _poolProviderRateLimitCooldown: number | undefined;
172
+ /**
173
+ * Fallback cooldown for a provider-scoped egress 429 when the upstream named no
174
+ * window (default 1h). See `CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS`.
175
+ * An upstream `retryAfterMs` always wins over this.
176
+ */
177
+ export function resolvePoolProviderRateLimitCooldownMs(): number {
178
+ if (_poolProviderRateLimitCooldown === undefined)
179
+ _poolProviderRateLimitCooldown = readInt(
180
+ "CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS",
181
+ CONFIG_SPEC.CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS,
182
+ );
183
+ return _poolProviderRateLimitCooldown;
184
+ }
185
+
171
186
  /**
172
187
  * Resets all cached cooldown values. Call this in tests that override
173
188
  * cooldown env-vars between test cases.
@@ -180,6 +195,7 @@ export function resetCooldownCacheForTests(): void {
180
195
  _unclassifiedCooldown = undefined;
181
196
  _escalationMaxCooldown = undefined;
182
197
  _poolCooldown = undefined;
198
+ _poolProviderRateLimitCooldown = undefined;
183
199
  _authRejectedCooldown = undefined;
184
200
  _policyBlockCooldown = undefined;
185
201
  _disabledRecovery = undefined;
@@ -4,7 +4,7 @@
4
4
  * public origin.
5
5
  */
6
6
 
7
- import { CONFIG_SPEC, readInt, readRequired, readText } from "./spec";
7
+ import { CONFIG_SPEC, readInt, readOptionalText, readRequired, readText } from "./spec";
8
8
 
9
9
  /** Reads and normalizes the public OAuth origin. */
10
10
  export function requirePublicOrigin(): string {
@@ -48,6 +48,18 @@ export function resolveKimiQuotaBaseUrl(): string {
48
48
  );
49
49
  }
50
50
 
51
+ /**
52
+ * Operator override for the directory installation identities are persisted in.
53
+ * Read here so the only literal `process.env` for this value stays in config;
54
+ * the providers that stamp an install id consume the resolver instead.
55
+ */
56
+ export function resolveInstallIdDir(): string | undefined {
57
+ return readOptionalText(
58
+ "CARTETHYIA_INSTALL_ID_DIR",
59
+ CONFIG_SPEC.CARTETHYIA_INSTALL_ID_DIR,
60
+ );
61
+ }
62
+
51
63
  /**
52
64
  * Operator override for the OpenCode free-tier ids upstream refuses, or
53
65
  * `undefined` when unset — meaning the bundled, measured list stands.
@@ -1,40 +1,26 @@
1
1
  /**
2
2
  * Canonical resource policy: how much memory this process may use, and how
3
- * much work it may hold at once.
3
+ * much room it must keep free.
4
4
  *
5
- * These two numbers were previously decided in three unrelated places — a
6
- * hardcoded cap in `concurrency-guard.ts`, a host-wide `os.totalmem()` in the
7
- * headroom guard, and a fallback constant in `runtime-metrics.ts` — which
8
- * meant a container limited to 512 MiB admitted the same concurrency as a 4 GiB
9
- * one and only started shedding once the *host* was nearly full. On a 1-core /
10
- * 2 GiB VPS that is the difference between shedding early and being
11
- * OOM-killed, and the OOM kills the dashboard too — the one surface that would
12
- * have explained what happened.
5
+ * The memory figure used to be decided in unrelated places — a host-wide
6
+ * `os.totalmem()` in the headroom guard, a fallback constant in
7
+ * `runtime-metrics.ts` — which meant a container limited to 512 MiB shed on
8
+ * the same threshold as a 4 GiB one and only reacted once the *host* was
9
+ * nearly full. On a 1-core / 2 GiB VPS that is the difference between
10
+ * shedding early and being OOM-killed, and the OOM kills the dashboard too —
11
+ * the one surface that would have explained what happened.
13
12
  *
14
- * Everything here is derived from one limit and one body ceiling, so the
15
- * numbers cannot disagree with each other:
16
- *
17
- * concurrency = floor(limit × BUDGET_FRACTION / (TRANSIENT_COPIES × body))
18
- *
19
- * The transient-copies factor is why this is not simply "limit / body": a
20
- * request body is held more than once between the socket read, the JSON parse,
21
- * and the upstream re-serialization, so budgeting one copy per request
22
- * understates the real hold by a factor of about two.
13
+ * How many requests may run at once is NOT decided here. That is admission's
14
+ * job: the tenant ceiling and each key's own limit, both set in Settings. A
15
+ * second, process-wide ceiling derived from memory sat below the number an
16
+ * operator could configure and refused requests that every configured limit
17
+ * still allowed, which is why it is gone.
23
18
  */
24
19
  import { CONFIG_SPEC, readOptionalInt } from "./spec";
25
- import { resolveMaxBodyBytes } from "./runtime";
26
20
 
27
21
  /** Used when no limit is discoverable at all (native dev, macOS, Windows). */
28
22
  export const FALLBACK_MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
29
23
 
30
- /**
31
- * Ceiling on concurrent in-flight requests per process.
32
- *
33
- * A hard ceiling even when memory would allow more: this process is one core,
34
- * and past this point queueing delay, not memory, is what degrades. It also
35
- * bounds every per-request structure that is sized by concurrency.
36
- */
37
- export const MAX_PROCESS_CONCURRENCY = 64;
38
24
 
39
25
  /**
40
26
  * How many copies of a request body are resident at once.
@@ -52,35 +38,6 @@ export const REQUEST_BODY_BUDGET_FRACTION = 0.25;
52
38
  /** Share of the memory limit kept free for allocation spikes and GC slack. */
53
39
  export const MEMORY_HEADROOM_FRACTION = 0.2;
54
40
 
55
- /**
56
- * Concurrency this process should admit, derived from the memory limit and the
57
- * body ceiling rather than guessed.
58
- *
59
- * Worked examples, which are the point of having one formula:
60
- * - 2 GiB limit, 4 MiB bodies → 64 (the ceiling; memory is not the binder)
61
- * - 512 MiB limit, 4 MiB bodies → 16
62
- * - 256 MiB limit, 16 MiB bodies → 2
63
- *
64
- * An explicit override is honoured, clamped to 1..MAX_PROCESS_CONCURRENCY. It
65
- * is the operator's call and is never silently raised back to the derived
66
- * value; exceeding the derived figure is a deliberate choice, so it is
67
- * reported at the call site rather than corrected here.
68
- */
69
- export function deriveProcessConcurrency(options: {
70
- readonly memoryLimitBytes: number;
71
- readonly maxBodyBytes: number;
72
- readonly override?: number | undefined;
73
- }): number {
74
- if (options.override !== undefined) {
75
- if (!Number.isInteger(options.override)) return 1;
76
- return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, options.override));
77
- }
78
- const body = Math.max(1, options.maxBodyBytes);
79
- const perRequest = REQUEST_BODY_TRANSIENT_COPIES * body;
80
- const budget = Math.max(1, Math.floor(options.memoryLimitBytes * REQUEST_BODY_BUDGET_FRACTION));
81
- const derived = Math.floor(budget / perRequest);
82
- return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, derived));
83
- }
84
41
 
85
42
  /**
86
43
  * Bytes kept free below the memory limit.
@@ -94,16 +51,6 @@ export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
94
51
  return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
95
52
  }
96
53
 
97
- /**
98
- * The optional operator override, read through the config spec so "unset" and
99
- * "out of range" are treated the way every other knob treats them.
100
- */
101
- export function resolveProcessConcurrencyOverride(): number | undefined {
102
- return readOptionalInt(
103
- "CARTETHYIA_PROCESS_CONCURRENCY_LIMIT",
104
- CONFIG_SPEC.CARTETHYIA_PROCESS_CONCURRENCY_LIMIT,
105
- );
106
- }
107
54
 
108
55
  /**
109
56
  * Slack added on top of every key's concurrency limit.
@@ -121,17 +68,3 @@ export function resolveConcurrencyTolerance(): number {
121
68
  return value === undefined ? 0 : Math.max(0, value);
122
69
  }
123
70
 
124
- /**
125
- * The effective concurrency for this process.
126
- *
127
- * Takes the limit as an argument rather than resolving it here: the limit
128
- * owner is `resolveMemoryLimitBytes()` in `observability/runtime-metrics.ts`,
129
- * and duplicating that resolution would let the two drift.
130
- */
131
- export function resolveProcessConcurrency(memoryLimitBytes: number): number {
132
- return deriveProcessConcurrency({
133
- memoryLimitBytes,
134
- maxBodyBytes: resolveMaxBodyBytes(),
135
- override: resolveProcessConcurrencyOverride(),
136
- });
137
- }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Security resolvers: the application encryption key, the abuse-protection
3
- * ceiling, and model-abuse strike/ban windows.
2
+ * Security resolvers: the application encryption key and the abuse-protection
3
+ * ceiling.
4
4
  */
5
5
 
6
6
  import { CONFIG_SPEC, readInt, readRequired } from "./spec";
@@ -16,42 +16,6 @@ export function resolveIpRateLimit(): number {
16
16
  return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
17
17
  }
18
18
 
19
- /** Invalid-model attempts inside the window that ban a client address. */
20
- export function resolveModelStrikeThreshold(): number {
21
- return readInt(
22
- "CARTETHYIA_MODEL_STRIKE_THRESHOLD",
23
- CONFIG_SPEC.CARTETHYIA_MODEL_STRIKE_THRESHOLD,
24
- );
25
- }
26
-
27
- /** Sliding window (ms) that keeps an invalid-model attempt countable. */
28
- export function resolveModelStrikeWindowMs(): number {
29
- return readInt(
30
- "CARTETHYIA_MODEL_STRIKE_WINDOW_MS",
31
- CONFIG_SPEC.CARTETHYIA_MODEL_STRIKE_WINDOW_MS,
32
- );
33
- }
34
-
35
- /** How long (ms) a model-abuse ban lasts before it lapses on its own. */
36
- export function resolveModelBanTtlMs(): number {
37
- return readInt("CARTETHYIA_MODEL_BAN_TTL_MS", CONFIG_SPEC.CARTETHYIA_MODEL_BAN_TTL_MS);
38
- }
39
-
40
- /**
41
- * The longest model-abuse ban the console will record, however long a request
42
- * asks for.
43
- *
44
- * A separate knob from the ban TTL because the two are different decisions:
45
- * the TTL is what a graduated ban gets, this is the ceiling on what an
46
- * operator may ask for by hand. Raising one was never meant to raise the other.
47
- */
48
- export function resolveModelBanMaxTtlMs(): number {
49
- return readInt(
50
- "CARTETHYIA_MODEL_BAN_MAX_TTL_MS",
51
- CONFIG_SPEC.CARTETHYIA_MODEL_BAN_MAX_TTL_MS,
52
- );
53
- }
54
-
55
19
  /** Reads the application encryption key without caching it. */
56
20
  export function requireEncryptionKeyEnv(): string {
57
21
  return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
@@ -90,10 +90,9 @@ export const CONFIG_SPEC = {
90
90
  CARTETHYIA_SERVER_MAX_BODY_BYTES: {
91
91
  kind: "int",
92
92
  // A 500K-token context is roughly 2 MB of text, so 4 MB admits every real
93
- // request while halving the worst-case hold. Worst-case retained bytes
94
- // across the process is `PROCESS_CONCURRENCY_CAP × this`, so on a 1 GB box
95
- // the product should stay within about 40% of the limit; an operator with
96
- // larger payloads raises this.
93
+ // request while halving the worst-case hold. How many may be in flight is
94
+ // admission's job, so an operator with larger payloads raises this and
95
+ // sets the ceiling in Settings.
97
96
  default: 4 * 1024 * 1024,
98
97
  min: 1024,
99
98
  max: Number.MAX_SAFE_INTEGER,
@@ -119,7 +118,6 @@ export const CONFIG_SPEC = {
119
118
  // Optional-int, not int-with-a-zero-default: "unset" and "explicitly zero"
120
119
  // must not be the same thing. Zero is not a valid ceiling, so a default of 0
121
120
  // would need a second sentinel to mean "derive".
122
- CARTETHYIA_PROCESS_CONCURRENCY_LIMIT: { kind: "optional-int", min: 1, max: 64 },
123
121
  // Slack above each key's concurrency limit. Effective ceiling is
124
122
  // limit + tolerance; only requests past it are refused.
125
123
  CARTETHYIA_CONCURRENCY_TOLERANCE: { kind: "optional-int", min: 0, max: 1024 },
@@ -139,6 +137,7 @@ export const CONFIG_SPEC = {
139
137
  // requires a loopback peer and a timing-safe token match; unset = the route
140
138
  // does not exist.
141
139
  CARTETHYIA_DRAIN_TOKEN: { kind: "optional-text" },
140
+ CARTETHYIA_INSTALL_ID_DIR: { kind: "optional-text" },
142
141
 
143
142
  // Network policy (trusted proxy + SSRF)
144
143
  TRUSTED_PROXY_CIDRS: { kind: "list" },
@@ -242,6 +241,18 @@ export const CONFIG_SPEC = {
242
241
  },
243
242
  CARTETHYIA_ACCOUNT_COOLDOWN_ESCALATION_MAX_MS: { kind: "int", default: 3_600_000, min: 0, max: 86_400_000 },
244
243
  CARTETHYIA_POOL_COOLDOWN_MS: { kind: "int", default: 120_000, min: 0, max: 86_400_000 },
244
+ /**
245
+ * A provider-scoped egress cooldown (a 429 the pool itself earned) when the
246
+ * upstream named no window. Default 1h: an egress that exhausted a
247
+ * request-counted allowance stays refused long past minute 15, and a short
248
+ * default sent the pool back into rotation to be refused again.
249
+ */
250
+ CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS: {
251
+ kind: "int",
252
+ default: 3_600_000,
253
+ min: 0,
254
+ max: 86_400_000,
255
+ },
245
256
  CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
246
257
  CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
247
258
  CARTETHYIA_ACCOUNT_DISABLED_RECOVERY_MS: { kind: "int", default: 86_400_000, min: 0, max: 604_800_000 },
@@ -252,24 +263,6 @@ export const CONFIG_SPEC = {
252
263
 
253
264
  // Security (encryption key, public origin, abuse ceiling)
254
265
  IP_RATE_MAX_PER_WINDOW: { kind: "int", default: 240, min: 1, max: Number.MAX_SAFE_INTEGER },
255
- // Invalid-model attempts inside the sliding window that ban the client
256
- // address, how long an attempt stays countable, and how long the ban lasts.
257
- // A valid request does not reset the window.
258
- CARTETHYIA_MODEL_STRIKE_THRESHOLD: { kind: "int", default: 10, min: 1, max: 100 },
259
- CARTETHYIA_MODEL_STRIKE_WINDOW_MS: { kind: "int", default: 1_800_000, min: 1_000, max: 86_400_000 },
260
- CARTETHYIA_MODEL_BAN_TTL_MS: { kind: "int", default: 21_600_000, min: 1_000, max: 604_800_000 },
261
- /**
262
- * The longest ban the console will record, however long a request asks for.
263
- * The "Permanent" choice is a very long TTL rather than an unbounded one,
264
- * and this is what keeps a typo in a duration from parking a caller for a
265
- * decade.
266
- */
267
- CARTETHYIA_MODEL_BAN_MAX_TTL_MS: {
268
- kind: "int",
269
- default: 604_800_000,
270
- min: 1_000,
271
- max: 604_800_000,
272
- },
273
266
  CARTETHYIA_ENCRYPTION_KEY: {
274
267
  kind: "required",
275
268
  error:
@@ -26,11 +26,11 @@ import type { NetworkPoolSelector } from "../network/pool/selector";
26
26
  import type { TelemetryBatchBuffer } from "../observability/telemetry-buffer";
27
27
  import type { OAuthRefreshService } from "../providers/authentication/oauth-refresh-service";
28
28
  import type { ApiKeyAdmissionService } from "../security/admission/service";
29
- import type { ModelStrikeService } from "../security/model-abuse";
30
29
  import type { RedisClient } from "../persistence/redis";
31
30
  import { CliToolMappingStore } from "./cli-tools/store";
32
31
  import { CliToolService } from "./cli-tools/service";
33
32
  import { DrizzleCliToolSecretSource } from "./cli-tools/secret-source";
33
+ import type { ShareLinkStore } from "../persistence/stores/share-store";
34
34
  import type { ProviderRegistry } from "../providers/provider-registry";
35
35
  import type { BundledProviderCatalog } from "../providers/operations/provider-catalog-service";
36
36
  import type { ValidatedNetworkBindingFactory } from "../network/pool/resolver";
@@ -65,13 +65,13 @@ export interface ConsoleApiCompositionDeps {
65
65
  readonly networkBindingFactory: ValidatedNetworkBindingFactory;
66
66
  readonly redis: RedisClient | undefined;
67
67
  readonly oauthRefreshService: OAuthRefreshService;
68
- readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
69
68
  /**
70
- * Graduated model-abuse strikes: list and lift bans from the console.
71
- * Optional so reduced compositions (route-only shell, console stubs) stay
72
- * valid; absent means the `/model-bans` routes are not mounted.
69
+ * Share-link store shared with the public share router, so both surfaces
70
+ * wrap the same table through one instance. Optional: absent means the
71
+ * console builds its own store from `db`.
73
72
  */
74
- readonly modelStrikes?: Pick<ModelStrikeService, "listBans" | "unban" | "ban">;
73
+ readonly shareStore?: ShareLinkStore;
74
+ readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
75
75
  readonly readRoutingAccountInflight?:
76
76
  | ((
77
77
  providerId: string,
@@ -190,11 +190,11 @@ export function createConsoleRouter(deps: ConsoleApiCompositionDeps): Elysia {
190
190
  poolSelector,
191
191
  telemetryBuffer,
192
192
  ...(deps.stateStore ? { stateStore: deps.stateStore } : {}),
193
+ ...(deps.shareStore ? { shareStore: deps.shareStore } : {}),
193
194
  providerRegistry: deps.providerRegistry,
194
195
  bundledModelCatalog: deps.bundledModelCatalog,
195
196
  networkBindingFactory: deps.networkBindingFactory,
196
197
  admissionService: deps.admissionService,
197
- ...(deps.modelStrikes ? { modelStrikes: deps.modelStrikes } : {}),
198
198
  readRoutingAccountInflight: deps.readRoutingAccountInflight,
199
199
  ...(deps.flushBuffers ? { flushBuffers: deps.flushBuffers } : {}),
200
200
  credentialService,
@@ -34,7 +34,6 @@ import { DrizzleObservabilityStore } from "./observability/store";
34
34
  import { createLiveRoutes } from "./observability/live";
35
35
  import { createLogRoutes } from "./observability/logs";
36
36
  import { createAuditRoutes } from "./audit/routes";
37
- import { createModelAbuseRoutes } from "./admin/model-abuse/routes";
38
37
  import { DrizzleAuditReadStore } from "./audit/store";
39
38
  import { createPerformanceRoutes } from "./observability/performance";
40
39
  import { createUpdateRoutes } from "./updates/routes";
@@ -47,6 +46,7 @@ import type { ConsoleCredentialService } from "./auth/credentials";
47
46
  import { resolveTelemetryRetentionDays } from "../config/telemetry";
48
47
  import { payloadRetentionMs, resolvePayloadCapture } from "../observability/payload-capture";
49
48
  import { TELEMETRY_EVENT_DIR } from "../observability/event-log";
49
+ import { providerUsageMatrix } from "../observability/event-query";
50
50
 
51
51
  import { InMemoryOAuthFlowStore, OAuthFlowStore } from "../providers/authentication/oauth-flow-store";
52
52
  import { OAuthCallbackListener } from "./providers/oauth/callback-listener";
@@ -56,7 +56,10 @@ import {
56
56
  } from "../providers/operations/provider-credential-service";
57
57
  import { syncByokProvider } from "../providers/operations/provider-catalog-service";
58
58
  import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
59
- import { DrizzleShareLinkStore } from "../persistence/stores/share-store";
59
+ import {
60
+ DrizzleShareLinkStore,
61
+ type ShareLinkStore,
62
+ } from "../persistence/stores/share-store";
60
63
  import { createShareUsagePort } from "./share/share-usage";
61
64
  import { resolveSsrfPolicy } from "../config/network";
62
65
  import { createValidatedFetch } from "../network/outbound-fetch";
@@ -65,7 +68,6 @@ import type { CliToolService } from "./cli-tools/service";
65
68
  import type { NetworkPoolSelector } from "../network/pool/selector";
66
69
  import type { LiveInFlightSource } from "./observability/live";
67
70
  import type { ApiKeyAdmissionService } from "../security/admission/service";
68
- import type { ModelStrikeService } from "../security/model-abuse";
69
71
  import type { TelemetryBatchBuffer } from "../observability/telemetry-buffer";
70
72
  import type { OAuthRefreshService } from "../providers/authentication/oauth-refresh-service";
71
73
  import { resolveRedisBackend } from "../persistence/redis";
@@ -101,9 +103,13 @@ export interface ConsoleDomainContext {
101
103
  };
102
104
  /** Owns the live in-flight gauge the console surfaces. */
103
105
  readonly stateStore?: LiveInFlightSource;
106
+ /**
107
+ * Share-link store shared with the public share router, so both surfaces
108
+ * wrap the same table through one instance. Falls back to a fresh store
109
+ * when absent (reduced compositions, tests).
110
+ */
111
+ readonly shareStore?: ShareLinkStore;
104
112
  readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
105
- /** Graduated model-abuse strikes: list and lift bans from the console. */
106
- readonly modelStrikes?: Pick<ModelStrikeService, "listBans" | "unban" | "ban">;
107
113
  readonly readRoutingAccountInflight?:
108
114
  | ((
109
115
  providerId: string,
@@ -229,7 +235,7 @@ export function registerConsoleDomains(
229
235
  const runtimeSettingsStore = new DrizzleRuntimeSettingsStore(ctx.db, resolveRedisBackend(ctx.redis));
230
236
  const modelRoutingStore = new DrizzleModelRoutingStore(ctx.db);
231
237
  const apiKeyStore = new DrizzleApiKeyStore(ctx.db);
232
- const shareStore = new DrizzleShareLinkStore(ctx.db);
238
+ const shareStore = ctx.shareStore ?? new DrizzleShareLinkStore(ctx.db);
233
239
  const shareActivity = createShareUsagePort({ db: ctx.db, redis: ctx.redis });
234
240
 
235
241
 
@@ -259,12 +265,6 @@ export function registerConsoleDomains(
259
265
  resolveConfigKey: () => inferhubKeys.read(),
260
266
  keyStore: inferhubKeys,
261
267
  }));
262
- if (ctx.modelStrikes) {
263
- console.use(createModelAbuseRoutes({
264
- strikes: ctx.modelStrikes,
265
- accessResolver: ctx.accessResolver,
266
- }));
267
- }
268
268
  console.use(createProviderCatalogRoutes({
269
269
  store: providerCatalogStore,
270
270
  accessResolver: ctx.accessResolver,
@@ -280,6 +280,13 @@ export function registerConsoleDomains(
280
280
  aliases: await modelRoutingStore.listAliases(tenantId),
281
281
  combos: await modelRoutingStore.listCombos(tenantId),
282
282
  }),
283
+ providerUsageMatrix: (tenantId, providerId, days) =>
284
+ providerUsageMatrix(
285
+ ctx.db,
286
+ tenantId,
287
+ providerId,
288
+ new Date(Date.now() - days * 86_400_000),
289
+ ),
283
290
  }));
284
291
  console.use(createProviderDetailRoutes({
285
292
  store: providerDetailStore,
@@ -9,6 +9,7 @@
9
9
  * segment. `mapProviderRow` is the single `providers` row projection.
10
10
  */
11
11
  import { isBundledProviderId } from "../../../providers/provider-contracts";
12
+ import { BUILTIN_DEFAULT_ENDPOINTS } from "../../../protocol/codecs";
12
13
 
13
14
  import { providerDisplayName, providerServiceKinds } from "../../../providers/provider-metadata";
14
15
  import type { CompatibilityProfile } from "../../../providers/provider-metadata";
@@ -26,12 +27,7 @@ export interface ProviderDiscoveryLookup {
26
27
  hasModelDiscovery(providerId: string): boolean;
27
28
  }
28
29
 
29
- export const DEFAULT_ENDPOINT_BY_WIRE_FAMILY: Record<WireFamily, string> = {
30
- chat: "/v1/chat/completions",
31
- responses: "/v1/responses",
32
- messages: "/v1/messages",
33
- gemini: "/api/llm/g/v1/generate",
34
- };
30
+ export const DEFAULT_ENDPOINT_BY_WIRE_FAMILY: Record<WireFamily, string> = BUILTIN_DEFAULT_ENDPOINTS;
35
31
  export function endpointPathForProviderModel(
36
32
  wireFamily: WireFamily,
37
33
  compatibilityProfile: unknown,
@@ -557,6 +557,14 @@ export interface ProviderAccountExport {
557
557
  accessToken: string;
558
558
  /** Decrypted OAuth refresh token, omitted when the account has none. */
559
559
  refreshToken?: string;
560
+ /**
561
+ * Non-secret upstream auth configuration carried on the account (the
562
+ * upstream user id, provisioned region, auth method, profile). Re-import
563
+ * must restore it: Trae refresh is addressed per upstream user id, and the
564
+ * data-plane host is region-scoped, so dropping it breaks re-imported
565
+ * accounts on their first refresh or dispatch.
566
+ */
567
+ authState?: Readonly<Record<string, unknown>>;
560
568
  createdAt: string;
561
569
  /** Live routing admission count at export time; absent when unavailable. */
562
570
  inflight?: number;
@@ -771,6 +779,17 @@ export interface ProviderCatalogStore {
771
779
  syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
772
780
  listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
773
781
  listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
782
+ /**
783
+ * Non-secret upstream auth configuration for the given owned accounts,
784
+ * keyed by account id. Used by the account-export route so a re-import can
785
+ * restore the upstream user id / region that refresh and dispatch are
786
+ * addressed by; never surfaced on the read models.
787
+ */
788
+ listAccountAuthStates(
789
+ tenantId: string,
790
+ providerId: string,
791
+ accountIds: readonly string[],
792
+ ): Promise<Readonly<Record<string, Readonly<Record<string, unknown>>>>>;
774
793
  createAccount(
775
794
  tenantId: string,
776
795
  providerId: string,
@@ -17,6 +17,7 @@ import {
17
17
  requireTenantAnyScope,
18
18
  } from "../../shared/errors";
19
19
  import type { AccessDecision } from "../../../security/access-control";
20
+ import type { ProviderUsageDayRow } from "../../../observability/event-query";
20
21
  import { isBundledProviderId } from "../../../providers/provider-contracts";
21
22
  import type { ProviderRegistry } from "../../../providers/provider-registry";
22
23
  import { getProviderClientVersion, getRouteIdentityVersions } from "../../../providers/operations/client-versions";
@@ -146,6 +147,16 @@ export interface ProviderCatalogConfig {
146
147
  readonly members: readonly string[];
147
148
  }[];
148
149
  }>;
150
+ /**
151
+ * Day × model usage aggregate feeding the provider's Activity Matrix card.
152
+ * Optional so hosts without telemetry reads (and existing test doubles) keep
153
+ * the route returning an empty matrix instead of failing.
154
+ */
155
+ readonly providerUsageMatrix?: (
156
+ tenantId: string,
157
+ providerId: string,
158
+ days: number,
159
+ ) => Promise<readonly ProviderUsageDayRow[]>;
149
160
  /**
150
161
  * Resolves the stored credential and optional OAuth refresh token for export,
151
162
  * decrypting them without applying provider-specific dispatch codecs.
@@ -249,6 +260,19 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
249
260
  throw new ConsoleDomainError("provider_not_found", 404, `Provider ${providerId} not found`);
250
261
  return attachProviderCapabilities(sanitizeProviderResponse(rec, true), config.providerRegistry);
251
262
  },
263
+ async getUsageMatrix(
264
+ access: AccessDecision | undefined,
265
+ providerId: string,
266
+ days: number,
267
+ ): Promise<{ readonly days: number; readonly rows: readonly ProviderUsageDayRow[] }> {
268
+ const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
269
+ // Bounded window even on a hostile query string: the card reads a
270
+ // calendar, not history, and the aggregate scan cost grows with `days`.
271
+ const bounded = Math.min(Math.max(Math.trunc(days) || 1, 1), 366);
272
+ if (!config.providerUsageMatrix) return { days: bounded, rows: [] };
273
+ const rows = await config.providerUsageMatrix(a.tenantId, providerId, bounded);
274
+ return { days: bounded, rows };
275
+ },
252
276
  async createProvider(
253
277
  access: AccessDecision | undefined,
254
278
  request: CreateProviderRequest,
@@ -639,6 +663,11 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
639
663
  const selected = owned.filter(
640
664
  (account) => wanted.has(account.id) && account.tenantId === a.tenantId,
641
665
  );
666
+ const authStates = await config.store.listAccountAuthStates(
667
+ a.tenantId,
668
+ providerId,
669
+ selected.map((account) => account.id),
670
+ );
642
671
  const accounts = await Promise.all(
643
672
  selected.map(async (account): Promise<ProviderAccountExport> => {
644
673
  let accessToken = "";
@@ -661,6 +690,15 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
661
690
  status: account.status,
662
691
  accessToken,
663
692
  ...(refreshToken === undefined ? {} : { refreshToken }),
693
+ // Non-secret auth state is part of the credential export: without
694
+ // it a re-import loses the upstream user id and region that Trae
695
+ // refresh and dispatch are addressed by.
696
+ ...(() => {
697
+ const authState = authStates[account.id];
698
+ return authState !== undefined && Object.keys(authState).length > 0
699
+ ? { authState }
700
+ : {};
701
+ })(),
664
702
  ...(account.inflight === undefined ? {} : { inflight: account.inflight }),
665
703
  createdAt: account.createdAt,
666
704
  ...(account.cooldownUntil ? { cooldownUntil: account.cooldownUntil } : {}),
@@ -147,6 +147,9 @@ const deleteModelBody = t.Object({
147
147
  const bulkDeleteModelsBody = t.Object({
148
148
  items: t.Array(deleteModelBody, { minItems: 1, maxItems: 1000 }),
149
149
  });
150
+ const usageMatrixQuery = t.Object({
151
+ days: t.Optional(t.Integer({ minimum: 1, maximum: 366 })),
152
+ });
150
153
  export function createProviderCatalogRoutes(config: ProviderCatalogConfig): Elysia {
151
154
  const factory = createProviderCatalogOperations(config);
152
155
  const modelFactory = createModelCatalogOperations(config);
@@ -191,6 +194,17 @@ export function createProviderCatalogRoutes(config: ProviderCatalogConfig): Elys
191
194
  .get("/:providerId", async ({ request, params }) => {
192
195
  return await factory.getProviderDetail(config.accessResolver(request), params.providerId);
193
196
  })
197
+ .get(
198
+ "/:providerId/usage/matrix",
199
+ { query: usageMatrixQuery },
200
+ async ({ request, params, query }) => {
201
+ return await factory.getUsageMatrix(
202
+ config.accessResolver(request),
203
+ params.providerId,
204
+ query.days ?? 30,
205
+ );
206
+ },
207
+ )
194
208
  .post("/", { body: createProviderBody }, async ({ request, body, set }) => {
195
209
  set.status = 201;
196
210
  return await factory.createProvider(