cartethyia 2.4.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +45 -13
  2. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  3. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  8. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  9. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  25. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  32. package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
  33. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  34. package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
  35. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
  37. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
  39. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  44. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  45. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  47. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
  49. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
  51. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
  53. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
  61. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
  63. package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
  64. package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
  65. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  67. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
  71. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
  73. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
  75. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
  77. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
  81. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  84. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  85. package/dist/dashboard/assets/providers/trae.webp +0 -0
  86. package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
  87. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  88. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  89. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  90. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  91. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  92. package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
  93. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  94. package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
  95. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  96. package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
  97. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  98. package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
  99. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  100. package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
  101. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  102. package/dist/dashboard/index.html +1 -1
  103. package/dist/dashboard/index.html.gz +0 -0
  104. package/dist/dashboard/precompressed-assets.json +51 -52
  105. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  106. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  107. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  108. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  109. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  110. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  111. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  112. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  113. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  114. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  115. package/package.json +3 -2
  116. package/scripts/cli/command/console-menu.ts +30 -11
  117. package/scripts/cli/command/start.ts +77 -13
  118. package/scripts/cli/command/update.ts +1 -1
  119. package/scripts/cli/gateway/instance-lock.ts +105 -0
  120. package/scripts/cli/gateway/spawn.ts +173 -101
  121. package/scripts/cli/ui/background.ts +110 -49
  122. package/src/boot/app.ts +20 -6
  123. package/src/boot/dependencies.ts +19 -0
  124. package/src/boot/elysia-typebox-bridge.ts +37 -0
  125. package/src/boot/lifecycle.ts +2 -0
  126. package/src/config/cooldowns.ts +138 -138
  127. package/src/config/providers.ts +5 -5
  128. package/src/config/runtime.ts +19 -3
  129. package/src/config/spec.ts +9 -0
  130. package/src/console/api-keys/contracts.ts +414 -391
  131. package/src/console/api-keys/routes.ts +880 -870
  132. package/src/console/auth/account-reset-store.ts +14 -0
  133. package/src/console/auth/account-reset.ts +3 -2
  134. package/src/console/auth/setup-service.ts +6 -3
  135. package/src/console/domain-registration.ts +9 -2
  136. package/src/console/observability/contracts.ts +475 -455
  137. package/src/console/observability/store.ts +1015 -952
  138. package/src/console/providers/catalog/contracts.ts +31 -0
  139. package/src/console/providers/catalog/provider-operations.ts +8 -0
  140. package/src/console/providers/catalog/store.ts +1532 -1525
  141. package/src/console/providers/oauth/callback-listener.ts +18 -3
  142. package/src/console/providers/oauth/routes.ts +38 -15
  143. package/src/console/quota/refresh.ts +19 -0
  144. package/src/console/updates/routes.ts +162 -4
  145. package/src/main.ts +3 -0
  146. package/src/network/http2-bidi.ts +85 -0
  147. package/src/network/outbound-fetch.ts +1 -26
  148. package/src/network/pool/agent.ts +478 -460
  149. package/src/network/pool/resolver.ts +1104 -544
  150. package/src/network/pool-health-machine.ts +12 -5
  151. package/src/observability/payload-capture.ts +344 -324
  152. package/src/persistence/db-pg.ts +9 -3
  153. package/src/persistence/db-sqlite.ts +497 -495
  154. package/src/persistence/health-event-flush.ts +137 -0
  155. package/src/persistence/migrate.ts +181 -129
  156. package/src/persistence/schema.ts +1360 -1295
  157. package/src/persistence/settings-revision.ts +25 -0
  158. package/src/persistence/shared-cache.ts +217 -0
  159. package/src/persistence/stores/share-store.ts +556 -528
  160. package/src/persistence/usage-flush.ts +61 -1
  161. package/src/protocol/canonical-model.ts +23 -1
  162. package/src/protocol/response/chat.ts +143 -10
  163. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  164. package/src/providers/default-registry.ts +173 -1
  165. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  166. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  167. package/src/providers/discovery/probing-service.ts +1022 -1001
  168. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
  169. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  170. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
  171. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  172. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  173. package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
  174. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  175. package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
  176. package/src/providers/integrations/claude/claude.ts +606 -582
  177. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  178. package/src/providers/integrations/cline/cline.ts +539 -468
  179. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  180. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  181. package/src/providers/integrations/commandcode/quota.ts +160 -0
  182. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  183. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  184. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  185. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  186. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  187. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  188. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  189. package/src/providers/integrations/cursor/cursor.ts +960 -0
  190. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  191. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  192. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  193. package/src/providers/integrations/github/github.ts +4 -4
  194. package/src/providers/integrations/hermes.ts +79 -8
  195. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  196. package/src/providers/integrations/kilo/kilo.ts +153 -146
  197. package/src/providers/integrations/opencode/provider.ts +532 -538
  198. package/src/providers/integrations/openrouter/provider.ts +139 -281
  199. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  200. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
  201. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  202. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  203. package/src/providers/integrations/qoder/qoder.ts +865 -730
  204. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  205. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  206. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  207. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  208. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  209. package/src/providers/integrations/trae/trae.ts +458 -0
  210. package/src/providers/operations/account-health-service.ts +107 -10
  211. package/src/providers/operations/client-versions.ts +133 -6
  212. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  213. package/src/providers/operations/provider-credential-service.ts +46 -7
  214. package/src/providers/provider-contracts.ts +76 -0
  215. package/src/providers/provider-metadata.ts +78 -6
  216. package/src/providers/provider-model-definition.ts +373 -349
  217. package/src/security/api-key-auth.ts +126 -8
  218. package/src/transport/contracts.ts +3 -0
  219. package/src/transport/dispatch/attempt-finalize.ts +32 -1
  220. package/src/transport/dispatch/attempt-loop.ts +530 -515
  221. package/src/transport/dispatch/generation.ts +88 -21
  222. package/src/transport/dispatch/leases.ts +99 -2
  223. package/src/transport/dispatch/proxy-request.ts +60 -3
  224. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  225. package/src/transport/dispatch/streaming/stream.ts +22 -2
  226. package/src/transport/dispatch/upstream.ts +19 -1
  227. package/src/transport/guards/auth-guard.ts +26 -1
  228. package/src/transport/guards/body-policy.ts +5 -4
  229. package/src/transport/request/preparer.ts +103 -0
  230. package/src/transport/request/state.ts +14 -0
  231. package/src/transport/routing/plan-gate.ts +45 -0
  232. package/src/transport/routing/route-catalog.ts +799 -777
  233. package/src/transport/routing/route-model.ts +52 -1
  234. package/src/transport/routing/router.ts +51 -0
  235. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
  236. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  237. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  238. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  239. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
  240. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  241. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  242. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  243. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  244. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  245. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  246. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
  247. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  248. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
  249. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  250. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  251. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  252. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
  253. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  254. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
  255. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  256. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
  257. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  258. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  259. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  260. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
  261. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  262. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  263. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
  264. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  265. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  266. package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
  267. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  268. package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
  269. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  270. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  271. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  272. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  273. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  274. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  275. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  276. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  277. package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
  278. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  279. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  280. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  281. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  282. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  283. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  284. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  285. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  286. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  287. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  288. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  289. package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
  290. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  291. package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
  292. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  293. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  294. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  295. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  296. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  297. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  298. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  299. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  300. package/src/network/tls/bun-client-hello.ts +0 -112
  301. /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
  302. /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
@@ -1,6 +1,7 @@
1
1
  // API-key authentication and authorization snapshots: token extraction, hashing, lookup, and admission identity.
2
2
 
3
3
  import type { CartethyiaDatabase } from "../persistence/db-handle";
4
+ import { deleteShared, publishInvalidation, readShared, writeShared } from "../persistence/shared-cache";
4
5
  import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
5
6
  import { GatewayError } from "../protocol/gateway-error";
6
7
  import { hashSecret } from "./crypto";
@@ -39,6 +40,50 @@ const authCache = new Map<string, AuthCacheEntry>();
39
40
  const authCacheByKeyId = new Map<string, Set<string>>();
40
41
  /** Unknown/revoked token HMACs with their miss time; cleared on any key mutation. */
41
42
  const authNegativeCache = new Map<string, number>();
43
+
44
+ /**
45
+ * Short circuit after a credential-store failure.
46
+ *
47
+ * An overloaded or down database fails every lookup, and each failure costs a
48
+ * connection attempt. Without this, a saturated backend received one query per
49
+ * inbound request — load the operator sees as traffic, on top of the load that
50
+ * caused the outage. For the window, lookups answer "unavailable" instead.
51
+ *
52
+ * Deliberately short: this is an auth path, and a brief outage must not lock
53
+ * out valid keys for long.
54
+ */
55
+ const AUTH_FAILURE_COOLDOWN_MS = 1_000;
56
+ let authFailureUntil = 0;
57
+
58
+ /** Thrown when the credential store could not be reached at all. */
59
+ export class ApiKeyStoreUnavailableError extends Error {
60
+ constructor() {
61
+ super("The credential store is unavailable.");
62
+ this.name = "ApiKeyStoreUnavailableError";
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Source for lifetime-usage deltas recorded but not yet flushed.
68
+ *
69
+ * Injected rather than imported: this module is on the auth path and must not
70
+ * depend on the persistence/write layer it would otherwise pull in. Set once at
71
+ * boot (`boot/dependencies.ts`); until then it reads as zero, which is the
72
+ * value the code has always used.
73
+ */
74
+ let pendingUsage: (apiKeyId: string) => number = () => 0;
75
+
76
+ /**
77
+ * Supplies the in-flight usage source. See {@link pendingUsage}.
78
+ *
79
+ * The persisted `lifetime_tokens_consumed` is behind by up to one flush
80
+ * interval, and the flush coalesces by design — so it is behind by the *most*
81
+ * exactly when a key is busiest. Adding the pending delta costs nothing and
82
+ * removes the gap.
83
+ */
84
+ export function setPendingUsageSource(source: (apiKeyId: string) => number): void {
85
+ pendingUsage = source;
86
+ }
42
87
  /**
43
88
  * Negative-result fast path. Unknown/revoked tokens previously hit the DB on
44
89
  * every request; now a repeat miss within 1.5 s costs one HMAC + one map
@@ -97,12 +142,24 @@ export function invalidateApiKeyCache(keyId?: string): void {
97
142
  if (keyId === undefined) {
98
143
  authCache.clear();
99
144
  authCacheByKeyId.clear();
145
+ void deleteShared("auth");
146
+ publishInvalidation({ namespace: "auth" });
100
147
  return;
101
148
  }
102
149
  const hashes = authCacheByKeyId.get(keyId);
103
- if (!hashes) return;
104
- for (const hash of hashes) authCache.delete(hash);
105
- authCacheByKeyId.delete(keyId);
150
+ if (hashes) {
151
+ for (const hash of hashes) authCache.delete(hash);
152
+ authCacheByKeyId.delete(keyId);
153
+ }
154
+ // Peer processes hold their own copy, and `reusePort` means the console
155
+ // write may have landed on a different one entirely. Dropping only locally
156
+ // leaves a revoked key valid for up to a full TTL everywhere else.
157
+ //
158
+ // Delivered by broadcast rather than by deleting a shared entry: a mutation
159
+ // names a key id, and the shared layer is keyed by token hash, which the
160
+ // mutating request does not have. The notice is at-most-once, so the TTL
161
+ // stays the backstop for a peer that misses it.
162
+ publishInvalidation({ namespace: "auth", key: keyId });
106
163
  }
107
164
 
108
165
  /**
@@ -274,6 +331,19 @@ export function requestToken(headers: Headers | Record<string, string>): string
274
331
  return token;
275
332
  }
276
333
 
334
+ /**
335
+ * Whether a key can possibly have children, and so whether summing them can
336
+ * return anything but zero.
337
+ *
338
+ * Children exist only for a `share` parent — the schema's own check constraint
339
+ * ties the mode to the parent-child shape (schema.ts:799-803), and no other
340
+ * mode mints recipients. Extracted and exported because the alternative is an
341
+ * untestable inline comparison guarding a database round-trip.
342
+ */
343
+ export function canHaveChildKeys(key: { readonly keyMode?: string | null }): boolean {
344
+ return key.keyMode === "share";
345
+ }
346
+
277
347
  /**
278
348
  * Looks up the API key by its hashed bearer token. Returns `undefined` for
279
349
  * an unknown or revoked key — callers must reject with 401, never fall back
@@ -292,8 +362,37 @@ export async function resolveApiKeyAuthorization(
292
362
  }
293
363
  const cached = authCache.get(hash);
294
364
  if (cached && now - cached.at < AUTH_CACHE_TTL_MS) return cached.value;
365
+ // L2: a peer process may have resolved this token already. Reachable only
366
+ // after the L1 entry lapsed, so Redis is never consulted on a warm hit.
367
+ //
368
+ // Keyed by token hash, because that is what a lookup has — invalidation
369
+ // arrives as a key id and is delivered as a broadcast instead.
370
+ const shared = await readShared("auth", hash);
371
+ if (shared !== undefined) {
372
+ try {
373
+ const parsed = JSON.parse(shared) as ResolvedApiKey;
374
+ if (typeof parsed?.id === "string") {
375
+ indexAuthCache(hash, { at: now, value: parsed, keyId: parsed.id });
376
+ evictAuthCacheOldest();
377
+ return parsed;
378
+ }
379
+ } catch {
380
+ // A malformed or schema-drifted entry is ignored and re-resolved.
381
+ }
382
+ }
383
+ // Answered from the cooldown before any I/O: during a store outage the
384
+ // query is the expensive part, and it is also the part that fails.
385
+ if (now < authFailureUntil) throw new ApiKeyStoreUnavailableError();
295
386
  const store = new DrizzleApiKeyStore(db);
296
- const row = await store.findActiveByHash(hash);
387
+ let row: Awaited<ReturnType<typeof store.findActiveByHash>>;
388
+ try {
389
+ row = await store.findActiveByHash(hash);
390
+ } catch (error) {
391
+ // Any failure here is the store's, not the key's — a revoked key is a
392
+ // normal empty result, so this only runs on a real fault.
393
+ authFailureUntil = Date.now() + AUTH_FAILURE_COOLDOWN_MS;
394
+ throw error;
395
+ }
297
396
  if (!row) {
298
397
  authNegativeCache.set(hash, now);
299
398
  evictAuthNegativeOldest();
@@ -313,11 +412,19 @@ export async function resolveApiKeyAuthorization(
313
412
  // the old row values forever. Reading the parent here makes denylist /
314
413
  // one-time / recurring edits apply on the next cache miss.
315
414
  const policy = parent ?? row;
415
+ // Only a `share` parent can have children, and children are the only thing
416
+ // the SUM below can find. Every other key ran an aggregate that was certain
417
+ // to return 0 — one extra Postgres round-trip on the auth path for nothing.
418
+ //
419
+ // `row.parentKeyId !== null` is the same test, kept as the mode check because
420
+ // the mode is what the share dialog and the schema's own check constraint
421
+ // key off (schema.ts:799-803), so the two cannot drift apart.
422
+ const canHaveChildren = canHaveChildKeys(parent ?? row);
316
423
  const consumed =
317
- (policy.id === row.id
318
- ? (row.lifetimeTokensConsumed ?? 0)
319
- : (policy.lifetimeTokensConsumed ?? 0)) +
320
- (await store.sumChildrenConsumed(policy.id));
424
+ ((policy.id === row.id ? row.lifetimeTokensConsumed : policy.lifetimeTokensConsumed) ?? 0) +
425
+ (canHaveChildren ? await store.sumChildrenConsumed(policy.id) : 0) +
426
+ // In-flight deltas: what has been spent but not yet persisted.
427
+ pendingUsage(policy.id);
321
428
  const scopes = Array.isArray(policy.scopes) ? (policy.scopes as AccessScope[]) : [];
322
429
  const snapshot = createAuthorizationSnapshot({
323
430
  api_key_id: row.id,
@@ -349,5 +456,16 @@ export async function resolveApiKeyAuthorization(
349
456
  };
350
457
  indexAuthCache(hash, { at: Date.now(), value, keyId: value.id });
351
458
  evictAuthCacheOldest();
459
+ // Shared copy for peer processes, keyed by the token hash so a lookup can
460
+ // find it without resolving the key first. Invalidation travels by broadcast,
461
+ // since a console mutation names a key id and cannot enumerate the hashes
462
+ // that resolved to it in another process.
463
+ //
464
+ // The cached value deliberately carries `lifetime_tokens_consumed` as read.
465
+ // That figure moves constantly, and the module already documents the snapshot
466
+ // as advisory on that axis (spending is enforced by the admission counter),
467
+ // so a peer serving it stale for up to the TTL is the same tradeoff the
468
+ // in-process cache has always made.
469
+ void writeShared("auth", hash, JSON.stringify(value), AUTH_CACHE_TTL_MS);
352
470
  return value;
353
471
  }
@@ -64,6 +64,9 @@ export const NATIVE_SERVICES: readonly NativeServiceRoute[] = [
64
64
  { serviceKind: "websearch", path: "/v1/search" },
65
65
  { serviceKind: "imageGeneration", path: "/v1/images/generations" },
66
66
  { serviceKind: "videoGeneration", path: "/v1/videos/generations" },
67
+ { serviceKind: "embedding", path: "/v1/embeddings" },
68
+ { serviceKind: "audioTranscription", path: "/v1/audio/transcriptions" },
69
+ { serviceKind: "audioSpeech", path: "/v1/audio/speech" },
67
70
  ] as const;
68
71
 
69
72
  /** Paths served by a native service route, for the ingress body/canonical skip. */
@@ -39,6 +39,29 @@ export async function shouldCaptureProviderBody(
39
39
  return payloadCaptureKeepsBodies(db, tenantId);
40
40
  }
41
41
 
42
+ /**
43
+ * Same answer as {@link shouldCaptureProviderBody}, memoized on the request.
44
+ *
45
+ * The preference is a tenant setting, so it cannot change between attempts of
46
+ * one request. Resolving it per attempt meant a request with several attempts
47
+ * paid for it repeatedly, and the per-attempt answer could in principle differ
48
+ * from the one the terminal capture then acted on. Passing `state` lets every
49
+ * attempt of a request share one resolution.
50
+ */
51
+ export async function shouldCaptureProviderBodyForRequest(
52
+ db: CartethyiaDatabase,
53
+ tenantId: string | null,
54
+ state: { payloadCapturePreference?: unknown },
55
+ ): Promise<boolean> {
56
+ if (state.payloadCapturePreference === undefined) {
57
+ state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
58
+ }
59
+ const resolved = state.payloadCapturePreference as
60
+ | { readonly ok: true; readonly mode: string }
61
+ | { readonly ok: false };
62
+ return resolved.ok === true && resolved.mode === "full";
63
+ }
64
+
42
65
  /**
43
66
  * Combined body cap for one capture depth. Fixed size tiers: every depth
44
67
  * stores all four drawer panels (client request, translated provider
@@ -313,7 +336,15 @@ async function captureTerminalPayload(
313
336
  return;
314
337
  }
315
338
  try {
316
- const resolved = await resolvePayloadCapture(db, tenantId);
339
+ // Resolved once per request and memoized on the request state. The
340
+ // per-attempt call in the dispatch paths and this terminal call were
341
+ // resolving the same tenant setting independently, so a request with
342
+ // several attempts paid for it repeatedly and the two call sites could
343
+ // disagree about whether capture is on at all.
344
+ if (state.payloadCapturePreference === undefined) {
345
+ state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
346
+ }
347
+ const resolved = state.payloadCapturePreference;
317
348
  if (!resolved.ok) {
318
349
  note("skipped_preference_read_failed");
319
350
  return;