cartethyia 2.5.4 → 2.5.6

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 (395) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-mUfcknqm.js} +1 -1
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-uPiGVTTK.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  60. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  61. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  62. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
  64. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  66. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  68. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-js6TJvdr.js → index-CmxZr14F.js} +2 -2
  72. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
  74. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
  76. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  77. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  78. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
  80. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
  82. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
  86. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  87. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  88. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
  92. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-WUCXorXw.js → select-BYR_5K2c.js} +1 -1
  94. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
  96. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
  98. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
  100. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
  102. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  103. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  104. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  105. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  106. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  107. package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
  108. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  109. package/dist/dashboard/index.html +1 -1
  110. package/dist/dashboard/index.html.gz +0 -0
  111. package/dist/dashboard/precompressed-assets.json +54 -54
  112. package/migrations/README.md +52 -52
  113. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  114. package/package.json +96 -95
  115. package/scripts/cli/admin.ts +125 -0
  116. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  117. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
  118. package/scripts/cli/doctor.ts +37 -0
  119. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  120. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  121. package/scripts/cli/instance.ts +224 -0
  122. package/scripts/cli/main.ts +61 -18
  123. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  124. package/scripts/cli/port.ts +100 -0
  125. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  126. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  127. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  128. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  129. package/scripts/cli/tray/index.ts +62 -12
  130. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  131. package/scripts/cli/{command/update.ts → update.ts} +39 -39
  132. package/scripts/cli/version.ts +67 -0
  133. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  134. package/src/boot/app.ts +697 -693
  135. package/src/boot/dependencies.ts +647 -646
  136. package/src/boot/timeout.ts +2 -13
  137. package/src/config/cooldowns.ts +186 -138
  138. package/src/config/network.ts +8 -14
  139. package/src/config/providers.ts +7 -10
  140. package/src/config/runtime.ts +76 -76
  141. package/src/config/security.ts +15 -0
  142. package/src/config/spec.ts +20 -30
  143. package/src/config/telemetry.ts +0 -20
  144. package/src/config/timeouts.ts +0 -6
  145. package/src/console/admin/model-abuse/contracts.ts +28 -6
  146. package/src/console/admin/model-abuse/routes.ts +56 -3
  147. package/src/console/api-keys/contracts.ts +410 -414
  148. package/src/console/api-keys/routes.ts +881 -880
  149. package/src/console/auth/credentials.ts +3 -3
  150. package/src/console/auth/setup-service.ts +2 -2
  151. package/src/console/backup/export-endpoint.ts +6 -2
  152. package/src/console/backup/nine-router.ts +2 -5
  153. package/src/console/backup/service.ts +4 -0
  154. package/src/console/backup/store.ts +192 -29
  155. package/src/console/backup/validate.ts +3 -45
  156. package/src/console/cli-tools/fs-ops.ts +487 -487
  157. package/src/console/console-router-guards.ts +1 -1
  158. package/src/console/data-management/contracts.ts +13 -6
  159. package/src/console/data-management/operations.ts +23 -18
  160. package/src/console/data-management/routes.ts +0 -1
  161. package/src/console/domain-registration.ts +10 -5
  162. package/src/console/observability/live.ts +22 -0
  163. package/src/console/observability/store.ts +39 -16
  164. package/src/console/observability/usage-dimensions.ts +4 -4
  165. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  166. package/src/console/providers/catalog/model-operations.ts +5 -2
  167. package/src/console/providers/catalog/provider-operations.ts +4 -4
  168. package/src/console/providers/catalog/public-model-store.ts +8 -11
  169. package/src/console/providers/catalog/store.ts +1553 -1553
  170. package/src/console/providers/oauth/routes.ts +8 -12
  171. package/src/console/quota/account-quota-tenant.ts +6 -6
  172. package/src/console/quota/quota-view.ts +1 -1
  173. package/src/console/remote-support/contracts.ts +61 -0
  174. package/src/console/remote-support/routes.ts +65 -0
  175. package/src/console/remote-support/service.ts +207 -0
  176. package/src/console/response-compression.ts +0 -3
  177. package/src/console/routing/pools/probe-result.ts +3 -2
  178. package/src/console/routing/pools/probe-verdict.ts +132 -132
  179. package/src/console/routing/pools/store.ts +8 -8
  180. package/src/console/settings/contracts.ts +58 -2
  181. package/src/console/settings/store.ts +30 -3
  182. package/src/console/shared/cursor.ts +8 -8
  183. package/src/console/shared/errors.ts +1 -3
  184. package/src/network/http2-bidi.ts +2 -4
  185. package/src/network/pool/agent.ts +478 -478
  186. package/src/network/pool/selector.ts +5 -4
  187. package/src/network/retry.ts +1 -2
  188. package/src/network/ssrf.ts +5 -4
  189. package/src/observability/event-compaction.ts +80 -78
  190. package/src/observability/event-day-index.ts +13 -15
  191. package/src/observability/event-log.ts +4 -4
  192. package/src/observability/logger.ts +6 -6
  193. package/src/observability/payload-capture.ts +151 -33
  194. package/src/observability/payload-store.ts +397 -440
  195. package/src/observability/redaction.ts +5 -6
  196. package/src/observability/request-notify.ts +43 -0
  197. package/src/observability/telemetry-status.ts +8 -9
  198. package/src/observability/token-speed.ts +5 -4
  199. package/src/persistence/db-mode.ts +7 -7
  200. package/src/persistence/readiness.ts +1 -1
  201. package/src/persistence/schema.ts +1406 -1402
  202. package/src/persistence/shared-cache.ts +1 -2
  203. package/src/persistence/stores/api-key-store.ts +18 -6
  204. package/src/persistence/stores/share-store.ts +4 -3
  205. package/src/persistence/stores/telemetry-store.ts +236 -237
  206. package/src/protocol/canonical-model.ts +11 -3
  207. package/src/protocol/codecs.ts +138 -4
  208. package/src/protocol/gateway-error.ts +6 -4
  209. package/src/protocol/request/chat.ts +21 -1
  210. package/src/protocol/request/codex.ts +10 -1
  211. package/src/protocol/request/messages.ts +4 -5
  212. package/src/protocol/request/responses.ts +9 -1
  213. package/src/protocol/response/chat.ts +34 -4
  214. package/src/protocol/response/codex.ts +7 -4
  215. package/src/protocol/response/dsml.ts +140 -28
  216. package/src/protocol/response/responses.ts +11 -5
  217. package/src/protocol/stream-error-frames.ts +4 -5
  218. package/src/providers/authentication/jwt-validator.ts +9 -10
  219. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  220. package/src/providers/configured-openai-providers.ts +134 -136
  221. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  222. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  223. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  224. package/src/providers/discovery/probe-phases.ts +4 -4
  225. package/src/providers/discovery/probe-wire.ts +6 -13
  226. package/src/providers/discovery/probing-service.ts +1117 -1045
  227. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  228. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  231. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  232. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  233. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  234. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  235. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  236. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  237. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  238. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  239. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  240. package/src/providers/integrations/claude/claude.ts +608 -608
  241. package/src/providers/integrations/cline/cline-quota.ts +132 -132
  242. package/src/providers/integrations/cline/cline.ts +537 -539
  243. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  244. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  245. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  246. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  247. package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
  248. package/src/providers/integrations/cursor/cursor.ts +11 -163
  249. package/src/providers/integrations/factory/factory-models.ts +3 -5
  250. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  251. package/src/providers/integrations/grok/grok.ts +6 -7
  252. package/src/providers/integrations/hermes.ts +14 -18
  253. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  254. package/src/providers/integrations/kiro/kiro.ts +1 -1
  255. package/src/providers/integrations/mistral.ts +6 -6
  256. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  257. package/src/providers/integrations/opencode/provider.ts +8 -8
  258. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  259. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  260. package/src/providers/integrations/qoder/qoder.ts +884 -865
  261. package/src/providers/integrations/shared/connect.ts +6 -6
  262. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  263. package/src/providers/integrations/trae/trae.ts +2 -2
  264. package/src/providers/integrations/zcode/oauth.ts +6 -1
  265. package/src/providers/operations/account-health-service.ts +1528 -1523
  266. package/src/providers/operations/account-reset-service.ts +1 -1
  267. package/src/providers/operations/client-versions.ts +6 -6
  268. package/src/providers/operations/custom-cli-headers.ts +3 -4
  269. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  270. package/src/providers/operations/provider-catalog-service.ts +3 -4
  271. package/src/providers/provider-metadata.ts +4 -8
  272. package/src/providers/provider-registry.ts +6 -7
  273. package/src/providers/provider-usage.ts +1 -1
  274. package/src/security/abuse.ts +15 -10
  275. package/src/security/access-control.ts +144 -146
  276. package/src/security/admission/contracts.ts +11 -0
  277. package/src/security/admission/in-memory-store.ts +4 -3
  278. package/src/security/admission/lease-sweep.ts +6 -6
  279. package/src/security/admission/reasons.ts +5 -1
  280. package/src/security/admission/redis-store.ts +211 -210
  281. package/src/security/admission/service.ts +4 -0
  282. package/src/security/api-key-auth.ts +504 -504
  283. package/src/security/ip-boundary.ts +155 -15
  284. package/src/security/model-abuse.ts +366 -94
  285. package/src/security/outbound-headers.ts +3 -4
  286. package/src/shared/sleep.ts +4 -0
  287. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  288. package/src/transport/dispatch/attempt-loop.ts +531 -530
  289. package/src/transport/dispatch/stream-bridge.ts +1 -3
  290. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  291. package/src/transport/dispatch/streaming/stream.ts +5 -6
  292. package/src/transport/failure-policy.ts +162 -8
  293. package/src/transport/guards/auth-guard.ts +7 -4
  294. package/src/transport/guards/concurrency-guard.ts +0 -5
  295. package/src/transport/guards/error-lifecycle.ts +13 -6
  296. package/src/transport/guards/pipeline.ts +15 -3
  297. package/src/transport/guards/request-context.ts +8 -2
  298. package/src/transport/request/inflight.ts +5 -6
  299. package/src/transport/request/preparer.ts +2 -2
  300. package/src/transport/request/state.ts +12 -8
  301. package/src/transport/routing/admission.ts +4 -0
  302. package/src/transport/routing/route-catalog.ts +8 -1
  303. package/src/transport/routing/route-model.ts +46 -14
  304. package/src/transport/routing/router.ts +21 -14
  305. package/src/transport/surface/completion.ts +2 -3
  306. package/src/transport/surface/dialects.ts +5 -6
  307. package/src/transport/surface/responses/encode.ts +38 -0
  308. package/src/transport/surface/responses/parse.ts +4 -0
  309. package/src/transport/translation/capabilities.ts +2 -3
  310. package/src/transport/translation/quirks.ts +3 -3
  311. package/src/workers/buddy-growth-tasks.ts +5 -10
  312. package/src/workers/daily-checkin.ts +17 -23
  313. package/src/workers/quota-refresh-sweep.ts +26 -26
  314. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  315. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
  316. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  317. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  318. package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
  319. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  320. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  321. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  322. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  323. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  324. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
  325. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  326. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
  327. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  328. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
  329. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  330. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  331. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
  332. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  333. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
  334. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  335. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
  336. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  337. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  338. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  339. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  340. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
  341. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  342. package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
  343. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  344. package/dist/dashboard/assets/app-COlK95oi.css +0 -2
  345. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  346. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  347. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  348. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  349. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  350. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  351. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  352. package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
  353. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  354. package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
  355. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  356. package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
  357. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  358. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
  360. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  361. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  363. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  365. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
  367. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  368. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  369. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  370. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  371. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  372. package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
  373. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  374. package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
  375. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  376. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  377. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  378. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  379. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  380. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  381. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  382. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  383. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  384. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  385. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  386. package/scripts/cli/command/admin.ts +0 -65
  387. package/scripts/cli/command/doctor.ts +0 -257
  388. package/scripts/cli/gateway/drain-token.ts +0 -78
  389. package/scripts/cli/gateway/instance-lock.ts +0 -105
  390. package/scripts/cli/gateway/stop.ts +0 -51
  391. package/scripts/cli/support/bun.ts +0 -27
  392. package/scripts/cli/support/version.ts +0 -37
  393. /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
  394. /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
  395. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -226,10 +226,10 @@ function spreadDeadline(cooldownUntil: number | undefined): { cooldown_until?: n
226
226
  * The soonest moment a quota window the caller is blocked on refills.
227
227
  *
228
228
  * A usage-floor exclusion is time-bounded by construction — the floor compares
229
- * the *remaining* allowance, and the window states when that allowance resets —
230
- * but the exclusion used to carry no deadline, so `plan()` could not tell a
231
- * client how long to wait and `earliestResetMs` reported nothing. The minimum
232
- * across windows, for the same reason `earliestResetMs` takes it: the account
229
+ * the *remaining* allowance, and the window states when that allowance resets.
230
+ * The exclusion carries a deadline so `plan()` can tell a client how long to
231
+ * wait and `earliestResetMs` reports a value. The minimum across windows, for
232
+ * the same reason `earliestResetMs` takes it: the account
233
233
  * can serve again as soon as its first window refills, so quoting the slowest
234
234
  * would hold the client off longer than necessary.
235
235
  *
@@ -317,9 +317,8 @@ export class EligibilityEvaluator {
317
317
  return { eligible: false, reason: "model_cooldown", candidate, ...spreadDeadline(state.cooldown_until) };
318
318
  if (state.health_status === "cooldown" && state.cooldown_kind === "hard")
319
319
  return { eligible: false, reason: "cooldown_hard", candidate, ...spreadDeadline(state.cooldown_until) };
320
- // The floor is checked here, not after selection: the old minimum-balance
321
- // feature computed the number and never consulted it, so an account below
322
- // its floor still won the route. An account with no reading is left
320
+ // The floor is checked here, not after selection: an account below its
321
+ // floor must not win the route. An account with no reading is left
323
322
  // eligible — a missing quota fetch is not proof the allowance is gone.
324
323
  if (belowUsageFloor(state))
325
324
  return {
@@ -487,8 +486,8 @@ export class RoutingEngine {
487
486
  * Stamps each account with its provider's usage floor and the quota reading
488
487
  * the console last fetched, so eligibility can reject an account that has
489
488
  * spent down to the floor. The reading is the live cache, not the route
490
- * snapshot: a snapshot rebuilt every few minutes would route on a stale
491
- * balance, which is how the old floor let a spent account through.
489
+ * snapshot: a snapshot rebuilt every few minutes routes on a stale balance
490
+ * and lets a spent account through.
492
491
  */
493
492
  private async applyUsageFloor(
494
493
  candidates: readonly RouteCandidate[],
@@ -598,10 +597,10 @@ export class RoutingEngine {
598
597
  d.reason === "model_cooldown" ||
599
598
  // A usage-floor exclusion whose window states a reset is the same
600
599
  // shape as the two above: the account is resting until a named
601
- // instant, not broken. It used to fall through to 503, which told
602
- // the client the model was down while every account serving it was
603
- // merely waiting for its window to refill — the "503 DOWN while the
604
- // model is allowed" report again. Only a floor with *no* stated
600
+ // instant, not broken. Falling through to 503 tells the client the
601
+ // model is down while every account serving it is merely waiting for
602
+ // its window to refill — the "503 DOWN while the model is allowed"
603
+ // report again. Only a floor with *no* stated
605
604
  // reset stays unavailable: there is nothing to retry against yet.
606
605
  (d.reason === "quota_exhausted" && typeof d.cooldown_until === "number"),
607
606
  );
@@ -828,7 +827,15 @@ export class RoutingEngine {
828
827
  const admitted = await this.admission.admit(candidate);
829
828
  if (admitted.admitted) return this.reservations.acquire(candidate, plan.revision);
830
829
  }
831
- throw capacityExhaustedError();
830
+ // Last candidate's identity names the provider the plan was actually for;
831
+ // every candidate shares it, so any one of them is representative.
832
+ const last = plan.candidates[plan.candidates.length - 1];
833
+ throw capacityExhaustedError({
834
+ ...(last?.provider_id !== undefined ? { providerId: last.provider_id } : {}),
835
+ ...(plan.resolved_model !== undefined ? { model: plan.resolved_model } : {}),
836
+ candidateCount: plan.candidates.length,
837
+ ...(last?.max_inflight !== undefined ? { maxInflight: last.max_inflight } : {}),
838
+ });
832
839
  }
833
840
  async release(reservation: Reservation): Promise<void> {
834
841
  await this.admission.release(reservation);
@@ -45,9 +45,8 @@ function stopReason(events: readonly CanonicalEvent[]): string | null {
45
45
  }
46
46
 
47
47
  /**
48
- * Completion wire usage. Delegates to the chat builder (same wire family) —
49
- * completion responses now carry cache/reasoning details instead of dropping
50
- * them (intended fix; previously only the three totals were forwarded).
48
+ * Completion wire usage. Delegates to the chat builder (same wire family), so
49
+ * completion responses carry cache/reasoning details, not just the three totals.
51
50
  */
52
51
  function usage(events: readonly CanonicalEvent[]): OpenAiUsage | undefined {
53
52
  const terminal = [...events].reverse().find((event) => event.type === "terminal");
@@ -79,12 +79,11 @@ function parseToolObject(item: Record<string, unknown>, dialect: ToolDialect): T
79
79
  }
80
80
  } else if (dialect === "responses") {
81
81
  // A Responses caller may still send the Chat-shaped envelope — the two
82
- // spellings are interchangeable in the wild, and a client that posts
83
- // `{"type":"function","function":{"parameters":…}}` here previously fell
84
- // through to `schema = item`, which shipped the whole tool object upstream
85
- // as the parameter schema and came back as
86
- // `invalid_function_parameters` (CodeBuddy 11129). Unwrap it the way the
87
- // Chat dialect does before falling back to the flat Responses spelling.
82
+ // spellings are interchangeable in the wild. Unwrap it the way the Chat
83
+ // dialect does before falling back to the flat Responses spelling; taking
84
+ // `schema = item` instead ships the whole tool object upstream as the
85
+ // parameter schema and comes back as `invalid_function_parameters`
86
+ // (CodeBuddy 11129).
88
87
  const wrapped = isRecord(item["function"]) ? item["function"] : undefined;
89
88
  schema =
90
89
  item["input_schema"] ??
@@ -704,6 +704,44 @@ export class ResponsesEventEncoder extends SurfaceStreamEncoder<CanonicalEvent,
704
704
  throw new ResponsesLifecycleError("Responses stream ended without a terminal event");
705
705
  return [];
706
706
  }
707
+
708
+ /**
709
+ * Encodes a fatal stream error as `response.error` followed immediately by
710
+ * `response.failed`.
711
+ *
712
+ * The OpenAI Responses SDK (and any strict client) requires a terminal
713
+ * lifecycle event (`response.completed`, `response.failed`, or
714
+ * `response.incomplete`) to close the stream cleanly. Emitting only
715
+ * `response.error` and then closing the SSE connection causes the SDK to
716
+ * throw "stream closed before a terminal response event was received".
717
+ *
718
+ * `response.error` is kept so the client can surface the error message;
719
+ * `response.failed` immediately follows so the client's state machine
720
+ * reaches a terminal state and stops waiting.
721
+ */
722
+ encodeError(input: {
723
+ readonly origin: string;
724
+ readonly code: string;
725
+ readonly message: string;
726
+ readonly details: unknown;
727
+ }): ResponsesWireEvent[] {
728
+ if (this.terminal) return [];
729
+ this.terminal = true;
730
+ const errorEvent = this.emit("response.error", {
731
+ origin: input.origin,
732
+ code: input.code,
733
+ message: input.message,
734
+ details: input.details,
735
+ });
736
+ const response = this.responseObject("failed");
737
+ response["error"] = {
738
+ type: "server_error",
739
+ code: input.code,
740
+ message: input.message,
741
+ };
742
+ const failedEvent = this.emit("response.failed", { response });
743
+ return [errorEvent, failedEvent];
744
+ }
707
745
  }
708
746
 
709
747
  /** Encode synchronous canonical events into ordered Responses wire events. */
@@ -456,6 +456,10 @@ function parseInputItem(item: ResponsesInputItem): {
456
456
  encrypted_content: encrypted,
457
457
  };
458
458
  if (summary !== undefined) reasoningPart.summary = summary;
459
+ // The upstream binds encrypted reasoning to the item id it was issued
460
+ // for; a replay under a different id is rejected outright.
461
+ const itemId = typeof item["id"] === "string" ? item["id"] : undefined;
462
+ if (itemId !== undefined) reasoningPart.item_id = itemId;
459
463
  return {
460
464
  message: { role: "assistant", content: [reasoningPart] },
461
465
  metadata: itemMetadata(item),
@@ -334,9 +334,8 @@ export function deriveRequiredCapabilities(
334
334
  for (const key of Object.keys(request.generation_controls)) {
335
335
  // `extension:*` keys are passthrough hints to the wire codec, not route
336
336
  // capabilities: the codec reads the names it understands and drops the
337
- // rest. Gating them here used to strip every hint (this set was never
338
- // populated), degrading `include_usage`, `prompt_cache_key`, `user`,
339
- // `metadata`, etc. and re-warning per request.
337
+ // rest. Gating them here would strip `include_usage`, `prompt_cache_key`,
338
+ // `user`, `metadata`, etc. and re-warn per request.
340
339
  if (key.startsWith("extension:")) continue;
341
340
  capabilities.push(`generation_control:${key}`);
342
341
  }
@@ -180,9 +180,9 @@ export function applyParamQuirks(
180
180
  const quirk = findParamQuirk(providerId, request.model);
181
181
  // The thinking-sampling strip is universal, not quirk-gated: extended
182
182
  // thinking rejects temperature/top_p/top_k on every Messages-wire upstream.
183
- // Previously the early return below skipped it for any provider without a
184
- // PARAM_QUIRKS row (only anthropic/claude had one), so strict upstreams got
185
- // a 400 the strip exists to prevent. Declarative rows still apply after.
183
+ // It runs before the early return below so a provider with no PARAM_QUIRKS
184
+ // row still gets it — otherwise a strict upstream takes a 400 the strip
185
+ // exists to prevent. Declarative rows still apply after.
186
186
 
187
187
  let controls = request.generation_controls;
188
188
  let changed = false;
@@ -22,6 +22,7 @@ import { randomUUID } from "node:crypto";
22
22
  import type { CartethyiaDatabase } from "../persistence/db-handle";
23
23
  import { buddyTaskLedger, providerAccounts } from "../persistence/schema";
24
24
  import { log } from "../observability/logger";
25
+ import { sleep } from "../shared/sleep";
25
26
  import { buddyAccountUid } from "../providers/integrations/buddy/buddy-oauth-shared";
26
27
  import {
27
28
  adoptFirstBuddy,
@@ -207,12 +208,6 @@ async function forAccount(
207
208
  return run(call);
208
209
  }
209
210
 
210
- /** Pause between accounts, so one pass does not look like a burst upstream. */
211
- function sleep(ms: number): Promise<void> {
212
- const { promise, resolve } = Promise.withResolvers<void>();
213
- setTimeout(resolve, ms);
214
- return promise;
215
- }
216
211
  /** Walks the accounts, skipping any already settled today. */
217
212
  async function runTask(
218
213
  deps: BuddyTaskDeps,
@@ -353,10 +348,10 @@ export function buddyTrialTask(deps: BuddyTaskDeps) {
353
348
  * Growth-activity report: the `/v2/report` batch that lights the login streak and
354
349
  * scores the daily growth event.
355
350
  *
356
- * It used to ride the quota sweep with no ledger of its own, which meant eight
357
- * POSTs per account *per sweep* (two rounds of four events) because the sweep
358
- * ran every minute. It is its own once-a-day task here, gated by the same
359
- * (day, account, task) ledger as every other growth task.
351
+ * It is a once-a-day task with its own ledger, gated by the same
352
+ * (day, account, task) ledger as every other growth task: running it on the
353
+ * quota sweep would post two rounds of four events per account per sweep, and
354
+ * the sweep runs every minute.
360
355
  *
361
356
  * The report is not a credit claim, so its failure never marks an account
362
357
  * unhealthy — the result is logged and the ledger is stamped either way.
@@ -120,9 +120,8 @@ export function ledgerKey(dayKey: string, accountId: string): string {
120
120
  * With Redis the `SET NX EX` is the cross-process guard (two gateway instances
121
121
  * cannot both attempt the same account). Without it the ledger *table* is the
122
122
  * guard, and its composite PK makes the insert atomic — so Lite records the
123
- * attempt it just made instead of silently returning `true`, which is what left
124
- * the console unable to answer "attempted today" and showing "Check-in due"
125
- * forever.
123
+ * attempt it just made instead of silently returning `true`, which leaves the
124
+ * console unable to answer "attempted today" and shows "Check-in due" forever.
126
125
  */
127
126
  async function reserveDaySlot(
128
127
  db: CartethyiaDatabase | undefined,
@@ -160,8 +159,8 @@ async function reserveDaySlot(
160
159
  * Releases today's slot for many accounts in one round-trip.
161
160
  *
162
161
  * The sweep reserves a slot per account as it walks the wave; releasing them
163
- * one at a time on the error path turned every failed pass into a Redis RTT per
164
- * failed account. A pass that budgets out with twenty failures paid twenty
162
+ * one at a time on the error path turns every failed pass into a Redis RTT per
163
+ * failed account. A pass that budgets out with twenty failures pays twenty
165
164
  * round-trips for bookkeeping that has no bearing on the claim itself.
166
165
  *
167
166
  * A pipeline is used when Redis is present (one `MULTI/DEL` for the whole
@@ -246,8 +245,8 @@ export async function markCheckinAttempted(args: {
246
245
  * The read belongs next to the writes because there are two places the marker
247
246
  * can live — the Redis key when Redis is present, the `daily_checkin_ledger`
248
247
  * row when it is not — and only this module knows which one its own writes
249
- * used. A caller that read the Redis key directly answered "not attempted" on
250
- * every Lite deployment, where the sweep had written the row.
248
+ * used. A caller that reads the Redis key directly answers "not attempted" on
249
+ * every Lite deployment, where the sweep writes the row.
251
250
  *
252
251
  * Absent or unreadable means unknown, never "not claimed": the sweep may
253
252
  * simply not have reached the account yet.
@@ -355,9 +354,9 @@ export function supportsDailyCheckin(providerId: string): boolean {
355
354
  * Wraps a proxy-bound fetcher so a 403 falls back to a direct connection once.
356
355
  *
357
356
  * A proxy that answers 403 (auth, expired pool credential, a provider that
358
- * rejects the egress) used to fail the whole claim even though the account's
359
- * own credential was fine — the proxy was the only thing wrong. One direct
360
- * retry costs nothing when the proxy was healthy (the first attempt already
357
+ * rejects the egress) would fail the whole claim even though the account's own
358
+ * credential was fine — the proxy is the only thing wrong. One direct retry
359
+ * costs nothing when the proxy was healthy (the first attempt already
361
360
  * succeeded) and rescues the claim when it was not.
362
361
  *
363
362
  * The fallback only fires on 403, not on every error: a 401 means the account
@@ -375,15 +374,10 @@ export function withDirectFallback(fetcher: typeof fetch): typeof fetch {
375
374
  return wrapped;
376
375
  }
377
376
  /**
378
- * The outcome of one daily check-in pass: the claim alone.
379
- *
380
- * The growth-activity report is no longer part of this pass — it moved to its
381
- * own once-a-day growth task (`buddy-activity-report`) with its own ledger, so
382
- /**
383
- * Runs the full daily growth pass for one account, once per calendar day: the
384
- * check-in claim alone. The growth-activity report moved to its own once-a-day
385
- * growth task (`buddy-activity-report`) with its own ledger, so it is not
386
- * re-POSTed on every sweep. See `buddy-growth-tasks.ts`.
377
+ * The outcome of one daily check-in pass: the claim alone. The
378
+ * growth-activity report runs as its own once-a-day growth task
379
+ * (`buddy-activity-report`) with its own ledger, so it is not re-POSTed on
380
+ * every sweep. See `buddy-growth-tasks.ts`.
387
381
  */
388
382
  export type DailyGrowthPassResult = {
389
383
  readonly checkin: DailyCheckinResult;
@@ -448,10 +442,10 @@ export async function attemptDailyGrowthPass(args: {
448
442
 
449
443
  // Only a transient `error` releases the slot so a later pass today can retry
450
444
  // (network, 5xx). `unavailable` is the status probe answering that no
451
- // activity is running — retrying that every pass spent the account's daily
452
- // slot on a door the provider had closed, and releasing the marker was also
453
- // what erased the evidence that anything had been attempted: the console then
454
- // read "Check-in due" forever while the sweep retried once a minute.
445
+ // activity is running — retrying that every pass spends the account's daily
446
+ // slot on a door the provider has closed, and releasing the marker also
447
+ // erases the evidence that anything was attempted: the console then reads
448
+ // "Check-in due" forever while the sweep retries once a minute.
455
449
  // `already_claimed`, `not_eligible`, and `event_ended` are terminal for the
456
450
  // day and keep the slot.
457
451
  return { checkin };
@@ -91,11 +91,11 @@ const DEFAULT_MAX_CHECKINS_PER_PASS = 10;
91
91
  *
92
92
  * The provider filter is applied *before* the slot count is consulted, and
93
93
  * that ordering is the whole point of the function: the sweep's batch is
94
- * oldest-first across every provider, so a cap checked first was spent on
95
- * non-buddy accounts the ride-along would have skipped anyway. A buddy
96
- * account sitting behind them (a `cbcn` row past the tenth older non-buddy
97
- * row) then never got its automatic check-in — only a manual press, which
98
- * bypasses the cap, ever reached it.
94
+ * oldest-first across every provider, so a cap checked first is spent on
95
+ * non-buddy accounts the ride-along would skip anyway. A buddy account sitting
96
+ * behind them (a `cbcn` row past the tenth older non-buddy row) then never gets
97
+ * its automatic check-in — only a manual press, which bypasses the cap, ever
98
+ * reaches it.
99
99
  */
100
100
  export function checkinSlotAvailable(
101
101
  providerId: string,
@@ -114,7 +114,7 @@ export function checkinSlotAvailable(
114
114
  *
115
115
  * Module scope, not per-call: suppression must survive across passes, and one
116
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;
117
+ * id and is never pruned, so an account deleted years ago still holds an entry;
118
118
  * the oldest keys are the ones least likely to be announced again.
119
119
  */
120
120
  const LAST_SWEEP_LINE_MAX = 512;
@@ -159,7 +159,7 @@ function quotaPhrase(outcome: QuotaRefreshOutcome): string {
159
159
  * nothing to report.
160
160
  *
161
161
  * Only providers that expose the check-in route appear here at all: the sweep
162
- * skips the rest, and `daily unsupported` on every non-WorkBuddy account was
162
+ * skips the rest, and `daily unsupported` on every non-WorkBuddy account is
163
163
  * pure noise. The routine "nothing to claim today" states collapse to one
164
164
  * short phrase so the line stays scannable; only a real grant or a failure
165
165
  * carries detail.
@@ -238,11 +238,11 @@ function logSweepAccounts(
238
238
  /**
239
239
  * How stale a cached quota reading must be before it is refreshed again.
240
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.
241
+ * Half the cache TTL. The sweep runs every 60 s, so a 4 min threshold refreshes
242
+ * every account roughly three times per 15 min TTL window — two of those three
243
+ * passes hit the provider and learn nothing the cache did not already know.
244
+ * 8 min is still comfortably inside the TTL, so a reading never goes stale
245
+ * while it waits.
246
246
  */
247
247
  const DEFAULT_MIN_AGE_MS = 8 * 60_000;
248
248
  const DEFAULT_MAX_PER_PASS = 40;
@@ -296,7 +296,7 @@ export async function quotaRefreshSweep(deps: QuotaRefreshSweepDeps): Promise<vo
296
296
  ? await deps.listTargets()
297
297
  : await listQuotaRefreshTargets(deps.db);
298
298
  // Read cache ages in one batched pass per lens: the sweep runs over
299
- // dozens of accounts each minute, so a `get` per account made the age
299
+ // dozens of accounts each minute, so a `get` per account makes the age
300
300
  // scan's latency scale with the account count. `getCachedQuotaEntries`
301
301
  // is the cache's own batched reader, so the key format stays owned by
302
302
  // the cache module.
@@ -406,10 +406,10 @@ export async function quotaRefreshSweep(deps: QuotaRefreshSweepDeps): Promise<vo
406
406
  // The check-in ride-along still runs: the daily credit grant is the
407
407
  // *remedy* for an account that just reported an exhausted or unreadable
408
408
  // quota, and it is a different upstream call that does not depend on the
409
- // quota leg. Returning here meant an account sitting at 0 credits — the
410
- // one that most needs the grant — never had its check-in attempted, so
411
- // the Quota page read "Check-in due" while the sweep believed it had
412
- // done its job. Only a credential we could not resolve at all rules the
409
+ // quota leg. Returning here leaves an account sitting at 0 credits — the
410
+ // one that most needs the grant — with no check-in attempted, so the
411
+ // Quota page reads "Check-in due" while the sweep believes it has done
412
+ // its job. Only a credential we could not resolve at all rules the
413
413
  // check-in out; that is the one input both legs share.
414
414
  if (outcome.failure === "missing_credential") return;
415
415
  }
@@ -420,16 +420,16 @@ export async function quotaRefreshSweep(deps: QuotaRefreshSweepDeps): Promise<vo
420
420
  // pass after the first of the day.
421
421
  //
422
422
  // The provider filter comes *before* the slot is consumed. The batch is
423
- // oldest-first across every provider, so checking the cap first spent the
423
+ // oldest-first across every provider, so checking the cap first spends the
424
424
  // whole per-pass budget on non-buddy accounts that the next line would
425
- // have skipped anyway — a buddy account past the cap (a cbcn row behind
426
- // ten older non-buddy rows) was never checked in automatically, only when
427
- // an operator pressed the button, which bypasses this cap entirely.
425
+ // skip anyway — a buddy account past the cap (a cbcn row behind ten older
426
+ // non-buddy rows) is never checked in automatically, only when an operator
427
+ // presses the button, which bypasses this cap entirely.
428
428
  if (!checkinSlotAvailable(entry.target.providerId, checkins, maxCheckins)) return;
429
429
  // Claim the slot before the first await. The wave runs its items
430
430
  // concurrently, and reading `checkins` only after the growth pass
431
- // resolved let every item in the wave pass the cap check against the
432
- // same stale count — the cap then bounded nothing.
431
+ // resolves lets every item in the wave pass the cap check against the
432
+ // same stale count — the cap then bounds nothing.
433
433
  checkins += 1;
434
434
  try {
435
435
  // is only for the `preconnect` static Bun adds to its own fetch (the
@@ -489,9 +489,9 @@ export async function quotaRefreshSweep(deps: QuotaRefreshSweepDeps): Promise<vo
489
489
  logSweepAccounts(attemptedTargets, quotaOutcomes, checkinOutcomes, failed, claimed);
490
490
 
491
491
  // Release the slots for the accounts whose check-in errored, in one
492
- // round-trip. The pass used to release inline per account, so a pass that
493
- // budgeted out with twenty failures paid twenty Redis round-trips for
494
- // bookkeeping that has no bearing on the claim itself.
492
+ // round-trip. Releasing inline per account makes a pass that budgets out with
493
+ // twenty failures pay twenty Redis round-trips for bookkeeping that has no
494
+ // bearing on the claim itself.
495
495
  const releasedAccountIds: string[] = [];
496
496
  for (const outcome of checkinOutcomes.values()) {
497
497
  if (outcome.state === "error") releasedAccountIds.push(outcome.accountId);
@@ -1 +0,0 @@
1
- import{n as e,o as t,r as n,t as r}from"./jsx-runtime-CXUMngI_.js";import{t as i}from"./arrow-left-CpBxkoxI.js";import{c as a,s as o}from"./numbers-YNcHvRY7.js";import{t as ee}from"./circle-check-BU3v_mFW.js";import{n as s,r as c}from"./state-BgTxHcJS.js";import{t as l}from"./route-CNSNj9Zm.js";import{t as u}from"./search-JOKus9rb.js";import{l as d}from"./query-keys-gmjzC3RS.js";import{Q as f,et as p,lt as m}from"./entry-CgdSAzEV.js";import{n as h,r as g,t as _}from"./card-BKqlUOe4.js";import{n as te}from"./download-dRFo5_Wu.js";import{t as v}from"./badge-D4pQapKj.js";import{n as ne}from"./input-LVjgZq4f.js";import{t as re}from"./select-WUCXorXw.js";import{t as ie}from"./ModelPicker-BY1aKEkP.js";import{t as y}from"./switch-DEjTtOWf.js";import{t as b}from"./stack-_3O0F8Xn.js";import{n as x}from"./inline-DUWDSmDV.js";import{f as ae,t as S}from"./api-keys-VY68Mhd1.js";import{a as C,i as oe,n as se,o as w,r as T,t as ce}from"./cli-tools-fTgljTGA.js";import{t as le}from"./ToolIcon-K1ckcDKg.js";var ue=e(`book-open`,[[`path`,{d:`M12 5v16`,key:`1f6ucr`}],[`path`,{d:`M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z`,key:`1fyvmf`}]]),E=e(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),D=e(`file-code-corner`,[[`path`,{d:`M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35`,key:`1wthlu`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`m5 16-3 3 3 3`,key:`331omg`}],[`path`,{d:`m9 22 3-3-3-3`,key:`lsp7cz`}]]),O=e(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),k=t(n(),1);function A(e,t){return e==null?!1:e instanceof Set?e.has(t):e.includes(t)}function j(e){return e==null?0:e instanceof Set?e.size:e.length}function M(e){let t=e.lastIndexOf(`/`);return t<0?e:e.slice(t+1)}function N(e,t,n,r){let i=n?`${n}/${M(t)}`:void 0,a=[t,M(t),...i?[i]:[]];r&&r!==t&&a.push(r);let o=e.model_list;return e.model_access_mode===`blacklist`?a.some(e=>A(o,e))?`model-denied`:null:o==null||j(o)===0||a.some(e=>A(o,e))?null:`model-not-allowed`}function de(e,t,n,r){return N(e,t,n,r)===null}var P=r();function F(){let{toolId:e}=p(),t=C(),n=w(),r=S(),i=(0,k.useMemo)(()=>t.data?.find(t=>t.id===e)??null,[t.data,e]),a=(0,k.useMemo)(()=>{let t=n.data;if(t)return Array.isArray(t)?t.find(t=>t.toolId===e):t[e]},[n.data,e]);return t.isLoading?(0,P.jsx)(c,{label:`Loading tool details…`}):t.isError?(0,P.jsx)(s,{title:`Unable to load tool`,message:t.error?.message??`Could not load the CLI tool registry.`,onRetry:()=>void t.refetch()}):i?(0,P.jsx)(I,{tool:i,status:a,apiKeys:r.data??[]}):(0,P.jsxs)(b,{gap:`12px`,children:[(0,P.jsx)(f,{to:`/cli-tools`,className:`text-caption text-accent`,children:`← Back to CLI Tools`}),(0,P.jsx)(s,{title:`Tool not found`,message:`Unknown CLI tool id.`})]})}function I({tool:e,status:t,apiKeys:n}){let[r,s]=(0,k.useState)(``),[c,p]=(0,k.useState)(()=>typeof window>`u`?`http://localhost:12800`:window.location.origin),[S,C]=(0,k.useState)({}),[w,A]=(0,k.useState)(!1),[j,M]=(0,k.useState)({}),[N,F]=(0,k.useState)(null),[I,L]=(0,k.useState)(`slot`),R=(0,k.useRef)(null),z=(0,k.useMemo)(()=>n.filter(e=>e.keyMode===`personal`||e.keyMode===`share`&&e.parentKeyId===void 0),[n]),B=oe(e.id,r),V=T(),H=se(),U=ae(),W=ce(),G=z.find(e=>e.id===r),K=G?.scopes?.includes(`routing:cli_mapping`)===!0,q=G?.keyMode===`personal`,J=G?.modelAccessMode??`whitelist`,Y=G?.modelList,X=e=>de({model_access_mode:J,model_list:Y},e),fe=(0,k.useMemo)(()=>{let t=new Set;if(!e.mappingSupported)return t;for(let n of e.defaultModels){let e=j[n.alias]??``;if(e.length===0)continue;let r=S[n.alias]??n.defaultValue??n.id;!X(e)&&!X(r)&&t.add(n.alias)}return t},[e.defaultModels,e.mappingSupported,j,S,G]),pe=e=>{if(!G)return;let t=Y??[],n=J===`blacklist`?t.filter(t=>t!==e):[...new Set([...t,e])];U.mutate({keyId:G.id,request:{modelAccessMode:J,modelList:n}},{onSuccess:()=>o.success(J===`blacklist`?`Model unblocked for this key`:`Model allowed for this key`),onError:e=>o.error(d(e,`Could not update the API key.`))})};(0,k.useEffect)(()=>{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,e.defaultValue??e.id]));C(t),M({}),R.current=null},[e,r]),(0,k.useEffect)(()=>{let t=B.data;if(t&&t.apiKeyId===r){if(A(t.enabled),t.mappings.length>0)C(e=>({...e,...Object.fromEntries(t.mappings.map(e=>[e.slotKey,e.sourceModel]))})),M(e=>({...e,...Object.fromEntries(t.mappings.map(e=>[e.slotKey,e.targetModel]))}));else{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,e.defaultValue??e.id]));C(t),M({})}R.current=JSON.stringify({enabled:t.enabled,mappings:t.mappings})}},[r,B.data,e.defaultModels]);let me=t?.installed??!1,Z=t?.configured??!1,he=e.configType===`guide`,Q=(0,k.useMemo)(()=>e.defaultModels.map(e=>S[e.alias]??e.defaultValue??e.id),[S,e.defaultModels]),$=(0,k.useCallback)(()=>{if(e.mappingSupported)return{enabled:w,mappings:e.defaultModels.filter(e=>(j[e.alias]??``).length>0).map(e=>({slotKey:e.alias,sourceModel:S[e.alias]??e.defaultValue??e.id,targetModel:j[e.alias]??``,enabled:!0})).filter(e=>e.targetModel.length>0)}},[w,j,S,e.defaultModels,e.mappingSupported]);(0,k.useEffect)(()=>{if(!e.mappingSupported||r.length===0)return;let t=B.data;if(!t||t.apiKeyId!==r)return;let n=$();if(!n)return;let i=JSON.stringify({enabled:n.enabled,mappings:n.mappings});if(i===R.current)return;let a=setTimeout(()=>{V.mutate({toolId:e.id,keyId:r,input:n},{onSuccess:()=>{R.current=i},onError:e=>o.error(d(e,`Could not save mappings.`))})},600);return()=>clearTimeout(a)},[$,r,B.data,V,e.id,e.mappingSupported]);let ge=(0,k.useCallback)(()=>{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,S[e.alias]??e.defaultValue??e.id])),n=e.defaultModels.find(e=>e.roleKind===`subagent`);return{endpoint:c,apiKey:``,modelIds:e.id===`claude`?[]:Q,modelSlots:t,...Q[0]?{activeModel:Q[0]}:{},...n?{subagentModel:S[n.alias]??n.defaultValue??n.id}:{},mappingOwnerId:r,...e.mappingSupported?{mapping:$()}:{}}},[Q,$,r,c,S,e]);return(0,P.jsxs)(b,{gap:`16px`,children:[(0,P.jsxs)(f,{to:`/cli-tools`,className:`inline-flex items-center gap-3 text-caption text-accent`,children:[(0,P.jsx)(i,{size:14}),`Back to CLI Tools`]}),(0,P.jsx)(_,{children:(0,P.jsxs)(h,{className:`flex flex-col gap-5`,children:[(0,P.jsxs)(x,{gap:`14px`,align:`flex-start`,className:`flex-wrap`,children:[(0,P.jsx)(le,{toolId:e.id,name:e.name,color:e.color,size:46}),(0,P.jsxs)(x,{gap:`8px`,className:`flex-wrap`,children:[(0,P.jsx)(`h1`,{className:`text-title font-bold m-0`,children:e.name}),me?(0,P.jsx)(v,{tone:`ok`,children:`Installed`}):(0,P.jsx)(v,{tone:`disabled`,children:`Not installed`}),Z?(0,P.jsx)(v,{tone:`accent`,children:`Configured`}):null,e.configType===`guide`?(0,P.jsx)(v,{tone:`info`,children:`Guide`}):null]})]}),(0,P.jsx)(`p`,{className:`m-0 text-caption text-text-secondary`,children:e.description}),e.docsUrl?(0,P.jsx)(`a`,{href:e.docsUrl,target:`_blank`,rel:`noreferrer`,className:`inline-block text-caption text-accent`,children:`Official documentation →`}):null,t?.settingsPath?(0,P.jsxs)(`div`,{className:`font-mono tabular-nums px-6 py-4 rounded-inset bg-surface-2 border border-inner-border text-badge text-text-secondary break-all`,children:[`Config file: `,t.settingsPath]}):null,t?.message?(0,P.jsxs)(`div`,{className:`flex items-center gap-3 text-badge ${Z?`text-green`:`text-orange`}`,children:[Z?(0,P.jsx)(ee,{size:13}):(0,P.jsx)(E,{size:13}),t.message]}):null]})}),(0,P.jsxs)(_,{children:[(0,P.jsx)(g,{title:`Setup`,subtitle:`Pick the Apikey Name and endpoint, set the models, then download. The key's secret is read server-side — you never paste it.`,icon:(0,P.jsx)(O,{size:16})}),(0,P.jsx)(h,{children:(0,P.jsx)(b,{gap:`12px`,children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 items-start min-w-0 max-medium:grid-cols-1`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-3 min-w-0`,children:[(0,P.jsx)(re,{label:`Apikey Name`,id:`api-key-select`,value:r,onValueChange:s,options:[{value:``,label:`Select…`},...z.map(e=>({value:e.id,label:`${e.label} — ${e.keyMode===`personal`?`Personal`:`Share template`}`}))]}),(0,P.jsx)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:G===void 0?(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Choose the Apikey Name whose routes this CLI session should use.`}):K?(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(v,{tone:`ok`,children:`routing:cli_mapping`}),(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Remote routes work with this key.`})]}):(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(v,{tone:`warn`,title:`This key cannot resolve persisted CLI mappings on /v1/*. Turn on Remote Routing below to enable it.`,children:`no CLI scope`}),(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Remote routes stay inert on this key — turning on Remote Routing below will activate it.`})]})}),G?.keyMode===`share`?(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Share template: its child keys inherit these routes automatically.`}):null]}),(0,P.jsx)(`div`,{className:`min-w-0`,children:(0,P.jsx)(ne,{label:`Endpoint`,value:c,onChange:e=>p(e.target.value)})})]})})})]}),(0,P.jsxs)(_,{children:[(0,P.jsx)(g,{title:`Model routing`,icon:(0,P.jsx)(l,{size:16}),subtitle:e.mappingSupported?`Left is the model name the CLI asks for. Right is the route Cartethyia sends it to. Leave a target empty to send that slot straight to the provider.`:`The model names written into this tool's config.`,action:e.mappingSupported?(0,P.jsxs)(`div`,{className:`flex items-center gap-5`,children:[(0,P.jsx)(y,{checked:w,disabled:!r,onChange:t=>{if(A(t),!r){o.error(`Select an API key first to configure its Remote Routing.`);return}if(t&&G&&!K){let e=[...new Set([...G.scopes??[],`routing:cli_mapping`])];U.mutate({keyId:G.id,request:{scopes:e}},{onSuccess:()=>{o.success(`Activated routing:cli_mapping on "${G.label}".`)},onError:e=>{o.error(d(e,`Could not update key scope.`))}})}let n=$();n&&V.mutate({toolId:e.id,keyId:r,input:{...n,enabled:t}},{onError:e=>o.error(d(e,`Could not save mappings.`))})},label:w?`Remote Routing on`:`Remote Routing off`}),(0,P.jsx)(a,{variant:`secondary`,size:`sm`,disabled:!r||H.isPending,onClick:()=>{if(!r){o.error(`Select an API key first.`);return}H.mutate({toolId:e.id,keyId:r},{onSuccess:()=>{A(!1),M({}),o.success(`Remote routes cleared and Remote Routing turned off.`)},onError:e=>o.error(d(e,`Could not reset remote routes.`))})},children:H.isPending?`Resetting…`:`Reset`})]}):void 0}),(0,P.jsxs)(h,{children:[r?null:(0,P.jsx)(`p`,{className:`text-badge text-text-tertiary mb-5`,children:`Select an API key above to load and save remote routes for that key. You can still set models below to download a config file.`}),e.mappingSupported?(0,P.jsxs)(`div`,{className:`mb-5 flex items-center gap-4 rounded-inset border border-inner-border bg-surface-2 px-5 py-3 text-micro text-text-tertiary`,children:[(0,P.jsx)(`span`,{className:`flex-1`,children:`CLI asks for`}),(0,P.jsx)(`span`,{className:`w-9 text-center`,children:`→`}),(0,P.jsx)(`span`,{className:`flex-1`,children:`Cartethyia routes to`})]}):null,(0,P.jsx)(b,{gap:`6px`,children:e.defaultModels.map(t=>{let n=S[t.alias]??t.defaultValue??t.id,r=j[t.alias]??``,i=r===n;return(0,P.jsxs)(`div`,{className:`grid items-center gap-4 ${e.mappingSupported?`grid-cols-2`:`grid-cols-1`}`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-1 min-w-0`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{F(t.alias),L(`slot`)},className:`font-mono tabular-nums flex w-full min-w-0 cursor-pointer items-center justify-between gap-4 rounded-inset border border-inner-border bg-surface-2 px-5 py-3.5 text-left text-badge text-text-primary`,children:[(0,P.jsx)(`span`,{className:`overflow-hidden truncate whitespace-nowrap`,children:n}),(0,P.jsx)(u,{size:12,className:`text-text-tertiary shrink-0`})]}),(0,P.jsx)(`span`,{className:`text-micro text-text-tertiary pl-1`,children:t.roleLabel??t.name})]}),e.mappingSupported?(0,P.jsxs)(`div`,{className:`flex flex-col gap-1 min-w-0`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{F(t.alias),L(`target`)},className:`font-mono tabular-nums flex w-full min-w-0 cursor-pointer items-center justify-between gap-4 rounded-inset border px-5 py-3.5 text-left text-badge ${i?`border-inner-border bg-surface-2 text-text-tertiary`:`border-accent bg-accent-soft text-text-primary`}`,children:[(0,P.jsx)(`span`,{className:`overflow-hidden truncate whitespace-nowrap`,children:r.length>0?r:`Not routed`}),(0,P.jsx)(u,{size:12,className:`text-text-tertiary shrink-0`})]}),(0,P.jsxs)(`span`,{className:`pl-1 text-micro ${r.length===0?`text-text-tertiary`:`text-accent`}`,children:[r.length===0?`not routed`:`rerouted`,r.length>0?(0,P.jsx)(`button`,{type:`button`,onClick:()=>M(e=>({...e,[t.alias]:``})),className:`ml-3 border-none bg-none p-0 text-micro text-text-tertiary cursor-pointer underline`,children:`clear`}):null]}),fe.has(t.alias)?(0,P.jsxs)(`span`,{className:`mt-1 flex items-center gap-3 text-micro text-orange`,children:[(0,P.jsx)(`span`,{children:J===`blacklist`?`This key blocks the target model.`:`This key does not allow the target model.`}),(0,P.jsx)(`button`,{type:`button`,disabled:U.isPending,onClick:()=>pe(r),className:`border-none bg-none p-0 text-micro font-semibold text-accent underline ${U.isPending?`cursor-not-allowed`:`cursor-pointer`}`,children:J===`blacklist`?`Unblock`:`Allow this model`})]}):null]}):null]},t.alias)})})]})]}),he&&e.guideSteps?(0,P.jsxs)(_,{children:[(0,P.jsx)(g,{title:`Guide`,subtitle:`Manual setup steps for tools that do not support file injection.`,icon:(0,P.jsx)(ue,{size:16})}),(0,P.jsx)(h,{children:(0,P.jsx)(`ol`,{className:`flex flex-col gap-6 pl-9`,children:e.guideSteps.map(e=>(0,P.jsxs)(`li`,{children:[(0,P.jsx)(`strong`,{className:`text-body`,children:e.title}),e.desc?(0,P.jsx)(`p`,{className:`text-caption text-text-secondary mt-2`,children:e.desc}):null,e.value?(0,P.jsx)(`pre`,{className:`mt-3 overflow-x-auto rounded-control border border-inner-border bg-surface-2 p-5 font-mono text-badge`,children:e.value.replace(/\{\{baseUrl\}\}/g,c)}):null]},e.step))})})]}):null,(0,P.jsxs)(_,{children:[(0,P.jsx)(g,{title:`Config file`,subtitle:`Download the config file for this CLI tool. The selected API key secret is decrypted automatically into the download.`,icon:(0,P.jsx)(D,{size:16})}),(0,P.jsxs)(h,{className:`flex flex-col gap-6`,children:[(0,P.jsx)(`div`,{className:`flex gap-4 flex-wrap justify-end`,children:(0,P.jsx)(a,{variant:`primary`,icon:(0,P.jsx)(m,{size:14}),onClick:()=>{if(!r){o.error(`Select an API key first.`);return}W.mutate({toolId:e.id,keyId:r,input:ge()},{onSuccess:e=>{te(e.filename,e.content,e.mimeType),o.success(`Downloaded ${e.filename}.`)},onError:e=>o.error(d(e,`Could not download config.`))})},disabled:W.isPending||!q,children:W.isPending?`Downloading…`:`Download config`})}),q?null:(0,P.jsx)(`p`,{className:`text-badge text-text-tertiary text-right`,children:G?.keyMode===`share`?`A share template has no secret to write into a config. Select a personal key to download the config file.`:`Select an Apikey Name above to enable downloading the config file.`})]})]}),(0,P.jsx)(ie,{open:N!==null,onClose:()=>F(null),selected:N?[I===`target`?j[N]??S[N]??``:S[N]??``]:[],onToggle:()=>{},onSelectOne:e=>{N&&(I===`target`?M(t=>({...t,[N]:e})):C(t=>({...t,[N]:e}))),F(null)},title:N?`Select ${N} model`:`Select model`,multi:!1})]})}export{F 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{t as i}from"./arrow-right-Dl5yKnp8.js";import{D as a,c as o,s,y as c}from"./numbers-YNcHvRY7.js";import{t as l}from"./SortableList-CB3Z7VTf.js";import{n as u,r as d,t as f}from"./state-BgTxHcJS.js";import{t as p}from"./layers-2-glBbjl.js";import{t as m}from"./dialog-BYj-8WuK.js";import{t as h}from"./pencil-768ysRAr.js";import{t as ee}from"./plus-DIdX_g-x.js";import{t as g}from"./route-CNSNj9Zm.js";import{t as _}from"./search-JOKus9rb.js";import{t as te}from"./trash-Cppn5c3R.js";import{l as v}from"./query-keys-gmjzC3RS.js";import{t as ne}from"./dialog-actions-B1axpEg7.js";import{R as y}from"./entry-CgdSAzEV.js";import{n as b,r as re,t as ie}from"./card-BKqlUOe4.js";import{t as ae}from"./use-clipboard-B0Sy03fE.js";import{n as x}from"./input-LVjgZq4f.js";import{t as S}from"./select-WUCXorXw.js";import{t as C}from"./ModelPicker-BY1aKEkP.js";import{t as w}from"./stack-_3O0F8Xn.js";import{n as T,t as oe}from"./inline-DUWDSmDV.js";import{t as E}from"./list-row-DupvLfDx.js";import{a as D,d as O,f as k,i as A,n as j,o as M,p as N,r as P,s as F,t as I,u as L}from"./routing-C7tBT5o-.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=[{value:`fallback`,label:`Fallback (try in order)`},{value:`round_robin`,label:`Round Robin (rotate)`}],H=r(),U=()=>`m${Date.now().toString(36)}${Math.random().toString(36).slice(2,8)}`;function W(){let e=M(),t=j(),n=k(),r=A(),c=L(),[p,S]=(0,B.useState)(!1),[D,O]=(0,B.useState)(null),[N,P]=(0,B.useState)(``),[F,I]=(0,B.useState)(``),[z,V]=(0,B.useState)(!1),[U,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(``),S(!0)},Z=e=>{O(e),P(e.alias),I(e.targetModel),S(!0)},Q=e=>{e.preventDefault(),N.trim()&&F.trim()&&(D?n.mutate({id:D.id,request:{alias:N.trim(),targetModel:F.trim()}},{onSuccess:()=>{S(!1),O(null),s.success(`Alias updated`)},onError:e=>s.error(v(e,`Could not save the alias.`))}):t.mutate({alias:N.trim(),targetModel:F.trim()},{onSuccess:()=>{S(!1),P(``),I(``),s.success(`Alias created`)},onError:e=>s.error(v(e,`Could not create the alias.`))}))},se=(e,t)=>{q(e).then(e=>{if(!e){s.error(`Copy failed`);return}K(t),J(()=>K(null),1500)})};return(0,H.jsxs)(ie,{children:[(0,H.jsx)(re,{title:`Model Aliases`,subtitle:`Map readable names to real models (e.g. claude-mythos-5 → claude-opus-5)`,icon:(0,H.jsx)(g,{size:16}),action:(0,H.jsx)(oe,{children:(0,H.jsx)(o,{variant:`primary`,size:`sm`,icon:(0,H.jsx)(ee,{size:13}),onClick:X,children:`New Alias`})})}),(0,H.jsx)(b,{children:e.isPending?(0,H.jsx)(d,{label:`Loading aliases...`}):e.isError?(0,H.jsx)(u,{message:`Failed to load aliases`,onRetry:()=>e.refetch()}):Y.length===0?(0,H.jsx)(f,{title:`No aliases defined`,message:`Create an alias to route short or friendly names to real provider model IDs.`}):(0,H.jsx)(l,{items:Y,label:`Model aliases`,gap:`8px`,disabled:c.isPending,onReorder:e=>c.mutate(e,{onError:e=>s.error(v(e,`Could not save the new order.`))}),renderItem:e=>(0,H.jsx)(E,{className:`items-start flex-col sm:flex-row`,actions:(0,H.jsxs)(T,{gap:`4px`,className:`w-full justify-start sm:w-auto sm:justify-end`,children:[(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(a,{size:13}),onClick:()=>se(e.alias,e.id),title:G===e.id?`Copied!`:`Copy alias`}),(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(h,{size:13}),onClick:()=>Z(e),title:`Edit target model`}),(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(te,{size:13}),onClick:()=>W(e),title:`Delete alias`,className:`text-red`})]}),children:(0,H.jsxs)(`div`,{className:`flex items-center gap-4 flex-wrap min-w-0`,children:[(0,H.jsx)(y,{value:e.alias,tone:`accent`}),(0,H.jsx)(i,{size:13,className:`text-text-tertiary shrink-0`}),(0,H.jsx)(y,{value:e.targetModel,tone:`plain`})]})})})}),(0,H.jsx)(m,{open:p,onClose:()=>S(!1),title:D?`Edit Model Alias`:`New Model Alias`,footer:(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(o,{variant:`secondary`,type:`button`,onClick:()=>S(!1),children:`Cancel`}),(0,H.jsx)(o,{variant:`primary`,type:`submit`,form:`alias-form`,disabled:t.isPending||n.isPending||!N.trim()||!F.trim(),children:D?`Save Changes`:`Create Alias`})]}),children:(0,H.jsxs)(`form`,{id:`alias-form`,onSubmit:Q,className:`flex flex-col gap-6`,children:[(0,H.jsx)(x,{label:`Alias name`,id:`alias-name-input`,value:N,onChange:e=>P(e.target.value),placeholder:`e.g. fast, sonnet, smart`,required:!0}),(0,H.jsxs)(T,{gap:`8px`,align:`flex-end`,children:[(0,H.jsx)(`div`,{className:`flex-1`,children:(0,H.jsx)(x,{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,H.jsx)(o,{variant:`secondary`,size:`sm`,type:`button`,onClick:()=>V(!0),icon:(0,H.jsx)(_,{size:13}),children:`Browse`})]}),(0,H.jsx)(C,{open:z,onClose:()=>V(!1),selected:F?[F]:[],onToggle:()=>{},onSelectOne:e=>I(e),title:`Select target model`,multi:!1})]})}),(0,H.jsx)(m,{open:!!U,onClose:()=>W(null),title:`Delete Model Alias`,children:(0,H.jsxs)(w,{gap:`12px`,children:[(0,H.jsxs)(`p`,{className:`text-body text-text-secondary`,children:[`Are you sure you want to delete alias `,(0,H.jsx)(`strong`,{children:U?.alias}),`? Clients requesting this name will need their full model path.`]}),(0,H.jsxs)(ne,{children:[(0,H.jsx)(o,{variant:`secondary`,onClick:()=>W(null),children:`Cancel`}),(0,H.jsx)(o,{variant:`danger`,onClick:()=>{U&&r.mutate(U.id,{onSuccess:()=>W(null)})},disabled:r.isPending,children:r.isPending?`Deleting...`:`Delete Alias`})]})]})})]})}function G(){let e=F(),t=P(),n=I(),r=N(),i=D(),g=O(),[k,A]=(0,B.useState)(!1),[j,M]=(0,B.useState)(null),[L,W]=(0,B.useState)(``),[G,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)(()=>G.map(e=>e.value),[G]),de=e=>{$.includes(e)?K(G.filter(t=>t.value!==e)):K([...G,{id:U(),value:e.trim()}])},fe=e.data??[],pe=()=>{M(null),W(``),K([]),J(`fallback`),A(!0)},me=e=>{M(e),W(e.name),K(e.members.map(e=>({id:U(),value:e}))),A(!0)},he=e=>{n.mutate(e.id,{onSuccess:e=>{let t=e.skippedMembers;t.length>0?s.success(`Combo cloned`,`${e.combo.name} — skipped unresolvable member${t.length>1?`s`:``}: ${t.join(`, `)}`):s.success(`Combo cloned`,e.combo.name)},onError:e=>s.error(v(e,`Could not clone the combo.`))})},ge=e=>{e.preventDefault(),L.trim()&&$.length!==0&&(j?r.mutate({id:j.id,request:{name:L.trim(),members:$,strategy:q}},{onSuccess:()=>{A(!1),M(null),s.success(`Combo saved`)},onError:e=>s.error(v(e,`Could not save the combo.`))}):t.mutate({name:L.trim(),members:$,strategy:q},{onSuccess:()=>{A(!1),W(``),K([]),s.success(`Combo created`)},onError:e=>s.error(v(e,`Could not create the combo.`))}))},_e=(e,t)=>{r.mutate({id:e.id,request:{strategy:t}},{onSuccess:()=>s.success(`Combo strategy updated`),onError:e=>s.error(v(e,`Could not update the combo strategy.`))})},ve=(e,t)=>{le(e).then(e=>{if(!e){s.error(`Copy failed`);return}ce(t),ue(()=>ce(null),1500)})};return(0,H.jsxs)(ie,{children:[(0,H.jsx)(re,{title:`Combos`,subtitle:`Combine multiple models with fallback or round-robin rotation`,icon:(0,H.jsx)(p,{size:16}),action:(0,H.jsx)(oe,{children:(0,H.jsx)(o,{variant:`primary`,size:`sm`,icon:(0,H.jsx)(ee,{size:13}),onClick:pe,children:`New Combo`})})}),(0,H.jsx)(b,{children:e.isPending?(0,H.jsx)(d,{label:`Loading combos...`}):e.isError?(0,H.jsx)(u,{message:`Failed to load combos`,onRetry:()=>e.refetch()}):fe.length===0?(0,H.jsx)(f,{title:`No combos defined`,message:`Create a model combo to distribute requests across multiple models.`}):(0,H.jsx)(l,{items:fe,label:`Model combos`,disabled:g.isPending,onReorder:e=>g.mutate(e,{onError:e=>s.error(v(e,`Could not save the new order.`))}),renderItem:e=>(0,H.jsxs)(E,{className:`items-start flex-col sm:flex-row`,actions:(0,H.jsxs)(T,{gap:`8px`,className:`w-full justify-between sm:w-auto sm:justify-start`,children:[(0,H.jsx)(S,{value:e.strategy,onValueChange:t=>_e(e,t),options:V}),(0,H.jsxs)(T,{gap:`4px`,className:`flex-wrap`,children:[(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(a,{size:13}),onClick:()=>ve(e.name,e.id),title:se===e.id?`Copied!`:`Copy combo name`}),(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(z,{size:13}),onClick:()=>he(e),disabled:n.isPending,title:`Clone combo (same members and strategy)`}),(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(h,{size:13}),onClick:()=>me(e),title:`Edit combo`}),(0,H.jsx)(o,{variant:`ghost`,size:`sm`,icon:(0,H.jsx)(te,{size:13}),onClick:()=>Q(e),title:`Delete combo`,className:`text-red`})]})]}),children:[(0,H.jsx)(`div`,{className:`flex items-center justify-between gap-6 flex-wrap`,children:(0,H.jsx)(`strong`,{className:`text-body`,children:e.name})}),(0,H.jsx)(T,{gap:`6px`,className:`w-full flex-wrap`,children:e.members.map(e=>(0,H.jsx)(y,{value:e},e))})]})})}),(0,H.jsx)(m,{open:k,onClose:()=>A(!1),title:j?`Edit Model Combo`:`New Model Combo`,footer:(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(o,{variant:`secondary`,type:`button`,onClick:()=>A(!1),children:`Cancel`}),(0,H.jsx)(o,{variant:`primary`,type:`submit`,form:`combo-form`,disabled:t.isPending||r.isPending||!L.trim()||$.length===0,children:j?`Save Changes`:`Create Combo`})]}),children:(0,H.jsxs)(`form`,{id:`combo-form`,onSubmit:ge,className:`flex flex-col gap-6`,children:[(0,H.jsx)(x,{label:`Combo name`,id:`combo-name-input`,value:L,onChange:e=>W(e.target.value),placeholder:`e.g. smart-combo, fast-pool`,required:!0}),(0,H.jsx)(S,{label:`Strategy`,id:`combo-strategy-select`,value:q,onValueChange:e=>J(e),options:V}),(0,H.jsxs)(w,{gap:`6px`,children:[(0,H.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,H.jsx)(`label`,{className:`text-caption font-semibold text-text-secondary`,children:`Member models — use arrows or drag to reorder`}),(0,H.jsx)(o,{variant:`secondary`,size:`sm`,type:`button`,icon:(0,H.jsx)(_,{size:12}),onClick:()=>X(!0),children:`Browse models`})]}),(0,H.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 ${G.length===0?`p-0`:`p-3`}`,children:G.length===0?(0,H.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,H.jsx)(l,{items:G,label:`Combo members`,gap:`4px`,onReorder:e=>{let t=new Map(G.map(e=>[e.id,e]));K(e.map(e=>t.get(e)).filter(Boolean))},renderItem:e=>(0,H.jsx)(E,{density:`compact`,actions:(0,H.jsx)(`button`,{type:`button`,onClick:()=>de(e.value),"aria-label":`Remove ${e.value}`,className:`flex shrink-0 cursor-pointer text-text-tertiary`,children:(0,H.jsx)(c,{size:12})}),children:(0,H.jsx)(y,{value:e.value,tone:`accent`})})})})]}),(0,H.jsx)(C,{open:Y,onClose:()=>X(!1),selected:$,onToggle:de,title:`Select combo members`,multi:!0})]})}),(0,H.jsx)(m,{open:!!Z,onClose:()=>Q(null),title:`Delete Model Combo`,children:(0,H.jsxs)(w,{gap:`12px`,children:[(0,H.jsxs)(`p`,{className:`text-body text-text-secondary`,children:[`Are you sure you want to delete combo `,(0,H.jsx)(`strong`,{children:Z?.name}),`? Any aliases pointing to it will fail to resolve.`]}),(0,H.jsxs)(ne,{children:[(0,H.jsx)(o,{variant:`secondary`,onClick:()=>Q(null),children:`Cancel`}),(0,H.jsx)(o,{variant:`danger`,onClick:()=>{Z&&i.mutate(Z.id,{onSuccess:()=>Q(null)})},disabled:i.isPending,children:i.isPending?`Deleting...`:`Delete Combo`})]})]})})]})}function K(){return(0,H.jsxs)(w,{gap:`16px`,children:[(0,H.jsx)(G,{}),(0,H.jsx)(W,{})]})}export{K as default};
@@ -1 +0,0 @@
1
- import{o as e,r as t,t as n}from"./jsx-runtime-CXUMngI_.js";import{c as r,k as i,y as a}from"./numbers-YNcHvRY7.js";import{t as o}from"./dialog-BYj-8WuK.js";import{t as s}from"./route-CNSNj9Zm.js";import{t as c}from"./search-JOKus9rb.js";import{C as l,v as u}from"./assert-Chi7pCnt.js";import{o as d,t as f}from"./query-keys-gmjzC3RS.js";import{s as p}from"./tokens-DDxMbSRL.js";import{t as m}from"./badge-D4pQapKj.js";import{t as h}from"./ProviderIcon-CmfzxpVM.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)(r,{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)(a,{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};