cartethyia 2.4.5 → 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 (415) hide show
  1. package/.env.example +4 -1
  2. package/README.md +63 -13
  3. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  4. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  5. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-D3xqZbN-.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  14. package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-CTTpCb25.js} +1 -1
  15. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-DHoB3Agn.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  22. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  23. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  26. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  27. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  31. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-CvjZxHE4.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  36. package/dist/dashboard/assets/SetupPage-D3nza7LH.js +1 -0
  37. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  38. package/dist/dashboard/assets/{SortableList-Bol1ASmI.js → SortableList-Dtt12WT1.js} +1 -1
  39. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  40. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js +31 -0
  41. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  42. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-BmDbsior.js +1 -0
  46. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  47. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  48. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  49. package/dist/dashboard/assets/{app-BDJWMThu.js → app-C-Y4oZT0.js} +3 -3
  50. package/dist/dashboard/assets/app-C-Y4oZT0.js.gz +0 -0
  51. package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
  52. package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
  53. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  54. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  55. package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
  56. package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-DpXT4Ixt.js} +1 -1
  57. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  58. package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-CSAAWLJv.js} +1 -1
  59. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  60. package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
  61. package/dist/dashboard/assets/{card-BTdbRFFg.js → card-DEhRXXw4.js} +1 -1
  62. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  63. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  64. package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
  66. package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  69. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +1 -0
  70. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  71. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  72. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  73. package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BvQeBMfP.js} +1 -1
  74. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  75. package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
  76. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  77. package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
  78. package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-CMwPXwQB.js} +1 -1
  79. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  80. package/dist/dashboard/assets/entry-D4O2229X.js +1 -0
  81. package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
  82. package/dist/dashboard/assets/entry-DKvr5Q9K.js +1 -0
  83. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  84. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  85. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  86. package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
  87. package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
  88. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  89. package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
  90. package/dist/dashboard/assets/github-badge-zliLvBwr.js +1 -0
  91. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  92. package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
  93. package/dist/dashboard/assets/{index-C4hXfAMs.js → index-CXm3FIyt.js} +2 -2
  94. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  95. package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1IlZZ8H.js} +1 -1
  96. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  97. package/dist/dashboard/assets/{input-Bn4_kNav.js → input-BpVZdrbc.js} +1 -1
  98. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  99. package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
  100. package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
  101. package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
  102. package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BioLW7gN.js} +1 -1
  103. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  104. package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-CwVcGAIc.js} +1 -1
  105. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  106. package/dist/dashboard/assets/{live-YlifPYox.js → live-yfEJFF9T.js} +1 -1
  107. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  108. package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
  109. package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
  110. package/dist/dashboard/assets/model-bans-CT-TxSNX.js +1 -0
  111. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  112. package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
  113. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  114. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  115. package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
  116. package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
  117. package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
  118. package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
  119. package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
  120. package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
  121. package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
  122. package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
  123. package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
  124. package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
  125. package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
  126. package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
  127. package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
  128. package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
  129. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  130. package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
  131. package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
  132. package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
  133. package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
  134. package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
  135. package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
  136. package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
  137. package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
  138. package/dist/dashboard/assets/providers/trae.webp +0 -0
  139. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  140. package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
  141. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  142. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  143. package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
  144. package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
  145. package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
  146. package/dist/dashboard/assets/routing-Cs6jl49L.js +1 -0
  147. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  148. package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
  149. package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
  150. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  151. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  152. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  153. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  154. package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
  155. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  156. package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-C9_uUjaA.js} +1 -1
  157. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  158. package/dist/dashboard/assets/{state-DHKdg-pm.js → state-DA0B95UQ.js} +1 -1
  159. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  160. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  161. package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-Cittxy-x.js} +1 -1
  162. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  163. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
  164. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  165. package/dist/dashboard/assets/toast-CNt_jzO4.js +2 -0
  166. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  167. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  168. package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
  169. package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
  170. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  171. package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
  172. package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
  173. package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-BQKMcJ1H.js} +1 -1
  174. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  175. package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
  176. package/dist/dashboard/index.html +1 -1
  177. package/dist/dashboard/index.html.gz +0 -0
  178. package/dist/dashboard/precompressed-assets.json +122 -0
  179. package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
  180. package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
  181. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  182. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  183. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  184. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  185. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  186. package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
  187. package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
  188. package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
  189. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  190. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  191. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  192. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  193. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  194. package/package.json +26 -30
  195. package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
  196. package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
  197. package/scripts/cli/command/admin.ts +65 -0
  198. package/scripts/cli/command/console-menu.ts +246 -0
  199. package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
  200. package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
  201. package/scripts/cli/command/start.ts +77 -0
  202. package/scripts/cli/command/tray.ts +165 -0
  203. package/scripts/cli/command/update.ts +23 -0
  204. package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
  205. package/scripts/cli/gateway/instance-lock.ts +105 -0
  206. package/scripts/cli/gateway/spawn.ts +173 -0
  207. package/scripts/cli/gateway/stop.ts +51 -0
  208. package/scripts/cli/main.ts +94 -0
  209. package/scripts/cli/support/bun.ts +27 -0
  210. package/scripts/cli/support/env.ts +99 -0
  211. package/scripts/cli/support/platform.ts +64 -0
  212. package/scripts/cli/support/services.ts +88 -0
  213. package/scripts/cli/support/startup-signal.ts +23 -0
  214. package/scripts/cli/{version.ts → support/version.ts} +1 -1
  215. package/scripts/cli/tray/hidden-window.vbs +28 -0
  216. package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
  217. package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
  218. package/scripts/cli/ui/background.ts +110 -0
  219. package/src/boot/app.ts +23 -7
  220. package/src/boot/dependencies.ts +55 -13
  221. package/src/boot/elysia-typebox-bridge.ts +37 -0
  222. package/src/boot/lifecycle.ts +2 -0
  223. package/src/config/cooldowns.ts +138 -138
  224. package/src/config/providers.ts +31 -5
  225. package/src/config/runtime.ts +19 -3
  226. package/src/config/spec.ts +20 -0
  227. package/src/console/api-keys/contracts.ts +414 -391
  228. package/src/console/api-keys/routes.ts +880 -870
  229. package/src/console/auth/account-reset-store.ts +14 -0
  230. package/src/console/auth/account-reset.ts +3 -2
  231. package/src/console/auth/setup-service.ts +6 -3
  232. package/src/console/backup/routes.ts +8 -0
  233. package/src/console/cli-tools/contracts.ts +6 -6
  234. package/src/console/console-router.ts +5 -3
  235. package/src/console/dashboard-assets.ts +81 -11
  236. package/src/console/domain-registration.ts +9 -2
  237. package/src/console/observability/contracts.ts +475 -423
  238. package/src/console/observability/routes.ts +31 -6
  239. package/src/console/observability/store.ts +1015 -912
  240. package/src/console/providers/catalog/contracts.ts +31 -0
  241. package/src/console/providers/catalog/provider-operations.ts +8 -0
  242. package/src/console/providers/catalog/public-model-store.ts +91 -60
  243. package/src/console/providers/catalog/store.ts +1532 -1521
  244. package/src/console/providers/oauth/callback-listener.ts +18 -3
  245. package/src/console/providers/oauth/routes.ts +38 -15
  246. package/src/console/quota/account-quota-tenant.ts +9 -6
  247. package/src/console/quota/refresh.ts +19 -0
  248. package/src/console/response-compression.ts +69 -2
  249. package/src/console/routing/pools/probe-verdict.ts +72 -0
  250. package/src/console/share/share-router.ts +7 -4
  251. package/src/console/share/share-stats.ts +12 -5
  252. package/src/console/shared/read-through-cache.ts +133 -7
  253. package/src/console/updates/repository-updates.ts +39 -34
  254. package/src/console/updates/routes.ts +162 -4
  255. package/src/main.ts +3 -0
  256. package/src/network/http2-bidi.ts +85 -0
  257. package/src/network/outbound-fetch.ts +87 -24
  258. package/src/network/pool/agent.ts +478 -460
  259. package/src/network/pool/resolver.ts +1104 -544
  260. package/src/network/pool-health-machine.ts +12 -5
  261. package/src/observability/event-log.ts +29 -8
  262. package/src/observability/metrics.ts +40 -13
  263. package/src/observability/payload-capture.ts +344 -324
  264. package/src/observability/runtime-metrics.ts +7 -0
  265. package/src/persistence/db-pg.ts +9 -3
  266. package/src/persistence/db-sqlite.ts +497 -495
  267. package/src/persistence/health-event-flush.ts +137 -0
  268. package/src/persistence/migrate.ts +181 -129
  269. package/src/persistence/schema.ts +1360 -1257
  270. package/src/persistence/settings-revision.ts +25 -0
  271. package/src/persistence/shared-cache.ts +217 -0
  272. package/src/persistence/stores/share-store.ts +556 -528
  273. package/src/persistence/usage-flush.ts +61 -1
  274. package/src/protocol/canonical-model.ts +23 -1
  275. package/src/protocol/response/chat.ts +143 -10
  276. package/src/protocol/response/codex.ts +9 -75
  277. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  278. package/src/protocol/tool-identity.ts +43 -9
  279. package/src/providers/authentication/jwt-validator.ts +130 -187
  280. package/src/providers/authentication/oauth-flow-store.ts +16 -9
  281. package/src/providers/default-registry.ts +177 -1
  282. package/src/providers/discovery/catalog/catalog-format.ts +20 -0
  283. package/src/providers/discovery/models-dev-catalog.ts +39 -22
  284. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  285. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  286. package/src/providers/discovery/probe-wire.ts +29 -5
  287. package/src/providers/discovery/probing-service.ts +1022 -1001
  288. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +42 -2
  289. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  290. package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
  291. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -5
  292. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
  293. package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
  294. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
  295. package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
  296. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  297. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  298. package/src/providers/integrations/buddy/codebuddy/shared.ts +115 -73
  299. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
  300. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  301. package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
  302. package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
  303. package/src/providers/integrations/claude/claude.ts +606 -582
  304. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  305. package/src/providers/integrations/cline/cline.ts +539 -468
  306. package/src/providers/integrations/codex/codex.ts +5 -8
  307. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  308. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  309. package/src/providers/integrations/commandcode/quota.ts +160 -0
  310. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  311. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  312. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  313. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  314. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  315. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  316. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  317. package/src/providers/integrations/cursor/cursor.ts +960 -0
  318. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  319. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  320. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  321. package/src/providers/integrations/factory/factory-oauth.ts +7 -11
  322. package/src/providers/integrations/github/github.ts +4 -4
  323. package/src/providers/integrations/hermes.ts +79 -8
  324. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  325. package/src/providers/integrations/kilo/kilo.ts +153 -146
  326. package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
  327. package/src/providers/integrations/opencode/provider.ts +532 -499
  328. package/src/providers/integrations/openrouter/provider.ts +139 -281
  329. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  330. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -263
  331. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  332. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  333. package/src/providers/integrations/qoder/qoder.ts +865 -730
  334. package/src/providers/integrations/shared/install-id.ts +29 -2
  335. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  336. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  337. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  338. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  339. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  340. package/src/providers/integrations/trae/trae.ts +458 -0
  341. package/src/providers/operations/account-health-service.ts +107 -10
  342. package/src/providers/operations/client-versions.ts +143 -6
  343. package/src/providers/operations/model-catalog-cache.ts +18 -3
  344. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  345. package/src/providers/operations/provider-catalog-service.ts +76 -0
  346. package/src/providers/operations/provider-credential-service.ts +46 -7
  347. package/src/providers/provider-contracts.ts +76 -0
  348. package/src/providers/provider-metadata.ts +78 -6
  349. package/src/providers/provider-model-definition.ts +373 -349
  350. package/src/security/api-key-auth.ts +126 -8
  351. package/src/transport/contracts.ts +3 -0
  352. package/src/transport/dispatch/attempt-finalize.ts +39 -2
  353. package/src/transport/dispatch/attempt-loop.ts +530 -513
  354. package/src/transport/dispatch/empty-stream.ts +17 -26
  355. package/src/transport/dispatch/generation.ts +88 -21
  356. package/src/transport/dispatch/leases.ts +99 -2
  357. package/src/transport/dispatch/proxy-request.ts +60 -3
  358. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  359. package/src/transport/dispatch/streaming/stream.ts +22 -2
  360. package/src/transport/dispatch/upstream.ts +19 -1
  361. package/src/transport/failure-policy.ts +20 -0
  362. package/src/transport/guards/auth-guard.ts +26 -1
  363. package/src/transport/guards/body-policy.ts +5 -4
  364. package/src/transport/request/preparer.ts +103 -0
  365. package/src/transport/request/state.ts +14 -0
  366. package/src/transport/routing/plan-gate.ts +45 -0
  367. package/src/transport/routing/route-catalog.ts +799 -738
  368. package/src/transport/routing/route-model.ts +52 -1
  369. package/src/transport/routing/router.ts +51 -0
  370. package/src/transport/surface/chat/encode.ts +18 -31
  371. package/src/transport/surface/messages/stream.ts +3 -2
  372. package/src/transport/translation/tool-repair.ts +37 -16
  373. package/src/workers/account-identity-sweep.ts +14 -12
  374. package/src/workers/daily-checkin.ts +62 -0
  375. package/src/workers/quota-refresh-sweep.ts +27 -2
  376. package/dist/dashboard/assets/BannedPage-DUrXpp-X.js +0 -1
  377. package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
  378. package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
  379. package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
  380. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  381. package/dist/dashboard/assets/OverviewPage-C3NIzsDx.js +0 -1
  382. package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
  383. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  384. package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
  385. package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
  386. package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
  387. package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
  388. package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
  389. package/dist/dashboard/assets/UsagePage-Djes-Epy.js +0 -56
  390. package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
  391. package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
  392. package/dist/dashboard/assets/assert-Bwh6DJTK.js +0 -1
  393. package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
  394. package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
  395. package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
  396. package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
  397. package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
  398. package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
  399. package/dist/dashboard/assets/model-bans-CVvHZ-qz.js +0 -1
  400. package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
  401. package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
  402. package/dist/dashboard/assets/routing-jntw9-Ui.js +0 -1
  403. package/dist/dashboard/assets/select-CGUuTQmj.js +0 -41
  404. package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
  405. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  406. package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
  407. package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
  408. package/scripts/cli/cli.ts +0 -771
  409. package/scripts/operations/probe-egress.ts +0 -335
  410. package/scripts/operations/restart.ts +0 -120
  411. package/scripts/shared/env.ts +0 -269
  412. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  413. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  414. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  415. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -63,6 +63,23 @@ export interface RouteCandidate {
63
63
  * Absent/`true` preserves the existing invariant: no account configured
64
64
  * means the route is unusable, not silently public. */
65
65
  readonly requires_account?: boolean;
66
+ /**
67
+ * True when the upstream refuses this model on a free plan.
68
+ *
69
+ * Three-valued by absence as well as by value: only `true` excludes a free
70
+ * account. `false` and absent both mean the model is not known to be gated,
71
+ * and routing must not turn "not known" into "free accounts may serve it" —
72
+ * that would send pro-only traffic to an account that is certain to be
73
+ * refused.
74
+ */
75
+ readonly pro_only?: boolean;
76
+ /**
77
+ * The plan the provider last reported for `provider_account_id`.
78
+ *
79
+ * Absent means the plan was never read, which is a different state from
80
+ * "free": guessing free here would defeat the gate it exists to serve.
81
+ */
82
+ readonly account_plan?: string | null;
66
83
  }
67
84
 
68
85
  /**
@@ -133,6 +150,21 @@ export interface PoolRoutingSetting {
133
150
  readonly rotateCount: number;
134
151
  }
135
152
 
153
+ /**
154
+ * Exact account a request must be routed to, because a provider session is
155
+ * already live on it.
156
+ *
157
+ * A tool result for a Cursor Run is only meaningful on the account whose Run
158
+ * asked for the call. Routing it to a sibling account — even a healthy one —
159
+ * writes the result into an unrelated upstream turn, so affinity is a hard
160
+ * constraint on the candidate set rather than a preference: `plan` pins to this
161
+ * account or refuses.
162
+ */
163
+ export interface RouteContinuationAffinity {
164
+ readonly providerId: string;
165
+ readonly providerAccountId: string;
166
+ }
167
+
136
168
  export interface RoutePlan {
137
169
  readonly revision: RoutingRevision;
138
170
  readonly candidates: readonly RouteCandidate[];
@@ -367,6 +399,8 @@ export function accountsRateLimitedError(
367
399
 
368
400
 
369
401
 
402
+ import { publishRoutingInvalidation, subscribeRoutingInvalidation } from "../../persistence/shared-cache";
403
+
370
404
  export type SnapshotBuilder = () => Promise<
371
405
  Omit<RouteSnapshot, "revision" | "created_at"> & { created_at?: number }
372
406
  >;
@@ -398,7 +432,17 @@ export class InMemoryRouteSnapshotService implements RouteSnapshotService {
398
432
  */
399
433
  private building: { revision: RoutingRevision; promise: Promise<RouteSnapshot> } | undefined;
400
434
 
401
- constructor(private readonly builder: SnapshotBuilder) {}
435
+ constructor(private readonly builder: SnapshotBuilder) {
436
+ // Peers' mutations arrive once per process, not per instance: the handler
437
+ // drops this instance's copy, which is the only one there is.
438
+ this.unsubscribe = subscribeRoutingInvalidation(() => {
439
+ this.revision += 1;
440
+ this.snapshot = undefined;
441
+ });
442
+ }
443
+
444
+ /** Ends the peer-invalidation subscription. */
445
+ readonly unsubscribe: () => void;
402
446
 
403
447
  async getSnapshot(): Promise<RouteSnapshot> {
404
448
  if (this.snapshot) return this.snapshot;
@@ -422,6 +466,13 @@ export class InMemoryRouteSnapshotService implements RouteSnapshotService {
422
466
  // Lazy: the next getSnapshot() rebuilds. Dashboard mutations only need
423
467
  // the new revision token — they must not pay seven Postgres round-trips
424
468
  // inside the mutating request while holding no pool pressure budget.
469
+ //
470
+ // Broadcast, because `reusePort` runs several gateway processes and each
471
+ // holds its own snapshot: a console write handled by one of them left the
472
+ // others serving pre-write routing until something else invalidated them.
473
+ // Peers drop their copy on receipt and rebuild lazily, same as here — the
474
+ // notice carries no data, so there is nothing to deserialize.
475
+ publishRoutingInvalidation();
425
476
  return this.revision;
426
477
  }
427
478
 
@@ -1,5 +1,6 @@
1
1
  // Routing model resolution and route planning.
2
2
  import { log } from "../../observability/logger";
3
+ import { GatewayError } from "../../protocol/gateway-error";
3
4
  import {
4
5
  accountsRateLimitedError,
5
6
  accountsUnavailableError,
@@ -14,12 +15,14 @@ import {
14
15
  type RouteCandidate,
15
16
  type RoutePlan,
16
17
  type EligibilityDecision,
18
+ type RouteContinuationAffinity,
17
19
  type RouteSnapshot,
18
20
  } from "./route-model";
19
21
  import {
20
22
  candidateSupportsRequest,
21
23
  type RequiredCapability,
22
24
  } from "../translation/capabilities";
25
+ import { planAllowsCandidate } from "./plan-gate";
23
26
  import { InMemoryAdmissionController, admissionKey } from "./admission";
24
27
  import { ReservationManager } from "./reservations";
25
28
  import { getOrCreateRoundRobin, RoundRobinState } from "./round-robin";
@@ -525,6 +528,7 @@ export class RoutingEngine {
525
528
  requiredCapabilities?: readonly RequiredCapability[],
526
529
  allowCliMappings = false,
527
530
  keyId?: string,
531
+ continuationAffinity?: RouteContinuationAffinity,
528
532
  ): Promise<RoutePlan> {
529
533
  const tid = tenantId ?? null;
530
534
  const { resolved, matching, unmatchedMembers } = this.resolveMatchingCandidates(
@@ -534,6 +538,41 @@ export class RoutingEngine {
534
538
  allowCliMappings,
535
539
  keyId,
536
540
  );
541
+ // Continuation affinity is applied first and absolutely.
542
+ //
543
+ // A request carrying a tool result for a provider session this process is
544
+ // holding MUST reach the exact live account that owns that session. Routing
545
+ // it anywhere else is not a degraded choice — it is a correctness failure:
546
+ // the result would be written onto a different account's upstream turn
547
+ // (corrupting both conversations and leaking the first account's context),
548
+ // or into a fresh turn that never asked for the call. So the candidate set
549
+ // is restricted before any health, cooling, or rotation logic runs, and an
550
+ // empty result is a refusal rather than a fallback to normal routing.
551
+ if (continuationAffinity !== undefined) {
552
+ const pinned = matching.filter(
553
+ (candidate) =>
554
+ candidate.provider_id === continuationAffinity.providerId &&
555
+ candidate.provider_account_id === continuationAffinity.providerAccountId,
556
+ );
557
+ if (pinned.length === 0) {
558
+ throw new GatewayError(
559
+ "invalid_request",
560
+ 400,
561
+ "This tool result belongs to a Cursor account that is no longer routable. Reconnect the account, or resend the complete conversation.",
562
+ {
563
+ providerId: continuationAffinity.providerId,
564
+ accountId: continuationAffinity.providerAccountId,
565
+ },
566
+ );
567
+ }
568
+ return {
569
+ revision: snapshot.revision,
570
+ candidates: pinned,
571
+ requested_model: requestedModel,
572
+ resolved_model: resolved.model,
573
+ provider_id: pinned[0]!.provider_id,
574
+ };
575
+ }
537
576
  // `matching` non-empty but nothing eligible means every account serving
538
577
  // this model is currently unusable — NOT a missing model. Distinct from
539
578
  // the `matching.length === 0` throw in the resolver, which is a genuine 404.
@@ -667,6 +706,18 @@ export class RoutingEngine {
667
706
  if (eligible.length === 0)
668
707
  throw capabilityUnsupportedError(requiredCapabilities.join(", "));
669
708
  }
709
+ // Plan gate: a model the upstream refuses on a free plan is not offered to
710
+ // an account the upstream reports as free. That pair is a certain refusal,
711
+ // and sending it anyway advances the account's failure streak toward a
712
+ // six-hour park for something that is not the account's fault.
713
+ //
714
+ // Applied only when another candidate survives. Every account being free is
715
+ // not a routing failure — it is the operator having only free accounts, and
716
+ // the honest outcome is the upstream's own `11158`, which names the model
717
+ // and the plan. Dropping the last candidate would replace that clear refusal
718
+ // with a generic "no candidates" that names neither.
719
+ const planAllowed = eligible.filter((candidate) => planAllowsCandidate(candidate));
720
+ if (planAllowed.length > 0) eligible = planAllowed;
670
721
  eligible = this.applyProviderRouting(eligible, snapshot, tid);
671
722
  // Re-assert the cooling order: `applyProviderRouting` rotates accounts
672
723
  // within one `provider::model` run, and that rotation is by design blind to
@@ -1,4 +1,5 @@
1
1
  import type { CanonicalEvent, CanonicalStopReason } from "../../../protocol/canonical-model";
2
+ import { jsonrepair } from "jsonrepair";
2
3
  import { reasoningSummaryIdentity, reasoningSummarySeparator, type ReasoningSummaryIdentity } from "../reasoning-summary";
3
4
  import { eventText } from "../adapters";
4
5
  import { isRecord } from "../../../protocol/codecs";
@@ -20,50 +21,36 @@ import type { ChatEncodeOptions, ChunkBase, JsonObject } from "./parse";
20
21
  * a raw control character inside a string, a trailing comma, an unquoted key.
21
22
  * The client parses the assembled string with `JSON.parse` and, on failure,
22
23
  * reports the call as `Tool <name> not found` with the parser's message
23
- * (`Unexpected trailing characters`, `Invalid number`). The repair is the same
24
- * one oh-my-pi applies before a tool runs: escape what strict JSON forbids,
25
- * then drop a trailing comma. A value that still does not parse is returned
26
- * unchanged — a half-repaired call is worse than the original error.
24
+ * (`Unexpected trailing characters`, `Invalid number`).
25
+ *
26
+ * Repairing is delegated to `jsonrepair`, which handles the whole family of
27
+ * near-JSON shapes (truncated objects, unquoted keys, single quotes, trailing
28
+ * commas) instead of the two cases a hand-rolled scanner covered.
29
+ *
30
+ * The policy around it is unchanged and is the load-bearing part: the result
31
+ * is parsed only to validate, and anything that does not parse *to an object*
32
+ * is discarded in favour of the original text — a half-repaired call is worse
33
+ * than the original error. The object check matters because `jsonrepair` will
34
+ * happily turn arbitrary prose into a quoted JSON string, which parses but is
35
+ * not a tool-argument object.
27
36
  */
28
37
  export function repairToolArguments(raw: string): string {
29
38
  if (raw.length === 0) return "{}";
30
39
  const repaired = repairToolArgumentText(raw);
31
40
  try {
32
- JSON.parse(repaired);
41
+ const parsed: unknown = JSON.parse(repaired);
42
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return raw;
33
43
  return repaired;
34
44
  } catch {
35
45
  return raw;
36
46
  }
37
47
  }
38
48
 
49
+ /** Repairs near-JSON text into strict JSON. Throws when the text is unrepairable. */
39
50
  function repairToolArgumentText(json: string): string {
40
- const parts: string[] = [];
41
- let last = 0;
42
- let inString = false;
43
- for (let i = 0; i < json.length; i++) {
44
- const code = json.charCodeAt(i);
45
- if (!inString) {
46
- if (code === 0x22) inString = true;
47
- continue;
48
- }
49
- if (code === 0x22) {
50
- inString = false;
51
- continue;
52
- }
53
- if (code === 0x5c) {
54
- i++;
55
- continue;
56
- }
57
- if (code < 0x20) {
58
- const escape =
59
- code === 0x0a ? "\\n" : code === 0x0d ? "\\r" : code === 0x09 ? "\\t" : `\\u${code.toString(16).padStart(4, "0")}`;
60
- parts.push(json.slice(last, i), escape);
61
- last = i + 1;
62
- }
63
- }
64
- const escaped = parts.length === 0 ? json : parts.join("") + json.slice(last);
65
- return escaped.replace(/,(\s*[}\]])/g, "$1");
51
+ return jsonrepair(json);
66
52
  }
53
+
67
54
  export function bytesToString(value: string | Uint8Array): string {
68
55
  return typeof value === "string" ? value : new TextDecoder().decode(value);
69
56
  }
@@ -6,6 +6,7 @@ import {
6
6
  type ReasoningSummaryIdentity,
7
7
  } from "../reasoning-summary";
8
8
  import {
9
+ argumentsFrom,
9
10
  createToolCallTracker,
10
11
  resetToolCallTracker,
11
12
  toolIdentityKey,
@@ -96,7 +97,7 @@ export class MessagesStreamEncoder extends SurfaceStreamEncoder<CanonicalEvent,
96
97
  const index = existing ?? this.nextIndex++;
97
98
  this.tools.indexById.set(callId, index);
98
99
  const sent = this.emittedArguments.get(callId) ?? 0;
99
- const pending = entry.arguments.slice(sent);
100
+ const pending = argumentsFrom(entry, sent);
100
101
  if (!this.openedToolCalls.has(callId)) {
101
102
  this.openedToolCalls.add(callId);
102
103
  out.push(...this.closeOpenBlock());
@@ -120,7 +121,7 @@ export class MessagesStreamEncoder extends SurfaceStreamEncoder<CanonicalEvent,
120
121
  index,
121
122
  delta: { type: "input_json_delta", partial_json: pending },
122
123
  });
123
- this.emittedArguments.set(callId, entry.arguments.length);
124
+ this.emittedArguments.set(callId, sent + pending.length);
124
125
  }
125
126
  return out;
126
127
  }
@@ -9,16 +9,31 @@ import { canContainToolResult } from "../../protocol/canonical-model";
9
9
 
10
10
  const MISSING_TOOL_OUTPUT = "<missing tool output>";
11
11
 
12
- /** True when any tool/user turn carries a `toolResult` part for `callId`. */
13
- function findToolResult(
14
- turns: readonly CanonicalMessage[],
15
- callId: string,
16
- ): boolean {
17
- return turns.some(
18
- (turn) =>
19
- canContainToolResult(turn) &&
20
- turn.content.some((part) => part.kind === "toolResult" && part.call_id === callId),
21
- );
12
+ /**
13
+ * Maps each answered call id to the LAST message index carrying its result.
14
+ *
15
+ * A call is answered iff some result appears at an index greater than the
16
+ * call's own, so the highest index is sufficient — no per-call scan of the
17
+ * remaining messages is needed. Built in one forward sweep, O(n·p) overall.
18
+ *
19
+ * The previous implementation sliced the remaining array per assistant turn
20
+ * and scanned it per call id: O(n²·p) plus O(n) array allocations on every
21
+ * request carrying tools, which a 200-turn agentic conversation turned into
22
+ * ~20 000 part scans of pure synchronous CPU before the upstream was ever
23
+ * contacted.
24
+ */
25
+ function lastResultIndexByCallId(
26
+ messages: readonly CanonicalMessage[],
27
+ ): Map<string, number> {
28
+ const lastIndex = new Map<string, number>();
29
+ for (let index = 0; index < messages.length; index += 1) {
30
+ const message = messages[index] as CanonicalMessage;
31
+ if (!canContainToolResult(message)) continue;
32
+ for (const part of message.content) {
33
+ if (part.kind === "toolResult") lastIndex.set(part.call_id, index);
34
+ }
35
+ }
36
+ return lastIndex;
22
37
  }
23
38
 
24
39
  /**
@@ -40,18 +55,24 @@ export function repairMissingToolResponses(
40
55
  messages: readonly CanonicalMessage[],
41
56
  ): CanonicalMessage[] {
42
57
  const repaired: CanonicalMessage[] = [];
58
+ // Answered-by-a-later-message, resolved once instead of per assistant turn.
59
+ const lastResultIndex = lastResultIndexByCallId(messages);
43
60
  for (let index = 0; index < messages.length; index += 1) {
44
61
  const message = messages[index] as CanonicalMessage;
45
62
  repaired.push(message);
46
63
  if (message.role !== "assistant") continue;
47
- const callIds = message.content
48
- .filter((part) => part.kind === "toolCall")
49
- .map((part) => (part as Extract<typeof part, { kind: "toolCall" }>).call_id);
64
+ const callIds = toolCallIdsIn(message);
50
65
  if (callIds.length === 0) continue;
51
66
 
52
- // Answered = a later tool message carrying a matching toolResult part.
53
- const later = messages.slice(index + 1);
54
- const orphanIds = callIds.filter((id) => !findToolResult(later, id));
67
+ // Positional: a result answers only calls in EARLIER messages, so the
68
+ // answering index must be strictly greater than this one. That is also
69
+ // what stops a result for batch N from silencing a same-id call the
70
+ // client reuses in batch N+1, and stops a replayed stale turn from
71
+ // satisfying the call declared beside it.
72
+ const orphanIds = callIds.filter((id) => {
73
+ const answeredAt = lastResultIndex.get(id);
74
+ return answeredAt === undefined || answeredAt <= index;
75
+ });
55
76
  if (orphanIds.length === 0) continue;
56
77
 
57
78
  for (const callId of orphanIds) {
@@ -21,6 +21,12 @@ export interface UnenrichedAccount {
21
21
  readonly id: string;
22
22
  readonly providerId: string;
23
23
  readonly label: string;
24
+ /**
25
+ * The stored credential, carried from the listing query. It is what the
26
+ * sweep exists to parse, so fetching it a second time per account was a
27
+ * redundant read of the row's widest column.
28
+ */
29
+ readonly credentialCiphertext: Buffer | Uint8Array | null;
24
30
  }
25
31
 
26
32
  export interface AccountIdentityEnrichmentSweepDeps {
@@ -76,11 +82,17 @@ export async function accountIdentitySweep(
76
82
  await runSweep<UnenrichedAccount>({
77
83
  name: "account-enrichment",
78
84
  list: async () => {
85
+ // `credentialCiphertext` is selected here, not re-read per account: the
86
+ // sweep ran one listing query and then a second SELECT for every account
87
+ // in it, to fetch the one column the listing had already matched on. The
88
+ // credential is the widest column on the row, so this is also the row
89
+ // that most needed to be fetched once.
79
90
  const rows = await deps.db
80
91
  .select({
81
92
  id: providerAccounts.id,
82
93
  providerId: providerAccounts.providerId,
83
94
  label: providerAccounts.label,
95
+ credentialCiphertext: providerAccounts.credentialCiphertext,
84
96
  })
85
97
  .from(providerAccounts)
86
98
  .where(
@@ -94,22 +106,12 @@ export async function accountIdentitySweep(
94
106
  return rows;
95
107
  },
96
108
  run: async (account) => {
97
- const rows = await deps.db
98
- .select({
99
- credentialCiphertext: providerAccounts.credentialCiphertext,
100
- })
101
- .from(providerAccounts)
102
- .where(eq(providerAccounts.id, account.id))
103
- .limit(1);
104
- const row = rows[0];
105
- if (row === undefined) return;
106
-
107
109
  let secret: string | undefined;
108
110
  try {
109
111
  secret =
110
- row.credentialCiphertext === null || row.credentialCiphertext === undefined
112
+ account.credentialCiphertext === null || account.credentialCiphertext === undefined
111
113
  ? undefined
112
- : decryptCredentialToString(row.credentialCiphertext);
114
+ : decryptCredentialToString(account.credentialCiphertext);
113
115
  } catch (error) {
114
116
  // An undecryptable credential is a real problem, but not one this sweep
115
117
  // can fix. Stamp it so the pass stops retrying and the account's own
@@ -252,6 +252,68 @@ export async function markCheckinAttempted(args: {
252
252
  * Absent or unreadable means unknown, never "not claimed": the sweep may
253
253
  * simply not have reached the account yet.
254
254
  */
255
+ /**
256
+ * The same question as {@link checkinAttemptedToday}, asked once for many
257
+ * accounts.
258
+ *
259
+ * The console answers it for every account on the page, and doing that one
260
+ * account at a time cost a round trip per account — one `GET` to Redis, or one
261
+ * `SELECT` to Postgres — on the route the dashboard polls. This is one `MGET`
262
+ * when Redis is present, or one `SELECT ... WHERE day_key = ? AND account_id
263
+ * IN (...)` when it is not, which is the same shape the release path already
264
+ * uses.
265
+ *
266
+ * An account absent from the result was not attempted, matching the single
267
+ * read's "absent means unknown, never claimed".
268
+ */
269
+ export async function checkinAttemptedTodayBatch(args: {
270
+ readonly db: CartethyiaDatabase | undefined;
271
+ readonly redis: RedisClient | undefined;
272
+ readonly accountIds: readonly string[];
273
+ readonly now?: () => Date;
274
+ }): Promise<Map<string, boolean>> {
275
+ const attempted = new Map<string, boolean>();
276
+ if (args.accountIds.length === 0) return attempted;
277
+ const dayKey = checkinDayKey(args.now?.() ?? new Date());
278
+ // A client without MGET (the memory backend) takes the DB path below, or
279
+ // reports unknown when there is no DB either.
280
+ if (args.redis !== undefined && typeof args.redis.mget === "function") {
281
+ try {
282
+ // One command for every account: one key per account is small, and a
283
+ // single `MGET` keeps the cost flat instead of linear in the count.
284
+ const values = (await args.redis.mget(
285
+ ...args.accountIds.map((id) => ledgerKey(dayKey, id)),
286
+ )) as Array<string | null>;
287
+ args.accountIds.forEach((accountId, index) => {
288
+ attempted.set(accountId, values[index] != null);
289
+ });
290
+ return attempted;
291
+ } catch {
292
+ // Fall through: an unreadable ledger means unknown, and the DB path is
293
+ // the only other place the marker could be.
294
+ }
295
+ }
296
+ if (args.db === undefined) return attempted;
297
+ try {
298
+ const rows = await args.db
299
+ .select({ accountId: dailyCheckinLedger.accountId })
300
+ .from(dailyCheckinLedger)
301
+ .where(
302
+ and(
303
+ eq(dailyCheckinLedger.dayKey, dayKey),
304
+ inArray(dailyCheckinLedger.accountId, [...args.accountIds]),
305
+ ),
306
+ );
307
+ const present = new Set(rows.map((row) => row.accountId));
308
+ for (const accountId of args.accountIds) {
309
+ attempted.set(accountId, present.has(accountId));
310
+ }
311
+ } catch {
312
+ // Unknown, per account, as above.
313
+ }
314
+ return attempted;
315
+ }
316
+
255
317
  export async function checkinAttemptedToday(args: {
256
318
  readonly db: CartethyiaDatabase | undefined;
257
319
  readonly redis: RedisClient | undefined;
@@ -113,10 +113,25 @@ export function checkinSlotAvailable(
113
113
  * An account is re-announced only when its outcome actually changes.
114
114
  *
115
115
  * Module scope, not per-call: suppression must survive across passes, and one
116
- * `quotaRefreshSweep` call is one pass.
116
+ * `quotaRefreshSweep` call is one pass. Bounded because it is keyed by account
117
+ * id and was never pruned, so accounts deleted years ago still held an entry;
118
+ * the oldest keys are the ones least likely to be announced again.
117
119
  */
120
+ const LAST_SWEEP_LINE_MAX = 512;
118
121
  const lastSweepLine = new Map<string, string>();
119
122
 
123
+ /** Drops the oldest entries once the suppression map reaches its cap. */
124
+ function trimLastSweepLine(): void {
125
+ if (lastSweepLine.size <= LAST_SWEEP_LINE_MAX) return;
126
+ const excess = lastSweepLine.size - LAST_SWEEP_LINE_MAX;
127
+ let dropped = 0;
128
+ for (const key of lastSweepLine.keys()) {
129
+ if (dropped >= excess) break;
130
+ lastSweepLine.delete(key);
131
+ dropped += 1;
132
+ }
133
+ }
134
+
120
135
  function getErrorMessage(error: unknown): string {
121
136
  return error instanceof Error ? error.message : String(error);
122
137
  }
@@ -201,6 +216,7 @@ function logSweepAccounts(
201
216
 
202
217
  if (lastSweepLine.get(target.accountId) === line) continue;
203
218
  lastSweepLine.set(target.accountId, line);
219
+ trimLastSweepLine();
204
220
 
205
221
  // A failed quota fetch or a failed check-in is operator-actionable.
206
222
  if (outcome.failure !== null || result?.state === "error") {
@@ -219,7 +235,16 @@ function logSweepAccounts(
219
235
  }
220
236
  }
221
237
 
222
- const DEFAULT_MIN_AGE_MS = 4 * 60_000;
238
+ /**
239
+ * How stale a cached quota reading must be before it is refreshed again.
240
+ *
241
+ * Half the cache TTL. The sweep runs every 60 s, so at the old 4 min every
242
+ * account was refreshed roughly three times per 15 min TTL window — two of
243
+ * those three passes hit the provider and learned nothing the cache did not
244
+ * already know. 8 min is still comfortably inside the TTL, so a reading never
245
+ * goes stale while it waits.
246
+ */
247
+ const DEFAULT_MIN_AGE_MS = 8 * 60_000;
223
248
  const DEFAULT_MAX_PER_PASS = 40;
224
249
  const DEFAULT_PASS_BUDGET_MS = 90_000;
225
250
 
@@ -1 +0,0 @@
1
- import{t as e}from"./jsx-runtime-CXUMngI_.js";import{$ as t}from"./entry-Cpi0rph1.js";var n=e();function r(){return(0,n.jsx)(`main`,{className:`auth-viewport`,children:(0,n.jsxs)(`div`,{className:`card-solid auth-window border border-red-soft`,children:[(0,n.jsxs)(`div`,{className:`auth-header`,children:[(0,n.jsx)(`div`,{"aria-hidden":`true`,className:`auth-logo bg-red bg-none`,children:(0,n.jsx)(t,{size:24})}),(0,n.jsx)(`h1`,{className:`auth-title`,children:`Access Restricted`}),(0,n.jsx)(`p`,{className:`auth-desc`,children:`Security policy lockout or untrusted origin address`})]}),(0,n.jsxs)(`div`,{className:`rounded-panel border border-inner-border bg-surface-2 p-7 text-caption leading-relaxed text-text-secondary`,children:[(0,n.jsx)(`strong`,{className:`block text-text-primary mb-2`,children:`HTTP 403 Forbidden`}),`This client IP has been temporarily locked out due to repeated invalid credential attempts or untrusted proxy headers.`]})]})})}export{r as default};
@@ -1 +0,0 @@
1
- import{n as e,o as t,r as n,t as r}from"./jsx-runtime-CXUMngI_.js";import{o as i}from"./app-BDJWMThu.js";import{t as a}from"./arrow-right-Dl5yKnp8.js";import{t as o}from"./select-CGUuTQmj.js";import{t as s}from"./SortableList-Bol1ASmI.js";import{i as c,n as l,t as u}from"./toast-DKAdP2mo.js";import{t as d}from"./copy-ngmQ9zcZ.js";import{n as ee,r as te,t as f}from"./state-DHKdg-pm.js";import{t as p}from"./layers-2-glBbjl.js";import{t as m}from"./dialog-iocKA3jr.js";import{t as h}from"./pencil-768ysRAr.js";import{t as g}from"./plus-DIdX_g-x.js";import{t as ne}from"./route-CNSNj9Zm.js";import{t as _}from"./search-JOKus9rb.js";import{t as v}from"./trash-Cppn5c3R.js";import{c as y}from"./query-keys-ROf-g1Wi.js";import{t as b}from"./dialog-actions-B1axpEg7.js";import{n as x,r as re,t as ie}from"./card-BTdbRFFg.js";import{t as ae}from"./use-clipboard-B0Sy03fE.js";import{n as S}from"./input-Bn4_kNav.js";import{t as C}from"./ModelPicker-CAiPR5Vd.js";import{t as w}from"./stack-C1kTcOmP.js";import{n as T,t as E}from"./inline-DCejSLlA.js";import{t as D}from"./list-row-BLA0sldD.js";import{a as O,d as k,f as A,i as j,n as M,o as N,p as P,r as F,s as I,t as oe,u as L}from"./routing-jntw9-Ui.js";import{t as R}from"./use-timeout-BwcBBRrT.js";var z=e(`copy-plus`,[[`line`,{x1:`15`,x2:`15`,y1:`12`,y2:`18`,key:`1p7wdc`}],[`line`,{x1:`12`,x2:`18`,y1:`15`,y2:`15`,key:`1nscbv`}],[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),B=t(n(),1),V=r(),H={plain:`code-chip-plain`,muted:`code-chip-muted text-text-secondary bg-surface-muted border border-inner-border px-4 py-1 rounded-chip`,accent:`code-chip-accent text-accent bg-accent-soft px-4 py-1.5 rounded-chip font-bold`};function U({value:e,tone:t=`muted`,action:n,className:r=``,children:i,...a}){return(0,V.jsxs)(`span`,{className:c(`code-chip inline-flex items-center gap-3 min-w-0 max-w-full font-mono text-caption tabular-nums`,H[t],r),...a,children:[(0,V.jsx)(`code`,{className:`code-chip-value min-w-0 overflow-hidden text-ellipsis whitespace-nowrap`,title:a.title??e,children:e}),n?(0,V.jsx)(`span`,{className:`code-chip-action inline-flex shrink-0`,children:n}):null,i]})}var W=[{value:`fallback`,label:`Fallback (try in order)`},{value:`round_robin`,label:`Round Robin (rotate)`}],G=()=>`m${Date.now().toString(36)}${Math.random().toString(36).slice(2,8)}`;function K(){let e=N(),t=M(),n=A(),r=j(),i=L(),[o,c]=(0,B.useState)(!1),[p,O]=(0,B.useState)(null),[k,P]=(0,B.useState)(``),[F,I]=(0,B.useState)(``),[oe,z]=(0,B.useState)(!1),[H,W]=(0,B.useState)(null),[G,K]=(0,B.useState)(null),{copy:q}=ae(),J=R(),Y=(0,B.useMemo)(()=>e.data??[],[e.data]),X=()=>{O(null),P(``),I(``),c(!0)},Z=e=>{O(e),P(e.alias),I(e.targetModel),c(!0)},Q=e=>{e.preventDefault(),k.trim()&&F.trim()&&(p?n.mutate({id:p.id,request:{alias:k.trim(),targetModel:F.trim()}},{onSuccess:()=>{c(!1),O(null),u.success(`Alias updated`)},onError:e=>u.error(y(e,`Could not save the alias.`))}):t.mutate({alias:k.trim(),targetModel:F.trim()},{onSuccess:()=>{c(!1),P(``),I(``),u.success(`Alias created`)},onError:e=>u.error(y(e,`Could not create the alias.`))}))},se=(e,t)=>{q(e).then(e=>{if(!e){u.error(`Copy failed`);return}K(t),J(()=>K(null),1500)})};return(0,V.jsxs)(ie,{children:[(0,V.jsx)(re,{title:`Model Aliases`,subtitle:`Map readable names to real models (e.g. claude-mythos-5 → claude-opus-5)`,icon:(0,V.jsx)(ne,{size:16}),action:(0,V.jsx)(E,{children:(0,V.jsx)(l,{variant:`primary`,size:`sm`,icon:(0,V.jsx)(g,{size:13}),onClick:X,children:`New Alias`})})}),(0,V.jsx)(x,{children:e.isPending?(0,V.jsx)(te,{label:`Loading aliases...`}):e.isError?(0,V.jsx)(ee,{message:`Failed to load aliases`,onRetry:()=>e.refetch()}):Y.length===0?(0,V.jsx)(f,{title:`No aliases defined`,message:`Create an alias to route short or friendly names to real provider model IDs.`}):(0,V.jsx)(s,{items:Y,label:`Model aliases`,gap:`8px`,disabled:i.isPending,onReorder:e=>i.mutate(e,{onError:e=>u.error(y(e,`Could not save the new order.`))}),renderItem:e=>(0,V.jsx)(D,{className:`items-start flex-col sm:flex-row`,actions:(0,V.jsxs)(T,{gap:`4px`,className:`w-full justify-start sm:w-auto sm:justify-end`,children:[(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(d,{size:13}),onClick:()=>se(e.alias,e.id),title:G===e.id?`Copied!`:`Copy alias`}),(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(h,{size:13}),onClick:()=>Z(e),title:`Edit target model`}),(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(v,{size:13}),onClick:()=>W(e),title:`Delete alias`,className:`text-red`})]}),children:(0,V.jsxs)(`div`,{className:`flex items-center gap-4 flex-wrap min-w-0`,children:[(0,V.jsx)(U,{value:e.alias,tone:`accent`}),(0,V.jsx)(a,{size:13,className:`text-text-tertiary shrink-0`}),(0,V.jsx)(U,{value:e.targetModel,tone:`plain`})]})})})}),(0,V.jsx)(m,{open:o,onClose:()=>c(!1),title:p?`Edit Model Alias`:`New Model Alias`,footer:(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(l,{variant:`secondary`,type:`button`,onClick:()=>c(!1),children:`Cancel`}),(0,V.jsx)(l,{variant:`primary`,type:`submit`,form:`alias-form`,disabled:t.isPending||n.isPending||!k.trim()||!F.trim(),children:p?`Save Changes`:`Create Alias`})]}),children:(0,V.jsxs)(`form`,{id:`alias-form`,onSubmit:Q,className:`flex flex-col gap-6`,children:[(0,V.jsx)(S,{label:`Alias name`,id:`alias-name-input`,value:k,onChange:e=>P(e.target.value),placeholder:`e.g. fast, sonnet, smart`,required:!0}),(0,V.jsxs)(T,{gap:`8px`,align:`flex-end`,children:[(0,V.jsx)(`div`,{className:`flex-1`,children:(0,V.jsx)(S,{label:`Target model`,id:`target-model-input`,value:F,onChange:e=>I(e.target.value),placeholder:`e.g. codex/gpt-5.5, claude/claude-sonnet-5`,required:!0})}),(0,V.jsx)(l,{variant:`secondary`,size:`sm`,type:`button`,onClick:()=>z(!0),icon:(0,V.jsx)(_,{size:13}),children:`Browse`})]}),(0,V.jsx)(C,{open:oe,onClose:()=>z(!1),selected:F?[F]:[],onToggle:()=>{},onSelectOne:e=>I(e),title:`Select target model`,multi:!1})]})}),(0,V.jsx)(m,{open:!!H,onClose:()=>W(null),title:`Delete Model Alias`,children:(0,V.jsxs)(w,{gap:`12px`,children:[(0,V.jsxs)(`p`,{className:`text-body text-text-secondary`,children:[`Are you sure you want to delete alias `,(0,V.jsx)(`strong`,{children:H?.alias}),`? Clients requesting this name will need their full model path.`]}),(0,V.jsxs)(b,{children:[(0,V.jsx)(l,{variant:`secondary`,onClick:()=>W(null),children:`Cancel`}),(0,V.jsx)(l,{variant:`danger`,onClick:()=>{H&&r.mutate(H.id,{onSuccess:()=>W(null)})},disabled:r.isPending,children:r.isPending?`Deleting...`:`Delete Alias`})]})]})})]})}function q(){let e=I(),t=F(),n=oe(),r=P(),a=O(),c=k(),[ne,A]=(0,B.useState)(!1),[j,M]=(0,B.useState)(null),[N,L]=(0,B.useState)(``),[H,K]=(0,B.useState)([]),[q,J]=(0,B.useState)(`fallback`),[Y,X]=(0,B.useState)(!1),[Z,Q]=(0,B.useState)(null),[se,ce]=(0,B.useState)(null),{copy:le}=ae(),ue=R(),$=(0,B.useMemo)(()=>H.map(e=>e.value),[H]),de=e=>{$.includes(e)?K(H.filter(t=>t.value!==e)):K([...H,{id:G(),value:e.trim()}])},fe=e.data??[],pe=()=>{M(null),L(``),K([]),J(`fallback`),A(!0)},me=e=>{M(e),L(e.name),K(e.members.map(e=>({id:G(),value:e}))),A(!0)},he=e=>{n.mutate(e.id,{onSuccess:e=>{let t=e.skippedMembers;t.length>0?u.success(`Combo cloned`,`${e.combo.name} — skipped unresolvable member${t.length>1?`s`:``}: ${t.join(`, `)}`):u.success(`Combo cloned`,e.combo.name)},onError:e=>u.error(y(e,`Could not clone the combo.`))})},ge=e=>{e.preventDefault(),N.trim()&&$.length!==0&&(j?r.mutate({id:j.id,request:{name:N.trim(),members:$,strategy:q}},{onSuccess:()=>{A(!1),M(null),u.success(`Combo saved`)},onError:e=>u.error(y(e,`Could not save the combo.`))}):t.mutate({name:N.trim(),members:$,strategy:q},{onSuccess:()=>{A(!1),L(``),K([]),u.success(`Combo created`)},onError:e=>u.error(y(e,`Could not create the combo.`))}))},_e=(e,t)=>{r.mutate({id:e.id,request:{strategy:t}},{onSuccess:()=>u.success(`Combo strategy updated`),onError:e=>u.error(y(e,`Could not update the combo strategy.`))})},ve=(e,t)=>{le(e).then(e=>{if(!e){u.error(`Copy failed`);return}ce(t),ue(()=>ce(null),1500)})};return(0,V.jsxs)(ie,{children:[(0,V.jsx)(re,{title:`Combos`,subtitle:`Combine multiple models with fallback or round-robin rotation`,icon:(0,V.jsx)(p,{size:16}),action:(0,V.jsx)(E,{children:(0,V.jsx)(l,{variant:`primary`,size:`sm`,icon:(0,V.jsx)(g,{size:13}),onClick:pe,children:`New Combo`})})}),(0,V.jsx)(x,{children:e.isPending?(0,V.jsx)(te,{label:`Loading combos...`}):e.isError?(0,V.jsx)(ee,{message:`Failed to load combos`,onRetry:()=>e.refetch()}):fe.length===0?(0,V.jsx)(f,{title:`No combos defined`,message:`Create a model combo to distribute requests across multiple models.`}):(0,V.jsx)(s,{items:fe,label:`Model combos`,disabled:c.isPending,onReorder:e=>c.mutate(e,{onError:e=>u.error(y(e,`Could not save the new order.`))}),renderItem:e=>(0,V.jsxs)(D,{className:`items-start flex-col sm:flex-row`,actions:(0,V.jsxs)(T,{gap:`8px`,className:`w-full justify-between sm:w-auto sm:justify-start`,children:[(0,V.jsx)(o,{value:e.strategy,onValueChange:t=>_e(e,t),options:W}),(0,V.jsxs)(T,{gap:`4px`,className:`flex-wrap`,children:[(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(d,{size:13}),onClick:()=>ve(e.name,e.id),title:se===e.id?`Copied!`:`Copy combo name`}),(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(z,{size:13}),onClick:()=>he(e),disabled:n.isPending,title:`Clone combo (same members and strategy)`}),(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(h,{size:13}),onClick:()=>me(e),title:`Edit combo`}),(0,V.jsx)(l,{variant:`ghost`,size:`sm`,icon:(0,V.jsx)(v,{size:13}),onClick:()=>Q(e),title:`Delete combo`,className:`text-red`})]})]}),children:[(0,V.jsx)(`div`,{className:`flex items-center justify-between gap-6 flex-wrap`,children:(0,V.jsx)(`strong`,{className:`text-body`,children:e.name})}),(0,V.jsx)(T,{gap:`6px`,className:`w-full flex-wrap`,children:e.members.map(e=>(0,V.jsx)(U,{value:e},e))})]})})}),(0,V.jsx)(m,{open:ne,onClose:()=>A(!1),title:j?`Edit Model Combo`:`New Model Combo`,footer:(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(l,{variant:`secondary`,type:`button`,onClick:()=>A(!1),children:`Cancel`}),(0,V.jsx)(l,{variant:`primary`,type:`submit`,form:`combo-form`,disabled:t.isPending||r.isPending||!N.trim()||$.length===0,children:j?`Save Changes`:`Create Combo`})]}),children:(0,V.jsxs)(`form`,{id:`combo-form`,onSubmit:ge,className:`flex flex-col gap-6`,children:[(0,V.jsx)(S,{label:`Combo name`,id:`combo-name-input`,value:N,onChange:e=>L(e.target.value),placeholder:`e.g. smart-combo, fast-pool`,required:!0}),(0,V.jsx)(o,{label:`Strategy`,id:`combo-strategy-select`,value:q,onValueChange:e=>J(e),options:W}),(0,V.jsxs)(w,{gap:`6px`,children:[(0,V.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,V.jsx)(`label`,{className:`text-caption font-semibold text-text-secondary`,children:`Member models — use arrows or drag to reorder`}),(0,V.jsx)(l,{variant:`secondary`,size:`sm`,type:`button`,icon:(0,V.jsx)(_,{size:12}),onClick:()=>X(!0),children:`Browse models`})]}),(0,V.jsx)(`div`,{role:`list`,"aria-label":`Combo members`,"data-testid":`combo-members-list`,className:`flex min-h-22 flex-col gap-2 rounded-control border border-dashed border-inner-border ${H.length===0?`p-0`:`p-3`}`,children:H.length===0?(0,V.jsx)(`button`,{type:`button`,onClick:()=>X(!0),className:`flex-1 cursor-pointer font-inherit text-caption text-text-tertiary`,children:`Pick models to add`}):(0,V.jsx)(s,{items:H,label:`Combo members`,gap:`4px`,onReorder:e=>{let t=new Map(H.map(e=>[e.id,e]));K(e.map(e=>t.get(e)).filter(Boolean))},renderItem:e=>(0,V.jsx)(D,{density:`compact`,actions:(0,V.jsx)(`button`,{type:`button`,onClick:()=>de(e.value),"aria-label":`Remove ${e.value}`,className:`flex shrink-0 cursor-pointer text-text-tertiary`,children:(0,V.jsx)(i,{size:12})}),children:(0,V.jsx)(U,{value:e.value,tone:`accent`})})})})]}),(0,V.jsx)(C,{open:Y,onClose:()=>X(!1),selected:$,onToggle:de,title:`Select combo members`,multi:!0})]})}),(0,V.jsx)(m,{open:!!Z,onClose:()=>Q(null),title:`Delete Model Combo`,children:(0,V.jsxs)(w,{gap:`12px`,children:[(0,V.jsxs)(`p`,{className:`text-body text-text-secondary`,children:[`Are you sure you want to delete combo `,(0,V.jsx)(`strong`,{children:Z?.name}),`? Any aliases pointing to it will fail to resolve.`]}),(0,V.jsxs)(b,{children:[(0,V.jsx)(l,{variant:`secondary`,onClick:()=>Q(null),children:`Cancel`}),(0,V.jsx)(l,{variant:`danger`,onClick:()=>{Z&&a.mutate(Z.id,{onSuccess:()=>Q(null)})},disabled:a.isPending,children:a.isPending?`Deleting...`:`Delete Combo`})]})]})})]})}function J(){return(0,V.jsxs)(w,{gap:`16px`,children:[(0,V.jsx)(q,{}),(0,V.jsx)(K,{})]})}export{J as default};
@@ -1 +0,0 @@
1
- import{o as e,r as t,t as n}from"./jsx-runtime-CXUMngI_.js";import{n as r,t as i}from"./toast-DKAdP2mo.js";import{n as a,r as o,t as s}from"./state-DHKdg-pm.js";import{t as c}from"./refresh-cw-BqJFk4sU.js";import{t as l}from"./search-JOKus9rb.js";import{t as u}from"./shield-check-DVS0Foqh.js";import{t as d}from"./trash-Cppn5c3R.js";import{a as f,c as p,l as m}from"./query-keys-ROf-g1Wi.js";import{D as h,F as g,O as _,tt as v}from"./entry-Cpi0rph1.js";import{n as y,r as b,t as x}from"./card-BTdbRFFg.js";import{r as S}from"./numbers-CFL4_Dqc.js";import{t as C}from"./stream-retry-5QnVuSSo.js";import{t as w}from"./badge-BwmwEOgA.js";import{n as T}from"./input-Bn4_kNav.js";import{t as E}from"./layout-Bv-NxqZ5.js";var D=e(t(),1),O=500,k=1500;function A(e){return m(e)&&typeof e.ts==`string`&&(e.level===`debug`||e.level===`info`||e.level===`warn`||e.level===`error`)&&typeof e.msg==`string`}function j(e){return!m(e)||!Array.isArray(e.lines)?[]:e.lines.filter(A)}function M(){let[e,t]=(0,D.useState)([]),[n,r]=(0,D.useState)(new Set),[i,a]=(0,D.useState)(`idle`),o=(0,D.useRef)(0),s=(0,D.useRef)(0),c=(0,D.useRef)(new Set),l=e=>({...e,id:`l${s.current+=1}`});return(0,D.useEffect)(()=>{let e=!1,n,i=o.current+=1,s=(e,t)=>{let n=window.setTimeout(()=>{c.current.delete(n),e()},t);c.current.add(n)},u=e=>{let n=l(e);r(e=>{let t=new Set(e);return t.add(n.id),t}),s(()=>{r(e=>{if(!e.has(n.id))return e;let t=new Set(e);return t.delete(n.id),t})},k),t(e=>{let t=[...e,n];return t.length>O?t.slice(t.length-O):t})},d=()=>{n=new EventSource(`/console/api/logs/stream`),n.addEventListener(`init`,n=>{if(!e)try{let e=JSON.parse(n.data);m(e)&&(t(j(e).slice(-500).map(l)),r(new Set)),a(`live`)}catch{}}),n.addEventListener(`line`,t=>{if(!e)try{let e=JSON.parse(t.data);A(e)&&(u(e),a(`live`))}catch{}}),n.addEventListener(`clear`,()=>{e||(t([]),r(new Set))}),n.onerror=()=>{n?.close(),!e&&(a(`reconnecting`),s(()=>{!e&&i===o.current&&p()},C))}},p=async()=>{try{let n=await f(`/logs?limit=200`);if(e||i!==o.current)return;t(j(n).slice(-500).map(l)),r(new Set),d()}catch{}};return p(),()=>{e=!0;for(let e of c.current)window.clearTimeout(e);c.current.clear(),n?.close()}},[]),{lines:e,newLineIds:n,status:i,clear:(0,D.useCallback)(async()=>{await f(`/logs`,{method:`DELETE`}),t([]),r(new Set)},[])}}var N={"api_key.created":`API key created`,"api_key.updated":`API key updated`,"api_key.revoked":`API key revoked`,"api_key.regenerated":`API key regenerated`,"api_key.shared":`API key shared`,"api_key.share_regenerated":`API key share link regenerated`,"api_key.share_revoked":`API key unshared`,"provider.created":`Provider created`,"provider.updated":`Provider updated`,"provider.deleted":`Provider deleted`,"provider.global.updated":`Global provider updated`,"provider.global.deleted":`Global provider deleted`,"provider.routing.updated":`Provider routing updated`,"provider.models.registered":`Provider models registered`,"provider.models.bulk_deleted":`Provider models bulk-deleted`,"provider.model.deleted":`Provider model deleted`,"provider.model.enabled":`Provider model enabled`,"provider.model.disabled":`Provider model disabled`,"provider_account.created":`Provider account connected`,"provider_account.updated":`Provider account updated`,"provider_account.recovered":`Provider account recovered`,"provider_account.revoked":`Provider account revoked`,"provider_account.circuit_force_closed":`Provider account circuit force-closed`,"provider_account.deleted":`Provider account deleted`,"provider_account.exported":`Provider accounts exported (plaintext)`,"provider_account.global.updated":`Global provider account updated`,"provider_account.global.refreshed":`Global provider account refreshed`,"provider_account.global.deleted":`Global provider account deleted`,"model_alias.created":`Model alias created`,"model_alias.updated":`Model alias updated`,"model_alias.deleted":`Model alias deleted`,"model_combo.created":`Model combo created`,"model_combo.updated":`Model combo updated`,"model_combo.cloned":`Model combo cloned`,"model_combo.deleted":`Model combo deleted`,"network_pool.created":`Network pool created`,"network_pool.updated":`Network pool updated`,"network_pool.deleted":`Network pool deleted`,"network_pool.recovered":`Network pool recovered`,"network_pool.strategy_updated":`Network pool strategy updated`,"network_pool.relay_deployed":`Network pool relay deployed`,"filter_rule.created":`Filter rule created`,"filter_rule.updated":`Filter rule updated`,"filter_rule.deleted":`Filter rule deleted`,"filter_rule.reordered":`Filter rules reordered`,"filter_rule.master_toggle":`Filter master switch toggled`,"settings.runtime.updated":`Runtime settings updated`,"cli_tool.mappings_saved":`CLI tool mappings saved`,"cli_tool.mappings_reset":`CLI tool mappings reset`,"cli_tool.applied":`CLI tool applied`,"console.password_changed":`Console password changed`,"console_logs.cleared":`Console logs cleared`,"security.ip_banned":`IP banned`,"backup.reset":`Backup reset`,"backup.delete_all":`Delete all configuration`},P={api:`API`,ip:`IP`,cli:`CLI`,oauth:`OAuth`};function F(e){return e.split(`.`).map(e=>e.split(/[_-]+/).filter(Boolean).map(e=>P[e.toLowerCase()]??e.charAt(0).toUpperCase()+e.slice(1)).join(` `)).join(` · `)}function I(e){return N[e]??F(e)}var L=n(),R=50,z=[{id:`all`,label:`All`},{id:`debug`,label:`Debug`},{id:`info`,label:`Info`},{id:`warn`,label:`Warn`},{id:`error`,label:`Error`}],B={debug:`var(--text-tertiary)`,info:`var(--accent)`,warn:`var(--orange)`,error:`var(--red)`},V={debug:`DBG`,info:`INF`,warn:`WRN`,error:`ERR`};function H(e){return e>=500?`err`:e>=400?`warn`:`ok`}function U(e,t,n){let r=n.trim().toLowerCase();return e.filter(e=>t!==`all`&&e.level!==t?!1:!r||`${e.msg} ${e.endpoint??``} ${e.model??``} ${e.routedModel??``} ${e.providerId??``} ${e.errorCode??``} ${e.userAgent??``} ${e.clientIp??``} ${e.accountLabel??``}`.toLowerCase().includes(r))}function W(){let[e,t]=(0,D.useState)(`logs`),n=_().data?.isPlatformAdmin??!1,i=e===`audit`&&!n?`logs`:e,a=(0,L.jsxs)(`div`,{className:`flex gap-4`,children:[(0,L.jsx)(r,{variant:i===`logs`?`primary`:`secondary`,size:`sm`,onClick:()=>t(`logs`),icon:(0,L.jsx)(v,{size:13}),"aria-pressed":i===`logs`,children:`Console Log`}),n?(0,L.jsx)(r,{variant:i===`audit`?`primary`:`secondary`,size:`sm`,onClick:()=>t(`audit`),icon:(0,L.jsx)(u,{size:13}),"aria-pressed":i===`audit`,children:`Admin Audit`}):null]});return(0,L.jsx)(`div`,{className:`flex flex-col flex-1 min-h-0`,children:i===`logs`?(0,L.jsx)(Q,{tabAction:a}):(0,L.jsx)($,{tabAction:a})})}function G(e){let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleTimeString([],{hour12:!1})}function K(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function q(e){let t=e.details;if(!t)return null;let n=typeof t.inputTokens==`number`?t.inputTokens:void 0,r=typeof t.outputTokens==`number`?t.outputTokens:void 0,i=typeof t.cachedInputTokens==`number`?t.cachedInputTokens:void 0,a=typeof t.reasoningTokens==`number`?t.reasoningTokens:void 0;if(n===void 0&&r===void 0&&i===void 0&&a===void 0)return null;let o=[];return n!==void 0&&o.push(`${K(n)} in`),r!==void 0&&o.push(`${K(r)} out`),i!==void 0&&o.push(`${K(i)} cached`),a!==void 0&&a>0&&o.push(`${K(a)} reason`),o.join(` / `)}function J(e){let t=e.model,n=e.routedModel,r=e.providerId,i=n??t;if(!i&&!r)return null;let a=r&&i?`${r}/${i}`:i??r??``;return n&&t&&n!==t?`${a} ← ${t}`:a}var Y=(0,D.memo)(function({line:e,isNew:t}){let n=e.event!==void 0,r=e.level;return(0,L.jsxs)(`div`,{className:`flex flex-wrap items-baseline gap-4 px-8 py-1.5 font-mono text-caption leading-normal max-medium:gap-3 max-medium:px-5 max-medium:text-badge${r===`warn`?` bg-orange-soft`:r===`error`?` bg-red-soft`:``}${t?` bg-surface-muted`:``} hover:bg-surface-hover`,"data-log-tone":r,children:[(0,L.jsx)(`span`,{className:`max-medium:flex-none flex-none tabular-nums text-text-tertiary`,children:G(e.ts)}),(0,L.jsx)(`span`,{className:`flex-none w-13 text-left text-micro font-bold tracking-label`,style:{color:B[e.level]},title:e.level.toUpperCase(),children:V[e.level]}),n?(0,L.jsx)(X,{line:e}):(0,L.jsx)(`span`,{className:`min-w-0 flex-1 whitespace-pre-wrap wrap-anywhere break-words text-text-secondary`,children:e.msg})]})});function X({line:e}){let t=J(e),n=q(e),r=Z(e);return(0,L.jsxs)(`span`,{className:`min-w-0 flex-1 whitespace-pre-wrap wrap-anywhere`,children:[(0,L.jsxs)(`span`,{className:`font-bold tracking-label ${r.tone}`,"data-log-field":`badge`,children:[`[`,r.label,`]`]}),` `,(0,L.jsx)(`span`,{className:`text-text-tertiary`,children:e.method??`POST`}),` `,(0,L.jsx)(`span`,{className:`text-text-primary`,children:e.endpoint??``}),e.status===void 0?null:(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`tabular-nums ${H(e.status)===`ok`?`text-text-tertiary`:H(e.status)===`warn`?`text-orange font-semibold`:`text-red font-semibold`}`,children:e.status})]}),e.durationMs===void 0?null:(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`tabular-nums text-text-secondary`,children:S(e.durationMs)})]}),t?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`text-status-info`,"data-log-field":`route`,children:t})]}):null,e.accountLabel?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`text-purple`,"data-log-field":`account`,title:e.accountId??void 0,children:e.accountLabel})]}):null,e.clientIp?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`tabular-nums text-text-secondary`,"data-log-field":`ip`,children:e.clientIp})]}):null,n?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`tabular-nums text-orange`,"data-log-field":`tokens`,children:n})]}):null,e.errorCode?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsxs)(`span`,{className:`text-red`,title:e.msg,children:[e.msg,` (`,e.errorCode,`)`]})]}):null,e.requestId?(0,L.jsxs)(L.Fragment,{children:[` `,(0,L.jsx)(`span`,{className:`text-micro text-text-tertiary`,"data-log-field":`id`,children:e.requestId.slice(0,8)})]}):null]})}function Z(e){return e.event===`request_start`||e.status===void 0?{label:`REQUEST`,tone:`text-text-tertiary`}:e.event===`request_error`||e.status>=400?{label:`FAILED`,tone:`text-red`}:{label:`SUCCESS`,tone:`text-status-info`}}function Q({tabAction:e}){let{lines:t,newLineIds:n,clear:r}=M(),[a,o]=(0,D.useState)(`all`),[c,u]=(0,D.useState)(``),[f,p]=(0,D.useState)(!1),[m,h]=(0,D.useState)(null),_=c.trim().toLowerCase(),S=(0,D.useMemo)(()=>U(t,a,_).slice().reverse(),[t,a,_]),C=(0,D.useMemo)(()=>{let e=new Map([[`all`,t.length]]);for(let n of t)e.set(n.level,(e.get(n.level)??0)+1);return e},[t]);return(0,L.jsxs)(x,{className:`flex-1 min-h-0 flex flex-col`,children:[(0,L.jsx)(b,{title:`Console Log`,subtitle:`${S.length} of ${t.length} lines`,icon:(0,L.jsx)(v,{size:16}),action:e}),(0,L.jsxs)(y,{className:`flex flex-row items-center justify-between gap-8 flex-none min-h-0 px-7 py-4 border-b border-inner-border max-medium:flex-col max-medium:items-start max-medium:gap-4`,children:[(0,L.jsx)(`div`,{className:`inline-flex items-center gap-1.5 flex-none max-medium:w-full max-medium:overflow-x-auto`,role:`group`,"aria-label":`Log level`,children:z.map(e=>{let t=C.get(e.id)??0;return(0,L.jsxs)(`button`,{className:`inline-flex items-center appearance-none border-0 rounded-tiny px-5 py-3.5 bg-transparent font-sans text-badge font-semibold leading-none cursor-pointer ${a===e.id?`bg-surface-muted text-text-primary`:`text-text-tertiary hover:bg-surface-muted hover:text-text-primary`}`,type:`button`,onClick:()=>o(e.id),"aria-pressed":a===e.id,children:[(0,L.jsx)(`span`,{children:e.label}),(0,L.jsx)(`span`,{className:`ml-2.5 text-inherit opacity-65 tabular-nums`,children:t})]},e.id)})}),(0,L.jsxs)(`div`,{className:`flex items-center gap-4 ml-auto max-medium:w-full max-medium:ml-0 max-medium:min-w-0`,children:[(0,L.jsxs)(`div`,{className:`relative w-140 min-w-0 max-w-full max-medium:flex-auto max-medium:w-auto`,children:[(0,L.jsx)(l,{size:13,"aria-hidden":`true`,className:`absolute left-5 top-1/2 -translate-y-1/2 text-text-tertiary pointer-events-none`}),(0,L.jsx)(T,{"aria-label":`Search console logs`,value:c,onChange:e=>u(e.target.value),placeholder:`Search logs...`,className:`pl-15 w-full min-w-0`})]}),(0,L.jsx)(`button`,{className:`inline-grid place-items-center w-16 h-16 border border-border-subtle rounded-control bg-transparent text-text-secondary cursor-pointer hover:border-red hover:text-red hover:bg-red-soft`,type:`button`,onClick:()=>p(!0),"aria-label":`Clear console logs`,title:`Clear logs`,children:(0,L.jsx)(d,{size:15,"aria-hidden":`true`})})]})]}),(0,L.jsxs)(y,{className:`p-0 flex-1 min-h-0 flex flex-col`,children:[(0,L.jsx)(`div`,{className:`scroll-region flex-1 min-h-0 flex flex-col p-0 bg-code-surface text-text-primary`,children:S.length===0?(0,L.jsx)(s,{title:t.length===0?`No log lines yet`:_?`No lines match your search`:`No lines match this level`,message:t.length===0?`Server log output will stream here as the gateway runs.`:`Adjust the search or pick another level to widen the view.`,icon:(0,L.jsx)(v,{size:20})}):S.map(e=>(0,L.jsx)(Y,{line:e,isNew:n.has(e.id)},e.id))}),(0,L.jsx)(`div`,{className:`flex items-center justify-between gap-5 shrink-0 px-8 py-4 border-t border-inner-border bg-surface-2 text-micro text-text-tertiary`,"aria-live":`polite`,children:(0,L.jsxs)(`span`,{className:`min-w-0 overflow-hidden text-ellipsis whitespace-nowrap`,children:[S.length,` of `,t.length,` lines`,a===`all`?``:` · ${a}`,_?` · search: "${c.trim()}"`:``]})})]}),(0,L.jsx)(g,{open:f,onClose:()=>p(!1),onConfirm:async()=>{h(null);try{await r(),p(!1),i.success(`Console logs cleared`)}catch{h(`Failed to clear server logs.`)}},title:`Clear console logs`,message:`Clear the in-memory server log tail? New lines keep streaming afterwards.`,confirmLabel:`Clear`,danger:!0}),m?(0,L.jsx)(`span`,{className:`text-badge text-red pt-0 px-8 pb-4`,children:m}):null]})}function $({tabAction:e}){let[t,n]=(0,D.useState)(``),[i,l]=(0,D.useState)(``),[d,f]=(0,D.useState)(``),[m,g]=(0,D.useState)(``),[_,v]=(0,D.useState)(void 0),[S,C]=(0,D.useState)([]);(0,D.useEffect)(()=>{let e=setTimeout(()=>f(t),300);return()=>clearTimeout(e)},[t]),(0,D.useEffect)(()=>{let e=setTimeout(()=>g(i),300);return()=>clearTimeout(e)},[i]);let w=h({limit:R,...d?{action:d}:{},...m?{actor:m}:{},..._?{cursor:_}:{}}),O=w.data,k=O?.entries??[],A=O?.nextCursor,j=()=>{A&&(C(e=>[...e,_??``]),v(A))},M=()=>{C(e=>{let t=e[e.length-1];return v(t||void 0),e.slice(0,-1)})},N=()=>{v(void 0),C([])};return(0,L.jsxs)(x,{className:`flex-1 min-h-0 flex flex-col`,children:[(0,L.jsx)(b,{title:`Admin Audit Trail`,subtitle:`Every privileged console mutation, retained on its own lifecycle`,icon:(0,L.jsx)(u,{size:16}),action:(0,L.jsxs)(`div`,{className:`flex gap-4 items-center`,children:[e,(0,L.jsx)(r,{variant:`secondary`,size:`sm`,onClick:()=>void w.refetch(),disabled:w.isFetching,icon:(0,L.jsx)(c,{size:13,className:w.isFetching?`animate-spin`:``}),children:`Refresh`})]})}),(0,L.jsxs)(y,{className:`flex flex-row items-center justify-between gap-6 flex-none min-h-0 px-8 py-5 border-b border-inner-border items-end`,children:[(0,L.jsx)(T,{label:`Action`,placeholder:`e.g. provider updated`,value:t,onChange:e=>{n(e.target.value),N()}}),(0,L.jsx)(T,{label:`Actor`,placeholder:`e.g. alice or a user id`,value:i,onChange:e=>{l(e.target.value),N()}}),(0,L.jsxs)(`div`,{className:`ml-auto flex gap-4`,children:[(0,L.jsx)(r,{size:`sm`,variant:`secondary`,onClick:M,disabled:S.length===0,children:`← Newer`}),(0,L.jsx)(r,{size:`sm`,variant:`secondary`,onClick:j,disabled:!A,children:`Older →`})]})]}),(0,L.jsx)(y,{className:`p-0 flex-1 min-h-0 flex flex-col`,children:(0,L.jsx)(`div`,{className:`flex-1 min-h-0 flex flex-col overflow-y-auto p-0 bg-transparent text-inherit`,children:w.isPending&&k.length===0?(0,L.jsx)(o,{label:`Loading audit trail…`}):w.isError&&k.length===0?(0,L.jsx)(a,{message:p(w.error,`Unable to load audit trail.`),onRetry:()=>void w.refetch()}):k.length===0?(0,L.jsx)(s,{title:`No audit entries yet`,message:`Privileged mutations (provider changes, key rotations, backups, bans) will appear here.`,icon:(0,L.jsx)(u,{size:20})}):(0,L.jsx)(E,{headers:[`Timestamp`,`Actor`,`Action`,`Target`,`Detail`],children:k.map(e=>(0,L.jsx)(ee,{entry:e},e.id))})})})]})}function ee({entry:e}){return(0,L.jsxs)(`tr`,{children:[(0,L.jsx)(`td`,{className:`text-badge text-text-tertiary whitespace-nowrap`,children:new Date(e.createdAt).toLocaleString()}),(0,L.jsxs)(`td`,{className:`max-w-100`,children:[(0,L.jsx)(`div`,{className:`text-caption font-semibold`,children:e.actorName??e.actor}),e.actorName?(0,L.jsx)(`div`,{title:e.actor,className:`truncate font-mono tabular-nums text-micro text-text-tertiary`,children:e.actor}):null]}),(0,L.jsx)(`td`,{children:(0,L.jsx)(w,{tone:e.action.includes(`delete`)||e.action.includes(`revoked`)?`err`:`ok`,title:e.action,children:I(e.action)})}),(0,L.jsx)(`td`,{title:e.target,className:`truncate font-mono tabular-nums text-badge max-w-110`,children:e.target}),(0,L.jsx)(`td`,{className:`font-mono tabular-nums text-badge max-w-160 overflow-hidden truncate`,children:e.detail?JSON.stringify(e.detail):`—`})]})}export{W as default,U as filterLogLines,H as statusTone};
@@ -1 +0,0 @@
1
- import{o as e,r as t,t as n}from"./jsx-runtime-CXUMngI_.js";import{i as r}from"./terminal-C9mZ4Ugm.js";import{n as i,t as a}from"./toast-DKAdP2mo.js";import{n as o,r as s,t as c}from"./state-DHKdg-pm.js";import{t as l}from"./dialog-iocKA3jr.js";import{t as u}from"./rotate-ccw-CufcqITi.js";import{S as d,_ as f,w as p}from"./assert-Bwh6DJTK.js";import{a as m,t as h}from"./query-keys-ROf-g1Wi.js";import{t as g}from"./useMutation-DVmwOKU-.js";import{E as _,F as v,_ as y,n as b}from"./entry-Cpi0rph1.js";import{t as x}from"./badge-BwmwEOgA.js";import{n as S}from"./inline-DCejSLlA.js";import{t as C}from"./layout-Bv-NxqZ5.js";async function w(e){return m(`/quota/overview`,e?{signal:e}:void 0)}function T(){return d({queryKey:h.quota.all,queryFn:e=>w(f(e)),staleTime:1/0,refetchInterval:!1,refetchIntervalInBackground:!1,refetchOnMount:!1,refetchOnWindowFocus:!1,placeholderData:e=>e})}function E(e){let t=p(),n=h.quota.account(e);return g({mutationFn:async()=>m(`/accounts/${encodeURIComponent(e)}/quota/refresh`,{method:`POST`}),onSuccess:async()=>{await Promise.all([t.invalidateQueries({queryKey:n,exact:!0}),t.invalidateQueries({queryKey:h.quota.all}),t.invalidateQueries({queryKey:h.providers.all})])}})}var D=100;function O(){let e=p();return g({mutationFn:async e=>{let t=[];for(let n=0;n<e.length;n+=D)t.push(e.slice(n,n+D));let n=await Promise.all(t.map(e=>m(`/quota/refresh`,{method:`POST`,body:JSON.stringify({accountIds:e})}))),r=n.flatMap(e=>e.failures);return{ok:r.length===0,queued:n.reduce((e,t)=>e+t.queued,0),succeeded:n.reduce((e,t)=>e+t.succeeded,0),failed:n.reduce((e,t)=>e+t.failed,0),failures:r}},onSuccess:async(t,n)=>{let r=new Set(t.failures.map(e=>e.accountId)),i=n.filter(e=>!r.has(e));await Promise.all([...i.map(t=>e.invalidateQueries({queryKey:h.quota.account(t),exact:!0})),e.invalidateQueries({queryKey:h.quota.all})])}})}function k(){let e=p();return g({mutationFn:({id:e,active:t})=>m(`/accounts/${encodeURIComponent(e)}`,{method:`PATCH`,body:JSON.stringify({active:t})}),onSuccess:async()=>{await Promise.all([e.invalidateQueries({queryKey:h.quota.all}),e.invalidateQueries({queryKey:h.providers.all})])}})}function A(){let e=p();return g({mutationFn:async({ids:e,active:t})=>{e.length!==0&&await m(`/accounts/batch`,{method:`PATCH`,body:JSON.stringify({ids:e,active:t})})},onSuccess:async()=>{await Promise.all([e.invalidateQueries({queryKey:h.quota.all}),e.invalidateQueries({queryKey:h.providers.all})])}})}function j(){let e=p();return g({mutationFn:async e=>{await m(`/accounts/${encodeURIComponent(e)}`,{method:`DELETE`})},onSuccess:async(t,n)=>{e.removeQueries({queryKey:h.quota.account(n),exact:!0}),await Promise.all([e.invalidateQueries({queryKey:h.quota.all}),e.invalidateQueries({queryKey:h.providers.all})])}})}var M=new Set([`workbuddy`,`workbuddycn`,`cb`,`cbcn`]);function N(e){return M.has(e.trim().toLowerCase())}function P(e){let t=p();return g({mutationFn:async()=>{if(e.length===0)throw Error(`Select a provider first`);return m(`/quota/daily-growth`,{method:`POST`,body:JSON.stringify({providerId:e})})},onSuccess:async()=>{await Promise.all([t.invalidateQueries({queryKey:h.quota.all}),t.invalidateQueries({queryKey:h.providers.all})])}})}function F(e){let t=p();return g({mutationFn:async()=>{let t=await m(`/accounts/${encodeURIComponent(e)}/activity-report`,{method:`POST`}),n=t.message??`Activity reported`,r=await m(`/accounts/${encodeURIComponent(e)}/checkin`,{method:`POST`}),i=r.message??`Daily check-in done`;return{ok:t.ok,message:t.ok?`Report success · Check-in ${i}`:`Report failed: ${n} · Check-in ${i}`,checkin:r,report:t}},onSuccess:async()=>{await Promise.all([t.invalidateQueries({queryKey:h.quota.all}),t.invalidateQueries({queryKey:h.providers.all})])}})}var I=new Set([`codex`,`claude`,`anthropic`]);function L(e){return I.has(e.trim().toLowerCase())}function R(e,t){return d({queryKey:h.quota.resets(e),queryFn:({signal:t})=>m(`/accounts/${encodeURIComponent(e)}/resets`,t?{signal:t}:void 0),enabled:t,staleTime:3e4})}function z(e){let t=p();return g({mutationFn:async t=>m(`/accounts/${encodeURIComponent(e)}/reset`,{method:`POST`,body:JSON.stringify(t??{})}),onSettled:async()=>{await Promise.all([t.invalidateQueries({queryKey:h.quota.all}),t.invalidateQueries({queryKey:h.providers.all})])}})}var B=e(t(),1),V=n();function H(e){return e===`cooldown`}function U(e){return e===`active`?`ok`:e===`disabled`?`disabled`:`warn`}function W(e,t){return e?{title:`Enable static-token mode?`,message:`${t} will be used exactly as issued and will not be refreshed. Re-enable refresh before this token expires.`,confirmLabel:`Enable static mode`}:{title:`Re-enable credential refresh?`,message:`${t} will return to the OAuth refresh sweep. Use this only when a refresh token is available.`,confirmLabel:`Re-enable refresh`}}function G({summary:e,onClose:t}){let{providerId:n,accountId:d,title:f,status:p,errorCategory:m,errorMessage:h}=e,g=b(n,d),w=y(),T=g.data??[],E=_(),D=e.credentialKind===`oauth`||e.credentialMode===`jwt`||e.staticToken===!0,[O,k]=(0,B.useState)(e.staticToken===!0),[A,j]=(0,B.useState)(null);return(0,B.useEffect)(()=>k(e.staticToken===!0),[e.staticToken]),(0,V.jsx)(l,{open:!0,onClose:t,title:`Health & Error Log — ${f}`,size:`lg`,children:(0,V.jsxs)(`div`,{className:`flex flex-col gap-7 min-w-0`,children:[(0,V.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-5 rounded-control border border-inner-border bg-surface-2 py-5 px-7`,children:[(0,V.jsxs)(`div`,{className:`min-w-0`,children:[(0,V.jsxs)(S,{gap:`8px`,className:`flex-wrap`,children:[(0,V.jsx)(`span`,{className:`text-body font-semibold`,children:`Account status:`}),(0,V.jsx)(x,{tone:U(p),dot:!0,children:p.toUpperCase()}),m?(0,V.jsx)(x,{tone:`warn`,title:h??void 0,children:m}):null]}),h?(0,V.jsx)(`p`,{className:`text-badge text-red mt-2 wrap-anywhere`,children:h}):null]}),H(p)?(0,V.jsx)(i,{size:`sm`,variant:`secondary`,icon:(0,V.jsx)(u,{size:12,className:w.isPending?`animate-spin`:``}),disabled:w.isPending,onClick:()=>w.mutate({providerId:n,accountId:d},{onSuccess:()=>void g.refetch()}),children:w.isPending?`Recovering...`:`Recover Now`}):null]}),D?(0,V.jsxs)(`div`,{className:`flex items-center justify-between gap-6 rounded-control border border-inner-border bg-surface-2 py-5 px-7`,children:[(0,V.jsxs)(`div`,{children:[(0,V.jsx)(`div`,{className:`text-caption font-semibold`,children:`Credential management`}),(0,V.jsx)(`div`,{className:`text-badge text-text-tertiary`,children:O?`Static mode: no refresh request will be sent.`:`OAuth refresh is enabled for this account.`})]}),(0,V.jsx)(i,{size:`sm`,variant:O?`primary`:`secondary`,disabled:E.isPending,onClick:()=>j(!O),children:O?`Static · no refresh`:`Enable static mode`})]}):null,(0,V.jsxs)(`div`,{className:`min-w-0`,children:[(0,V.jsxs)(`h4`,{className:`text-caption font-semibold text-text-secondary mb-4`,children:[`Transition Events & Audit (`,T.length,`)`]}),g.isPending?(0,V.jsx)(s,{label:`Loading health events...`}):g.isError?(0,V.jsx)(o,{message:`Failed to load health events`,onRetry:()=>void g.refetch()}):T.length===0?(0,V.jsx)(c,{title:`No health events recorded`,message:e.emptyMessage,icon:(0,V.jsx)(r,{size:20})}):(0,V.jsx)(`div`,{className:`rounded-inset border border-inner-border`,children:(0,V.jsx)(C,{maxHeight:480,scrollRegion:!0,headers:[`Timestamp`,`From → To`,`Model`,`Category`,`Reason / Details`],children:T.map(e=>(0,V.jsxs)(`tr`,{children:[(0,V.jsx)(`td`,{className:`text-badge text-text-tertiary whitespace-nowrap`,children:new Date(e.createdAt).toLocaleString()}),(0,V.jsxs)(`td`,{className:`whitespace-nowrap`,children:[(0,V.jsxs)(`span`,{className:`text-badge text-text-secondary`,children:[e.fromStatus??`new`,` →`,` `]}),(0,V.jsx)(x,{tone:U(e.toStatus),children:e.toStatus})]}),(0,V.jsx)(`td`,{className:`font-mono tabular-nums text-badge text-text-secondary wrap-anywhere`,children:e.modelId??`—`}),(0,V.jsx)(`td`,{className:`font-mono tabular-nums text-badge text-text-secondary`,children:e.errorCategory??`—`}),(0,V.jsx)(`td`,{className:`text-badge text-text-primary min-w-110 max-w-260 wrap-anywhere`,children:e.reason??`—`})]},e.id))})})]}),(0,V.jsx)(v,{open:A!==null,onClose:()=>j(null),onConfirm:()=>{let e=A===!0;E.mutate({providerId:n,accountId:d,request:{staticToken:e}},{onSuccess:()=>{k(e),j(null),a.success(e?`Static mode enabled`:`Credential refresh re-enabled`,f),g.refetch()},onError:e=>a.error(`Credential update failed`,e instanceof Error?e.message:`Unable to update credential mode`)})},title:W(A===!0,f).title,message:W(A===!0,f).message,confirmLabel:W(A===!0,f).confirmLabel,cancelLabel:`Cancel`}),(0,V.jsx)(S,{justify:`flex-end`,gap:`0px`,children:(0,V.jsx)(i,{variant:`secondary`,size:`sm`,onClick:t,children:`Close`})})]})})}export{j as a,O as c,F as d,P as f,R as i,A as l,N as n,T as o,k as p,L as r,E as s,G as t,z as u};
@@ -1 +0,0 @@
1
- import{o as e,r as t,t as n}from"./jsx-runtime-CXUMngI_.js";import{o as r}from"./app-BDJWMThu.js";import{t as i}from"./check-D69FzdvC.js";import{n as a}from"./toast-DKAdP2mo.js";import{t as o}from"./dialog-iocKA3jr.js";import{t as s}from"./route-CNSNj9Zm.js";import{t as c}from"./search-JOKus9rb.js";import{S as l,_ as u}from"./assert-Bwh6DJTK.js";import{a as d,t as f}from"./query-keys-ROf-g1Wi.js";import{s as p}from"./tokens-DDxMbSRL.js";import{t as m}from"./badge-BwmwEOgA.js";import{t as h}from"./ProviderIcon-paOCq8I0.js";var g=e(t(),1),_=n();function v(e){let t=l({queryKey:f.providers.flatAll,queryFn:e=>d(`/providers/models/flat`,{signal:u(e)}),enabled:e,staleTime:0});return{items:t.data??[],isLoading:t.isPending,isError:t.isError,refetch:()=>void t.refetch()}}function y(e){return e==null?`?`:e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(0)}k`:String(e)}function b(e){let t=e.toLowerCase();return t===`responses`?`Responses`:t===`messages`?`Messages`:`Chat`}function x(e){let t=e.toLowerCase();return t===`responses`?`var(--purple)`:t===`messages`?`var(--orange)`:`var(--teal)`}function S(e){return e===`systemone`?`System One decision model — not a chat model. Call it at POST /v1/systemone with {state, questions}; it answers {answers}. It cannot run in a chat playground.`:e===`websearch`?`Web-search model — not a chat model. Call it at POST /v1/search with {query, max_results}; it answers a normalized list of hits. It cannot run in a chat playground.`:`This model is served by a native '${e}' route, not the chat wire.`}function C({open:e,onClose:t,selected:n,onToggle:l,onSelectOne:u,size:d=`lg`,title:f=`Select model`,multi:C=!0,filter:w}){let[T,E]=(0,g.useState)(``),{items:D,isLoading:O,isError:k,refetch:A}=v(e),j=(0,g.useMemo)(()=>{let e=T.trim().toLowerCase();return D.filter(t=>w&&!w(t)?!1:!e||t.qualified.toLowerCase().includes(e)||t.modelId.toLowerCase().includes(e)||t.providerId.toLowerCase().includes(e))},[D,T,w]),M=(0,g.useMemo)(()=>{let e=new Map;for(let t of j){let n=t.kind===`model`?t.providerId:t.kind===`alias`?`Aliases`:`Combos`,r=e.get(n)??[];r.push(t),e.set(n,r)}let t=(e,t)=>{let n=t[0]?.kind;return n===`alias`?0:n===`combo`?1:2};return Array.from(e.entries()).sort(([e,n],[r,i])=>{let a=t(e,n),o=t(r,i);return a===o?e.localeCompare(r):a-o})},[j]),N=e=>n.includes(e),P=e=>{u&&!C?(u(e),t()):l(e)};return(0,_.jsx)(o,{open:e,onClose:t,title:f,size:d,footer:(0,_.jsx)(a,{variant:`secondary`,onClick:t,children:`Done`}),children:(0,_.jsxs)(`div`,{className:`flex max-w-full flex-col gap-6`,children:[(0,_.jsxs)(`div`,{className:`relative`,children:[(0,_.jsx)(c,{size:14,className:`absolute left-5 top-1/2 -translate-y-1/2 text-text-tertiary`}),(0,_.jsx)(`input`,{value:T,onChange:e=>E(e.target.value),placeholder:`Search models or providers (e.g. gpt-5.4, codex)`,className:`w-full max-w-full rounded-inset border border-inner-border bg-surface-2 py-4 pr-5 pl-16 text-caption text-text-primary`})]}),C&&n.length>0&&(0,_.jsx)(`div`,{className:`flex flex-wrap gap-3 rounded-inset border border-inner-border bg-surface-2 p-4`,children:n.map(e=>(0,_.jsxs)(`span`,{className:`font-mono tabular-nums inline-flex items-center gap-2 text-badge bg-accent-soft text-accent py-1 px-3 rounded-chip border border-accent max-w-full overflow-hidden break-all`,children:[(0,_.jsx)(`span`,{className:`overflow-hidden truncate whitespace-nowrap`,children:e}),(0,_.jsx)(`button`,{type:`button`,onClick:()=>l(e),className:`bg-transparent border-none cursor-pointer p-0 flex shrink-0`,children:(0,_.jsx)(r,{size:12})})]},e))}),(0,_.jsx)(`div`,{className:`flex min-h-160 max-h-210 flex-1 flex-col gap-8 overflow-x-hidden overflow-y-auto rounded-control border border-inner-border bg-surface-1 p-6`,children:O?(0,_.jsx)(`div`,{className:`p-16 text-center text-caption text-text-tertiary`,children:`Loading catalog…`}):k?(0,_.jsxs)(`div`,{className:`p-16 text-center text-caption`,children:[(0,_.jsx)(`p`,{className:`text-text-tertiary`,children:`Failed to load catalog.`}),(0,_.jsx)(`button`,{type:`button`,onClick:A,className:`mt-4`,children:`Retry`})]}):M.length===0?(0,_.jsx)(`div`,{className:`p-16 text-center text-caption text-text-tertiary`,children:`No models found.`}):M.map(([e,t])=>{let n=t[0]?.kind===`model`;return(0,_.jsxs)(`div`,{className:`flex flex-col gap-4 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-badge font-bold tracking-[0.05em] text-text-secondary uppercase`,children:[n?(0,_.jsx)(h,{name:e,icon:e,size:16}):(0,_.jsx)(s,{size:14}),e,(0,_.jsx)(m,{tone:`default`,children:t.length})]}),(0,_.jsx)(`div`,{className:`grid min-w-0 grid-cols-2 gap-4 max-small:gap-2`,children:t.map(e=>{let t=N(e.qualified);return(0,_.jsxs)(`button`,{type:`button`,onClick:()=>P(e.qualified),className:`relative flex w-full min-w-0 cursor-pointer flex-col gap-2 overflow-hidden rounded-control px-6 py-5 text-left max-small:px-3 max-small:py-4 ${t?`border border-accent bg-accent-soft`:`border border-inner-border bg-surface-2`}`,children:[t&&(0,_.jsx)(i,{size:12,className:`absolute top-4 right-4 text-accent`}),(0,_.jsxs)(`span`,{title:e.qualified,className:`font-mono tabular-nums w-full whitespace-normal break-all text-badge font-semibold ${t?`pr-8 text-accent`:`text-text-primary`}`,children:[e.modelId,e.kind===`model`&&e.entry.serviceKind!==`llm`&&(0,_.jsx)(`span`,{title:S(e.entry.serviceKind),className:`ml-3 inline-block cursor-help rounded-tiny border border-orange px-2.5 align-middle font-bold tracking-[0.04em] text-micro text-orange uppercase`,children:e.entry.serviceKind})]}),(0,_.jsx)(`span`,{title:e.qualified,className:`font-mono tabular-nums w-full truncate text-micro text-text-tertiary`,children:e.kind===`alias`||e.kind===`combo`?(0,_.jsx)(`span`,{title:e.providerLabel,children:e.providerLabel}):(0,_.jsxs)(_.Fragment,{children:[e.entry.contextLimit?`${y(e.entry.contextLimit)} ctx`:(0,_.jsx)(`span`,{title:p,className:`cursor-help`,children:`n/a ctx`}),` · `,e.entry.outputLimit?`${y(e.entry.outputLimit)} out`:(0,_.jsx)(`span`,{title:p,className:`cursor-help`,children:`n/a out`}),` · `,(0,_.jsx)(`span`,{style:{color:x(e.entry.wireFamily),fontWeight:700},children:b(e.entry.wireFamily)})]})})]},e.qualified)})})]},e)})})]})})}export{v as n,C as t};