cartethyia 2.5.10 → 2.6.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 (367) hide show
  1. package/.env.example +8 -0
  2. package/dist/dashboard/assets/{BannedPage-Cd30Z0b9.js → BannedPage-l6XMnfr9.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-wf5u-ww6.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DykPvuck.js → CliToolsPage-BT3Up3QA.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-DwvSD3vs.js → CombosPage-6kYIUpzR.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CIr7KDex.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-82X0DDdH.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-CS5mPuVF.js → LoginPage-CbphLvjg.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-C0UCUsVJ.js → ModelPicker-DVny4S5_.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  24. package/dist/dashboard/assets/{ProvidersPage-vdja5ZPK.js → ProvidersPage-Dfb2896L.js} +1 -1
  25. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProxyPage-BB4CNm4-.js → ProxyPage-BaCIdAUt.js} +1 -1
  27. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  28. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  30. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  32. package/dist/dashboard/assets/{SetupPage-FadUOl9S.js → SetupPage-D4g6vOGc.js} +1 -1
  33. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  34. package/dist/dashboard/assets/{SortableList-Cfy6_Khv.js → SortableList-C93i14BD.js} +1 -1
  35. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  36. package/dist/dashboard/assets/{StudioPage-CM92fgJf.js → StudioPage-SgQpymjm.js} +1 -1
  37. package/dist/dashboard/assets/{StudioPage-CM92fgJf.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  38. package/dist/dashboard/assets/{UsagePage-BT4MyQeT.js → UsagePage-BFME-eba.js} +2 -2
  39. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  40. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  41. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  42. package/dist/dashboard/assets/{app-B7pVRhJ1.js → app-2KhtHCv9.js} +1 -1
  43. package/dist/dashboard/assets/{app-B7pVRhJ1.js.gz → app-2KhtHCv9.js.gz} +0 -0
  44. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  46. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  47. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-Bvyr7D0u.js → badge-CKsF123d.js} +1 -1
  49. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-DIvKAZih.js → banner-8j7uChA9.js} +1 -1
  51. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-D5_gYIS_.js → card-CdsSlWA3.js} +1 -1
  53. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-B6vDBtbm.js → clipboard-button-uL5aYLfW.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-CpjrgdwJ.js → dialog-C07hr5_G.js} +1 -1
  59. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BWmt3b5y.js → drawer-BPO1TcKH.js} +1 -1
  61. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-C_oakV3z.js → entry-Bz_CtDfC.js} +1 -1
  63. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  64. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-RRHHWGxh.js → github-badge-DmQreSDd.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-Bhey_rg_.js → index-Bblu6erR.js} +2 -2
  71. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-C2_EQ5Uf.js → inline-C9dCYaBZ.js} +1 -1
  73. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BMRlQ450.js → input-CjCnXweJ.js} +1 -1
  75. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-XR5xP8ew.js → layout-V6y8DAkl.js} +1 -1
  77. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-Cw07dioc.js → list-row-CM1AaG_y.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  81. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  84. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  85. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  86. package/dist/dashboard/assets/{numbers-DrdA9oSM.js → numbers-BOevJzdz.js} +1 -1
  87. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  88. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  89. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  90. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  91. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  92. package/dist/dashboard/assets/{select-CxVVB__N.js → select-C980c4cz.js} +1 -1
  93. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  94. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  95. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  96. package/dist/dashboard/assets/{stack-uPOwM9Hs.js → stack-_gfTdLVB.js} +1 -1
  97. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  98. package/dist/dashboard/assets/{state-D1Yz79iL.js → state-2c5V4zo7.js} +1 -1
  99. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  100. package/dist/dashboard/assets/{switch-sevsRrRo.js → switch-Dkke7Naf.js} +1 -1
  101. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  102. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  103. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  104. package/dist/dashboard/index.html +1 -1
  105. package/dist/dashboard/index.html.gz +0 -0
  106. package/dist/dashboard/precompressed-assets.json +51 -51
  107. package/migrations/README.md +52 -52
  108. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  109. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  110. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  111. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  112. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  113. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  114. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  115. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  116. package/package.json +96 -95
  117. package/scripts/cli/admin.ts +126 -128
  118. package/scripts/cli/autostart.ts +237 -237
  119. package/scripts/cli/background.ts +120 -110
  120. package/scripts/cli/bin/carte.cjs +8 -8
  121. package/scripts/cli/bin/cartethyia.cjs +167 -167
  122. package/scripts/cli/console-menu.ts +39 -30
  123. package/scripts/cli/controller.ts +145 -0
  124. package/scripts/cli/env.ts +99 -99
  125. package/scripts/cli/gateway.ts +264 -260
  126. package/scripts/cli/instance.ts +234 -224
  127. package/scripts/cli/main.ts +188 -167
  128. package/scripts/cli/platform.ts +64 -64
  129. package/scripts/cli/prompt.ts +217 -79
  130. package/scripts/cli/services.ts +88 -88
  131. package/scripts/cli/setup.ts +445 -445
  132. package/scripts/cli/start.ts +85 -85
  133. package/scripts/cli/startup-signal.ts +23 -23
  134. package/scripts/cli/telemetry.ts +70 -0
  135. package/scripts/cli/tray/index.ts +263 -263
  136. package/scripts/cli/tray/windows.ps1 +107 -107
  137. package/scripts/cli/tray.ts +29 -4
  138. package/scripts/cli/update.ts +39 -39
  139. package/scripts/cli/version.ts +67 -67
  140. package/scripts/cli/wordmark.ts +58 -58
  141. package/src/boot/app.ts +16 -1
  142. package/src/boot/dependencies.ts +128 -25
  143. package/src/config/resources.ts +137 -0
  144. package/src/config/runtime.ts +133 -76
  145. package/src/config/spec.ts +30 -29
  146. package/src/config/telemetry.ts +0 -25
  147. package/src/console/api-keys/bucket-spend.ts +14 -17
  148. package/src/console/api-keys/contracts.ts +410 -410
  149. package/src/console/api-keys/routes.ts +12 -0
  150. package/src/console/audit/store.ts +2 -3
  151. package/src/console/auth/setup-service.ts +8 -1
  152. package/src/console/backup/routes.ts +7 -0
  153. package/src/console/backup/service.ts +463 -463
  154. package/src/console/backup/store.ts +16 -11
  155. package/src/console/backup/validate.ts +1 -4
  156. package/src/console/cli-tools/fs-ops.ts +18 -2
  157. package/src/console/data-management/contracts.ts +1 -8
  158. package/src/console/data-management/operations.ts +61 -48
  159. package/src/console/data-management/routes.ts +4 -2
  160. package/src/console/domain-registration.ts +25 -3
  161. package/src/console/inferhub/client.ts +139 -0
  162. package/src/console/inferhub/key-store.ts +129 -0
  163. package/src/console/inferhub/normalize.ts +347 -0
  164. package/src/console/inferhub/routes.ts +411 -0
  165. package/src/console/inferhub/store.ts +109 -0
  166. package/src/console/inferhub/types.ts +183 -0
  167. package/src/console/observability/contracts.ts +2 -41
  168. package/src/console/observability/store.ts +176 -371
  169. package/src/console/providers/catalog/public-model-store.ts +0 -0
  170. package/src/console/providers/catalog/store.ts +23 -36
  171. package/src/console/quota/account-quota-shared.ts +52 -52
  172. package/src/console/quota/account-quota-tenant.ts +16 -24
  173. package/src/console/quota/refresh.ts +441 -441
  174. package/src/console/routing/pools/import-parse.ts +206 -206
  175. package/src/console/routing/pools/operations.ts +543 -543
  176. package/src/console/routing/pools/probe-verdict.ts +132 -132
  177. package/src/console/routing/pools/routes.ts +181 -181
  178. package/src/console/settings/contracts.ts +42 -40
  179. package/src/console/settings/store.ts +25 -25
  180. package/src/console/share/share-stats.ts +22 -4
  181. package/src/console/share/share-usage.ts +30 -4
  182. package/src/console/shared/cursor.ts +15 -0
  183. package/src/console/updates/routes.ts +12 -0
  184. package/src/main.ts +13 -5
  185. package/src/network/pool/resolver.ts +1104 -1104
  186. package/src/observability/client-name.ts +22 -0
  187. package/src/observability/event-log.ts +5 -29
  188. package/src/observability/event-query.ts +690 -0
  189. package/src/observability/log-ring.ts +26 -2
  190. package/src/observability/log-sanitize.ts +118 -0
  191. package/src/observability/logger.ts +17 -13
  192. package/src/observability/metrics.ts +35 -0
  193. package/src/observability/payload-capture.ts +114 -41
  194. package/src/observability/payload-store.ts +249 -24
  195. package/src/observability/raw-event-writer.ts +118 -0
  196. package/src/observability/runtime-metrics.ts +71 -1
  197. package/src/observability/telemetry-buffer.ts +100 -17
  198. package/src/observability/telemetry-reconciliation.ts +417 -0
  199. package/src/persistence/boot.ts +20 -9
  200. package/src/persistence/db-pg.ts +13 -0
  201. package/src/persistence/db-sqlite.ts +569 -497
  202. package/src/persistence/dialect.ts +3 -2
  203. package/src/persistence/migrate.ts +181 -181
  204. package/src/persistence/schema.ts +1496 -1406
  205. package/src/persistence/shared-cache.ts +5 -1
  206. package/src/persistence/stores/api-key-store.ts +581 -581
  207. package/src/persistence/stores/telemetry-store.ts +124 -63
  208. package/src/protocol/reasoning-text.ts +30 -0
  209. package/src/protocol/response/chat.ts +33 -7
  210. package/src/protocol/response/codex.ts +14 -2
  211. package/src/protocol/response/messages.ts +5 -2
  212. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  213. package/src/providers/bundled-discovery-providers.ts +122 -122
  214. package/src/providers/default-registry.ts +10 -0
  215. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  216. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  217. package/src/providers/discovery/probe-phases.ts +43 -7
  218. package/src/providers/discovery/probing-service.ts +70 -32
  219. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  220. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  221. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  222. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  223. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  224. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  225. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  226. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  227. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  228. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  229. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  230. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  231. package/src/providers/integrations/claude/claude.ts +3 -4
  232. package/src/providers/integrations/cline/cline.ts +94 -8
  233. package/src/providers/integrations/codex/codex.ts +61 -37
  234. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  235. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  236. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  237. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  238. package/src/providers/integrations/cursor/cursor.ts +29 -17
  239. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  240. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  241. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  242. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  243. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  244. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  245. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  246. package/src/providers/integrations/hermes.ts +7 -1
  247. package/src/providers/integrations/inferhub.ts +201 -73
  248. package/src/providers/integrations/kilo/kilo.ts +152 -152
  249. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  250. package/src/providers/integrations/openrouter/provider.ts +129 -129
  251. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  252. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  253. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  254. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  255. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  256. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  257. package/src/providers/integrations/zcode/oauth.ts +1 -1
  258. package/src/providers/operations/account-health-service.ts +605 -279
  259. package/src/providers/provider-metadata.ts +12 -1
  260. package/src/providers/provider-model-definition.ts +373 -373
  261. package/src/providers/quota/quota-contracts.ts +3 -7
  262. package/src/providers/search/search-provider.ts +2 -6
  263. package/src/security/admission/contracts.ts +10 -0
  264. package/src/security/admission/in-memory-store.ts +89 -14
  265. package/src/security/admission/lua.ts +191 -182
  266. package/src/security/admission/redis-store.ts +30 -0
  267. package/src/security/admission/service.ts +18 -0
  268. package/src/security/api-key-auth-cache.ts +96 -0
  269. package/src/security/api-key-auth.ts +11 -100
  270. package/src/security/client-router-fingerprint.ts +3 -7
  271. package/src/security/ip-boundary.ts +7 -3
  272. package/src/shared/coerce.ts +67 -0
  273. package/src/shared/hash.ts +6 -0
  274. package/src/shared/time.ts +7 -0
  275. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  276. package/src/transport/dispatch/attempt-loop.ts +103 -81
  277. package/src/transport/dispatch/capture-policy.ts +78 -0
  278. package/src/transport/dispatch/proxy-request.ts +4 -2
  279. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  280. package/src/transport/dispatch/streaming/stream.ts +19 -2
  281. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  282. package/src/transport/failure-policy.ts +27 -0
  283. package/src/transport/guards/abuse-guard.ts +2 -2
  284. package/src/transport/guards/auth-guard.ts +17 -0
  285. package/src/transport/guards/body-policy.ts +3 -2
  286. package/src/transport/guards/concurrency-guard.ts +89 -13
  287. package/src/transport/guards/early-rejection-capture.ts +43 -3
  288. package/src/transport/guards/error-lifecycle.ts +10 -2
  289. package/src/transport/guards/guard-errors.ts +23 -0
  290. package/src/transport/guards/headroom-guard.ts +41 -7
  291. package/src/transport/guards/pipeline.ts +24 -0
  292. package/src/transport/guards/request-context.ts +2 -1
  293. package/src/transport/routing/admission.ts +51 -9
  294. package/src/transport/routing/bounded-lru.ts +49 -0
  295. package/src/transport/routing/candidate-index.ts +88 -0
  296. package/src/transport/routing/reservations.ts +121 -7
  297. package/src/transport/routing/round-robin.ts +10 -22
  298. package/src/transport/routing/route-catalog.ts +26 -13
  299. package/src/transport/routing/route-model.ts +22 -2
  300. package/src/transport/routing/router.ts +53 -31
  301. package/src/transport/surface/chat/encode.ts +2 -3
  302. package/src/transport/surface/chat/parse.ts +2 -1
  303. package/src/transport/surface/dialects.ts +11 -3
  304. package/src/transport/surface/messages/encode.ts +2 -1
  305. package/src/transport/surface/messages/parse.ts +3 -1
  306. package/src/transport/surface/messages/stream.ts +2 -6
  307. package/src/workers/maintenance-lease.ts +200 -0
  308. package/src/workers/scheduler.ts +101 -1
  309. package/dist/dashboard/assets/BannedPage-Cd30Z0b9.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolDetailPage-wf5u-ww6.js.gz +0 -0
  311. package/dist/dashboard/assets/CliToolsPage-DykPvuck.js.gz +0 -0
  312. package/dist/dashboard/assets/CombosPage-DwvSD3vs.js.gz +0 -0
  313. package/dist/dashboard/assets/ConsoleLogPage-CIr7KDex.js.gz +0 -0
  314. package/dist/dashboard/assets/CustomizationPage-82X0DDdH.js.gz +0 -0
  315. package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js +0 -1
  316. package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-CS5mPuVF.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-C0UCUsVJ.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js +0 -1
  320. package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js.gz +0 -0
  321. package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js +0 -3
  322. package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-vdja5ZPK.js.gz +0 -0
  324. package/dist/dashboard/assets/ProxyPage-BB4CNm4-.js.gz +0 -0
  325. package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js +0 -1
  326. package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js.gz +0 -0
  327. package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js +0 -1
  328. package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-FadUOl9S.js.gz +0 -0
  330. package/dist/dashboard/assets/SortableList-Cfy6_Khv.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-BT4MyQeT.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  333. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  334. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  336. package/dist/dashboard/assets/badge-Bvyr7D0u.js.gz +0 -0
  337. package/dist/dashboard/assets/banner-DIvKAZih.js.gz +0 -0
  338. package/dist/dashboard/assets/card-D5_gYIS_.js.gz +0 -0
  339. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  340. package/dist/dashboard/assets/clipboard-button-B6vDBtbm.js.gz +0 -0
  341. package/dist/dashboard/assets/dialog-CpjrgdwJ.js.gz +0 -0
  342. package/dist/dashboard/assets/drawer-BWmt3b5y.js.gz +0 -0
  343. package/dist/dashboard/assets/entry-B-gvimrg.js +0 -1
  344. package/dist/dashboard/assets/entry-B-gvimrg.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-C_oakV3z.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-Dj0cHfEc.js +0 -4
  347. package/dist/dashboard/assets/entry-Dj0cHfEc.js.gz +0 -0
  348. package/dist/dashboard/assets/github-badge-RRHHWGxh.js.gz +0 -0
  349. package/dist/dashboard/assets/index-Bhey_rg_.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-C2_EQ5Uf.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BMRlQ450.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-XR5xP8ew.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-Cw07dioc.js.gz +0 -0
  354. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  355. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  356. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  357. package/dist/dashboard/assets/numbers-DrdA9oSM.js.gz +0 -0
  358. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  359. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  360. package/dist/dashboard/assets/select-CxVVB__N.js.gz +0 -0
  361. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  362. package/dist/dashboard/assets/stack-uPOwM9Hs.js.gz +0 -0
  363. package/dist/dashboard/assets/state-D1Yz79iL.js.gz +0 -0
  364. package/dist/dashboard/assets/switch-sevsRrRo.js.gz +0 -0
  365. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  366. package/src/observability/event-compaction.ts +0 -317
  367. package/src/observability/event-day-index.ts +0 -439
@@ -1,161 +1,161 @@
1
- import type { FetchLike, ProviderQuotaResult } from "../../../quota/quota-contracts";
2
- import type { QuotaCollectionContext } from "../../../quota/quota-support";
3
- import { providerBaseUrl } from "../../../provider-metadata";
4
- import {
5
- buildWorkBuddyUserAgent,
6
- getWorkBuddyClientVersion,
7
- getWorkBuddyCnClientVersion,
8
- getWorkBuddyCliVersion,
9
- } from "../../../operations/client-versions";
10
- import { WORKBUDDY_DOMAIN } from "./shared";
11
- import { fetchTencentBillingQuota } from "../buddy-quota-shared";
12
- import { fetchBuddyRequestUsage, type BuddyUsageResult } from "../buddy-usage-shared";
13
- import { buddyAccountUid } from "../buddy-oauth-shared";
14
-
15
- /**
16
- * WorkBuddy international billing quota.
17
- *
18
- * Endpoint: POST https://www.workbuddy.ai/v2/billing/meter/get-user-resource
19
- * Request: Authorization: Bearer <accessToken||apiKey>, desktop-client headers,
20
- * X-User-Id for the account UID, and the reference filter envelope
21
- * (ProductCode p_tcaca, statuses [0, 3], 101-year package window).
22
- * Response: { code: 0, data: { Response: { Data: { Accounts: [...] } } } }
23
- *
24
- * Same Tencent billing envelope as CodeBuddy CN, so the parsing is the shared
25
- * one in `buddy-quota-shared.ts`; only the endpoint and identity headers are
26
- * WorkBuddy-specific.
27
- */
28
-
29
- /**
30
- * The CN billing realm's host.
31
- *
32
- * The reference bills the CN realm here rather than on the CN chat host
33
- * (`www.workbuddy.cn`): billing is a separate service, and the chat host does
34
- * not serve the meter path.
35
- */
36
- const WORKBUDDY_CN_BILLING_BASE_URL = "https://www.codebuddy.cn";
37
-
38
- export const WORKBUDDY_USAGE_URL =
39
- `${providerBaseUrl("workbuddy")}/v2/billing/meter/get-user-resource`;
40
-
41
- async function billingHeaders(
42
- domain: string,
43
- clientVersion: string,
44
- enterpriseId?: string,
45
- ): Promise<Record<string, string>> {
46
- return {
47
- "User-Agent": buildWorkBuddyUserAgent(clientVersion, getWorkBuddyCliVersion()),
48
- "X-Product": "SaaS",
49
- "X-Domain": domain,
50
- "X-Requested-With": "XMLHttpRequest",
51
- "X-CodeBuddy-Request": "1",
52
- "Accept-Language": "en-US",
53
- ...(enterpriseId === undefined || enterpriseId.length === 0
54
- ? {}
55
- : { "X-Tenant-Id": enterpriseId, "X-Enterprise-Id": enterpriseId }),
56
- };
57
- }
58
-
59
- /**
60
- * Fetches WorkBuddy quota via the Tencent billing endpoint.
61
- *
62
- * Auth: `Authorization: Bearer <credential>` (api_key and oauth both use
63
- * Bearer; the caller passes the resolved secret).
64
- */
65
- export async function fetchWorkBuddyQuota(
66
- credential: string,
67
- fetcher: FetchLike,
68
- context?: QuotaCollectionContext,
69
- ): Promise<ProviderQuotaResult> {
70
- return fetchWorkBuddyQuotaFor({
71
- providerId: "workbuddy",
72
- display: "WorkBuddy",
73
- domain: WORKBUDDY_DOMAIN,
74
- credential,
75
- fetcher,
76
- ...(context === undefined ? {} : { context }),
77
- });
78
- }
79
-
80
- export async function fetchWorkBuddyCnQuota(
81
- credential: string,
82
- fetcher: FetchLike,
83
- context?: QuotaCollectionContext,
84
- ): Promise<ProviderQuotaResult> {
85
- return fetchWorkBuddyQuotaFor({
86
- providerId: "workbuddycn",
87
- display: "WorkBuddy CN",
88
- domain: "www.workbuddy.cn",
89
- credential,
90
- fetcher,
91
- ...(context === undefined ? {} : { context }),
92
- });
93
- }
94
-
95
- async function fetchWorkBuddyQuotaFor(args: {
96
- readonly providerId: "workbuddy" | "workbuddycn";
97
- readonly display: string;
98
- readonly domain: string;
99
- readonly credential: string;
100
- readonly fetcher: FetchLike;
101
- readonly context?: QuotaCollectionContext;
102
- }): Promise<ProviderQuotaResult> {
103
- const { providerId, display, domain, credential, fetcher, context } = args;
104
- const authState = typeof context?.auth_state === "object" && context?.auth_state !== null
105
- ? (context.auth_state as Record<string, unknown>)
106
- : undefined;
107
- const enterpriseId = typeof authState?.enterpriseId === "string" ? authState.enterpriseId : undefined;
108
- const clientVersion = providerId === "workbuddycn"
109
- ? getWorkBuddyCnClientVersion()
110
- : getWorkBuddyClientVersion();
111
- // The reference bills the CN realm on `www.codebuddy.cn`, not the chat host:
112
- // the billing realm is a different service from the one that serves chat,
113
- // and the CN chat host answers the meter path with an HTML error page.
114
- const billingBase = providerId === "workbuddycn" ? WORKBUDDY_CN_BILLING_BASE_URL : providerBaseUrl(providerId);
115
- return fetchTencentBillingQuota({
116
- source: providerId,
117
- display,
118
- url: `${billingBase}/v2/billing/meter/get-user-resource`,
119
- headers: await billingHeaders(domain, clientVersion, enterpriseId),
120
- credential,
121
- fetcher,
122
- defaultPlan: display,
123
- uid: buddyAccountUid(credential),
124
- });
125
- }
126
-
127
- /**
128
- * Real credit deductions for one WorkBuddy account.
129
- *
130
- * Mirrors {@link fetchWorkBuddyQuotaFor} in every respect but the endpoint —
131
- * same billing host, same client-version identity headers, same account UID —
132
- * because the usage-detail surface is the billing meter's other read.
133
- */
134
- export async function fetchWorkBuddyRequestUsage(
135
- credential: string,
136
- fetcher: FetchLike,
137
- args: {
138
- readonly providerId?: "workbuddy" | "workbuddycn";
139
- readonly days?: number;
140
- readonly context?: QuotaCollectionContext;
141
- } = {},
142
- ): Promise<BuddyUsageResult> {
143
- const providerId = args.providerId ?? "workbuddy";
144
- const domain = providerId === "workbuddycn" ? "www.workbuddy.cn" : WORKBUDDY_DOMAIN;
145
- const authState = typeof args.context?.auth_state === "object" && args.context?.auth_state !== null
146
- ? (args.context.auth_state as Record<string, unknown>)
147
- : undefined;
148
- const enterpriseId = typeof authState?.enterpriseId === "string" ? authState.enterpriseId : undefined;
149
- const clientVersion = providerId === "workbuddycn"
150
- ? getWorkBuddyCnClientVersion()
151
- : getWorkBuddyClientVersion();
152
- return fetchBuddyRequestUsage({
153
- url: `${providerBaseUrl(providerId)}/billing/meter/get-user-request-usage`,
154
- headers: await billingHeaders(domain, clientVersion, enterpriseId),
155
- credential,
156
- fetcher,
157
- days: args.days ?? 30,
158
- uid: buddyAccountUid(credential),
159
- });
160
- }
161
-
1
+ import type { FetchLike, ProviderQuotaResult } from "../../../quota/quota-contracts";
2
+ import type { QuotaCollectionContext } from "../../../quota/quota-support";
3
+ import { providerBaseUrl } from "../../../provider-metadata";
4
+ import {
5
+ buildWorkBuddyUserAgent,
6
+ getWorkBuddyClientVersion,
7
+ getWorkBuddyCnClientVersion,
8
+ getWorkBuddyCliVersion,
9
+ } from "../../../operations/client-versions";
10
+ import { WORKBUDDY_DOMAIN } from "./shared";
11
+ import { fetchTencentBillingQuota } from "../buddy-quota-shared";
12
+ import { fetchBuddyRequestUsage, type BuddyUsageResult } from "../buddy-usage-shared";
13
+ import { buddyAccountUid } from "../buddy-oauth-shared";
14
+
15
+ /**
16
+ * WorkBuddy international billing quota.
17
+ *
18
+ * Endpoint: POST https://www.workbuddy.ai/v2/billing/meter/get-user-resource
19
+ * Request: Authorization: Bearer <accessToken||apiKey>, desktop-client headers,
20
+ * X-User-Id for the account UID, and the reference filter envelope
21
+ * (ProductCode p_tcaca, statuses [0, 3], 101-year package window).
22
+ * Response: { code: 0, data: { Response: { Data: { Accounts: [...] } } } }
23
+ *
24
+ * Same Tencent billing envelope as CodeBuddy CN, so the parsing is the shared
25
+ * one in `buddy-quota-shared.ts`; only the endpoint and identity headers are
26
+ * WorkBuddy-specific.
27
+ */
28
+
29
+ /**
30
+ * The CN billing realm's host.
31
+ *
32
+ * The reference bills the CN realm here rather than on the CN chat host
33
+ * (`www.workbuddy.cn`): billing is a separate service, and the chat host does
34
+ * not serve the meter path.
35
+ */
36
+ const WORKBUDDY_CN_BILLING_BASE_URL = "https://www.codebuddy.cn";
37
+
38
+ export const WORKBUDDY_USAGE_URL =
39
+ `${providerBaseUrl("workbuddy")}/v2/billing/meter/get-user-resource`;
40
+
41
+ async function billingHeaders(
42
+ domain: string,
43
+ clientVersion: string,
44
+ enterpriseId?: string,
45
+ ): Promise<Record<string, string>> {
46
+ return {
47
+ "User-Agent": buildWorkBuddyUserAgent(clientVersion, getWorkBuddyCliVersion()),
48
+ "X-Product": "SaaS",
49
+ "X-Domain": domain,
50
+ "X-Requested-With": "XMLHttpRequest",
51
+ "X-CodeBuddy-Request": "1",
52
+ "Accept-Language": "en-US",
53
+ ...(enterpriseId === undefined || enterpriseId.length === 0
54
+ ? {}
55
+ : { "X-Tenant-Id": enterpriseId, "X-Enterprise-Id": enterpriseId }),
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Fetches WorkBuddy quota via the Tencent billing endpoint.
61
+ *
62
+ * Auth: `Authorization: Bearer <credential>` (api_key and oauth both use
63
+ * Bearer; the caller passes the resolved secret).
64
+ */
65
+ export async function fetchWorkBuddyQuota(
66
+ credential: string,
67
+ fetcher: FetchLike,
68
+ context?: QuotaCollectionContext,
69
+ ): Promise<ProviderQuotaResult> {
70
+ return fetchWorkBuddyQuotaFor({
71
+ providerId: "workbuddy",
72
+ display: "WorkBuddy",
73
+ domain: WORKBUDDY_DOMAIN,
74
+ credential,
75
+ fetcher,
76
+ ...(context === undefined ? {} : { context }),
77
+ });
78
+ }
79
+
80
+ export async function fetchWorkBuddyCnQuota(
81
+ credential: string,
82
+ fetcher: FetchLike,
83
+ context?: QuotaCollectionContext,
84
+ ): Promise<ProviderQuotaResult> {
85
+ return fetchWorkBuddyQuotaFor({
86
+ providerId: "workbuddycn",
87
+ display: "WorkBuddy CN",
88
+ domain: "www.workbuddy.cn",
89
+ credential,
90
+ fetcher,
91
+ ...(context === undefined ? {} : { context }),
92
+ });
93
+ }
94
+
95
+ async function fetchWorkBuddyQuotaFor(args: {
96
+ readonly providerId: "workbuddy" | "workbuddycn";
97
+ readonly display: string;
98
+ readonly domain: string;
99
+ readonly credential: string;
100
+ readonly fetcher: FetchLike;
101
+ readonly context?: QuotaCollectionContext;
102
+ }): Promise<ProviderQuotaResult> {
103
+ const { providerId, display, domain, credential, fetcher, context } = args;
104
+ const authState = typeof context?.auth_state === "object" && context?.auth_state !== null
105
+ ? (context.auth_state as Record<string, unknown>)
106
+ : undefined;
107
+ const enterpriseId = typeof authState?.enterpriseId === "string" ? authState.enterpriseId : undefined;
108
+ const clientVersion = providerId === "workbuddycn"
109
+ ? getWorkBuddyCnClientVersion()
110
+ : getWorkBuddyClientVersion();
111
+ // The reference bills the CN realm on `www.codebuddy.cn`, not the chat host:
112
+ // the billing realm is a different service from the one that serves chat,
113
+ // and the CN chat host answers the meter path with an HTML error page.
114
+ const billingBase = providerId === "workbuddycn" ? WORKBUDDY_CN_BILLING_BASE_URL : providerBaseUrl(providerId);
115
+ return fetchTencentBillingQuota({
116
+ source: providerId,
117
+ display,
118
+ url: `${billingBase}/v2/billing/meter/get-user-resource`,
119
+ headers: await billingHeaders(domain, clientVersion, enterpriseId),
120
+ credential,
121
+ fetcher,
122
+ defaultPlan: display,
123
+ uid: buddyAccountUid(credential),
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Real credit deductions for one WorkBuddy account.
129
+ *
130
+ * Mirrors {@link fetchWorkBuddyQuotaFor} in every respect but the endpoint —
131
+ * same billing host, same client-version identity headers, same account UID —
132
+ * because the usage-detail surface is the billing meter's other read.
133
+ */
134
+ export async function fetchWorkBuddyRequestUsage(
135
+ credential: string,
136
+ fetcher: FetchLike,
137
+ args: {
138
+ readonly providerId?: "workbuddy" | "workbuddycn";
139
+ readonly days?: number;
140
+ readonly context?: QuotaCollectionContext;
141
+ } = {},
142
+ ): Promise<BuddyUsageResult> {
143
+ const providerId = args.providerId ?? "workbuddy";
144
+ const domain = providerId === "workbuddycn" ? "www.workbuddy.cn" : WORKBUDDY_DOMAIN;
145
+ const authState = typeof args.context?.auth_state === "object" && args.context?.auth_state !== null
146
+ ? (args.context.auth_state as Record<string, unknown>)
147
+ : undefined;
148
+ const enterpriseId = typeof authState?.enterpriseId === "string" ? authState.enterpriseId : undefined;
149
+ const clientVersion = providerId === "workbuddycn"
150
+ ? getWorkBuddyCnClientVersion()
151
+ : getWorkBuddyClientVersion();
152
+ return fetchBuddyRequestUsage({
153
+ url: `${providerBaseUrl(providerId)}/billing/meter/get-user-request-usage`,
154
+ headers: await billingHeaders(domain, clientVersion, enterpriseId),
155
+ credential,
156
+ fetcher,
157
+ days: args.days ?? 30,
158
+ uid: buddyAccountUid(credential),
159
+ });
160
+ }
161
+
@@ -1,4 +1,4 @@
1
- import { createHash } from "node:crypto";
1
+ import { sha256Hex } from "../../../shared/hash";
2
2
  import { TtlCache } from "../../../persistence/ttl-cache";
3
3
  import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
4
4
  import { authCredential, getJson, percentWindow, record, text, number } from "../../quota/quota-contracts";
@@ -102,7 +102,7 @@ export async function fetchClaudeQuota(
102
102
  credential: string,
103
103
  fetcher: FetchLike,
104
104
  ): Promise<ProviderQuotaResult> {
105
- const key = createHash("sha256").update(credential).digest("hex");
105
+ const key = sha256Hex(credential);
106
106
  const cached = quotaCache.get(key);
107
107
  if (cached !== undefined) return cached;
108
108
  if (quotaCooldown.get(key) !== undefined) {
@@ -1,3 +1,5 @@
1
+ import { sha256Hex } from "../../../shared/hash";
2
+
1
3
  /**
2
4
  * Claude User Profiles REST client.
3
5
  *
@@ -32,7 +34,6 @@
32
34
  * (`claude-quota.ts:137-142`); **any future dispatch-time caller must catch and
33
35
  * continue**, because a profile lookup is metadata, not routing.
34
36
  */
35
- import { createHash } from "node:crypto";
36
37
  import { TtlCache } from "../../../persistence/ttl-cache";
37
38
  import { providerBaseUrl } from "../../provider-metadata";
38
39
  import type { FetchLike } from "../../quota/quota-contracts";
@@ -166,7 +167,7 @@ async function requestUserProfiles(
166
167
  }
167
168
 
168
169
  function credentialKey(credential: string): string {
169
- return createHash("sha256").update(credential).digest("hex");
170
+ return sha256Hex(credential);
170
171
  }
171
172
 
172
173
  /** Query string built exactly as the donor builds it (`:152-157`). */
@@ -484,14 +484,13 @@ export class ClaudeAdapter implements ProviderAdapter {
484
484
  type: "text",
485
485
  text: createClaudeBillingText(firstUserText(request), retryCliVersion),
486
486
  };
487
- const parsedBody = JSON.parse(requestBody) as Record<string, unknown>;
488
- const existingSystem = Array.isArray(parsedBody["system"])
489
- ? (parsedBody["system"] as unknown[])
487
+ const existingSystem = Array.isArray(payload["system"])
488
+ ? (payload["system"] as unknown[])
490
489
  : [];
491
490
  const retryBody = isClaudeCodeOAuth
492
491
  ? patchClaudeCchBody(
493
492
  JSON.stringify({
494
- ...parsedBody,
493
+ ...payload,
495
494
  system: [
496
495
  retryBilling,
497
496
  ...existingSystem.filter(
@@ -8,7 +8,6 @@ import { completeRequiredSchema, isRecord } from "../../../protocol/codecs";
8
8
  import { providerBaseUrl } from "../../provider-metadata";
9
9
  import { getCachedModelDiscovery } from "../../operations/model-discovery-cache";
10
10
  import { defineModel } from "../../provider-model-definition";
11
- import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
12
11
  import { clinePlatform, clinePlatformVersion } from "../../operations/cli-platform";
13
12
  import {
14
13
  getClineCliVersion,
@@ -502,25 +501,112 @@ export async function fetchClineFreeModelIds(
502
501
  }
503
502
 
504
503
  /**
505
- * Static seed for Cline's free tier.
504
+ * Static seed for Cline: the zero-cost tier and the Cline Pass roster.
506
505
  *
507
506
  * Every id here is materialized into the `models` table at boot with
508
507
  * `source: "builtin"`, and `seedBundledModels` deletes any builtin row this
509
508
  * list no longer declares. That makes the list the *owner* of these rows: an id
510
509
  * Cline retires from its roster keeps its catalog card — and its route — until
511
510
  * it is removed here, because nothing else prunes a builtin row. The seed is
512
- * therefore kept to ids Cline currently serves on the free tier; a stale entry
513
- * is a card that can only ever fail a probe.
511
+ * therefore kept to ids Cline currently serves; a stale entry is a card that
512
+ * can only ever fail a probe.
514
513
  *
515
514
  * Cline's live roster (`/ai/cline/recommended-models`, read by
516
515
  * `fetchClineRecommendedModels`) is the authority for what is routable right
517
516
  * now, and "Fetch models" reconciles against it. This list is the offline seed
518
517
  * that makes a fresh install usable before the first fetch.
518
+ *
519
+ * Both sections came from a live probe of that endpoint with a real key, not
520
+ * from the OpenRouter listing Cline is backed by — the two disagree, and the
521
+ * endpoint's own buckets are what the account can route. It is its own list
522
+ * rather than {@link openrouterFreeRoster}, which Kilo and OpenRouter still
523
+ * use: one list for three providers is right for a catalog fact and wrong for
524
+ * an account fact.
525
+ *
526
+ * This is a floor, and live discovery still finds every id the account can
527
+ * route — including any absent here. A manual fetch is unaffected by what this
528
+ * list declares.
529
+ */
530
+ interface ClineSeedRow {
531
+ readonly id: string;
532
+ readonly ctx: number;
533
+ readonly out: number;
534
+ readonly vision?: boolean;
535
+ }
536
+
537
+ function clineRow(
538
+ rows: readonly ClineSeedRow[],
539
+ extra: { free?: boolean; proOnly?: boolean },
540
+ ): readonly ModelDefinition[] {
541
+ return rows.map((row) =>
542
+ defineModel({
543
+ id: row.id,
544
+ providerId: CLINE_PROVIDER_ID,
545
+ wireFamily: "chat",
546
+ endpoint: CLINE_CHAT_PATH,
547
+ ctx: row.ctx,
548
+ out: row.out,
549
+ vision: row.vision ?? false,
550
+ reasoning: true,
551
+ toolCall: true,
552
+ ...(extra.free === true ? { free: true, freeTier: true } : {}),
553
+ ...(extra.proOnly === true ? { proOnly: true } : {}),
554
+ }),
555
+ );
556
+ }
557
+
558
+ /**
559
+ * The zero-cost tier, exactly as this account's `free` bucket publishes it.
560
+ *
561
+ * Probed against `/ai/cline/recommended-models` with a live key, not inferred
562
+ * from the OpenRouter listing. That matters because the two disagree: the
563
+ * `/models` listing names 17 `:free` ids, while the account's own `free` bucket
564
+ * names 5 and shares none of them. The listing is the upstream catalog; the
565
+ * bucket is what this account can actually route, and only the second is a
566
+ * fact worth seeding.
567
+ *
568
+ * Two ids carry the `cline-free/` prefix. That prefix is Cline's own namespace,
569
+ * not a tier marker — membership of the bucket is what makes a row free.
519
570
  */
520
- export const CLINE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
521
- providerId: CLINE_PROVIDER_ID,
522
- endpoint: CLINE_CHAT_PATH,
523
- });
571
+ const CLINE_FREE_ROWS: readonly ClineSeedRow[] = [
572
+ { id: "reflection/beam-free", ctx: 128_000, out: 32_000 },
573
+ { id: "cline-free/nemotron-3.5-super-vl", ctx: 1_000_000, out: 131_072, vision: true },
574
+ { id: "nvidia/nemotron-3.5-lightning-free", ctx: 1_000_000, out: 65_536 },
575
+ { id: "poolside/laguna-s-2.1-free", ctx: 262_144, out: 32_768 },
576
+ { id: "cline-free/solar-mini4", ctx: 524_288, out: 131_072 },
577
+ ];
578
+
579
+ /**
580
+ * The Cline Pass subscription roster, as the account's `clinePass` bucket
581
+ * publishes it.
582
+ *
583
+ * Seeded with `proOnly`, matching how the buddy roster marks a row a free plan
584
+ * is refused: dispatching one without a subscription 401s, so the card should
585
+ * say so rather than fail at request time. Discovery reads `clineAccountHasPass`
586
+ * first and only asks for these rows when the account has one.
587
+ */
588
+ const CLINE_PASS_ROWS: readonly ClineSeedRow[] = [
589
+ { id: "cline-pass/kimi-k3.1-pro", ctx: 1_000_000, out: 32_000 },
590
+ { id: "cline-pass/minimax-m3.1-flash", ctx: 1_000_000, out: 128_000 },
591
+ { id: "cline-pass/deepseek-v4.1-flash-1017", ctx: 1_000_000, out: 128_000 },
592
+ { id: "cline-pass/deepseek-v4.1-flash", ctx: 1_000_000, out: 128_000 },
593
+ { id: "cline-pass/mimo-v2.6-flash", ctx: 1_000_000, out: 128_000 },
594
+ { id: "cline-pass/mimo-v2.6-pro", ctx: 1_000_000, out: 128_000 },
595
+ { id: "cline-pass/glm-5.3", ctx: 1_000_000, out: 64_000 },
596
+ { id: "cline-pass/deepseek-v4-pro", ctx: 1_000_000, out: 128_000 },
597
+ { id: "cline-pass/qwen3.8-max", ctx: 1_000_000, out: 65_536 },
598
+ { id: "cline-pass/muse-spark-1.3-contributor", ctx: 1_000_000, out: 65_536 },
599
+ { id: "cline-pass/kimi-k3", ctx: 1_000_000, out: 32_000 },
600
+ { id: "cline-pass/glm-5.3-flash", ctx: 1_000_000, out: 32_000 },
601
+ { id: "cline-pass/qwen3.7-plus", ctx: 1_000_000, out: 65_536 },
602
+ { id: "cline-pass/qwen3.7-max", ctx: 1_000_000, out: 65_536 },
603
+ { id: "cline-pass/minimax-m3", ctx: 512_000, out: 64_000 },
604
+ ];
605
+
606
+ export const CLINE_MODELS: readonly ModelDefinition[] = [
607
+ ...clineRow(CLINE_FREE_ROWS, { free: true }),
608
+ ...clineRow(CLINE_PASS_ROWS, { proOnly: true }),
609
+ ];
524
610
 
525
611
 
526
612
  export function createClineAdapter(fetchImpl?: typeof fetch): ProviderAdapter {
@@ -369,16 +369,21 @@ function authHeadersForCodex(
369
369
  }
370
370
 
371
371
  /**
372
- * Path of the Codex model registry, relative to the ChatGPT backend root.
372
+ * Paths of the Codex model registry, in the order they are tried.
373
373
  *
374
- * Donor `packages/catalog/src/discovery/codex.ts:9` tries `/codex/models` first
375
- * and `/models` second. Only the first path is used here: it is the one the
376
- * donor's own tests assert against
377
- * (`packages/coding-agent/test/model-discovery.test.ts:568,598`), and the
378
- * fallback exists upstream to accommodate relay base URLs this adapter does not
379
- * support.
374
+ * The donor tries `/codex/models` first and `/models` second
375
+ * (`packages/catalog/src/discovery/codex.ts:9`), both relative to the
376
+ * `https://chatgpt.com/backend-api` root (`packages/catalog/src/wire/codex.ts:5`)
377
+ * — so the first candidate is the same URL this file always used.
378
+ *
379
+ * The fallback used to be omitted on the ground that it exists upstream only
380
+ * for relay base URLs this adapter does not support. It is restored because
381
+ * fetching models failed outright with no listing endpoint reported: a single
382
+ * candidate means one upstream shape change removes discovery entirely, while
383
+ * trying both costs one extra request only when the first misses.
380
384
  */
381
- const CODEX_MODELS_PATH = "/backend-api/codex/models";
385
+ const CODEX_MODELS_PATHS = ["/backend-api/codex/models", "/backend-api/models"] as const;
386
+ const CODEX_MODELS_PATH = CODEX_MODELS_PATHS[0];
382
387
 
383
388
  /**
384
389
  * Shapes a client version may take for the `client_version` query parameter.
@@ -410,7 +415,7 @@ const CODEX_CLIENT_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
410
415
  */
411
416
  export function buildCodexModelsUrl(
412
417
  baseUrl: string,
413
- path = CODEX_MODELS_PATH,
418
+ path: string = CODEX_MODELS_PATH,
414
419
  clientVersion = getCodexVersion(),
415
420
  ): string {
416
421
  const root = baseUrl.replace(/\/+$/, "");
@@ -471,39 +476,58 @@ export async function discoverCodexModels(input: {
471
476
  ? {}
472
477
  : { "x-openai-internal-codex-residency": residency }),
473
478
  };
479
+ const fetcher = input.fetcher ?? globalThis.fetch;
480
+ const root = input.baseUrl ?? DEFAULT_CHATGPT_BASE_URL;
481
+ let entries: readonly unknown[] | undefined;
474
482
  try {
475
- const response = await (input.fetcher ?? globalThis.fetch)(
476
- buildCodexModelsUrl(input.baseUrl ?? DEFAULT_CHATGPT_BASE_URL, CODEX_MODELS_PATH, version),
477
- {
478
- method: "GET",
479
- headers,
480
- ...(input.signal === undefined ? {} : { signal: input.signal }),
481
- },
482
- );
483
- if (!response.ok) return null;
484
- const payload = (await response.json()) as unknown;
485
- const entries = boundedUpstreamArray(isRecord(payload) ? payload["models"] : undefined);
486
- if (entries === undefined) return null;
487
- const byId = new Map<string, ModelDefinition>();
488
- for (const entry of entries) {
489
- if (!isRecord(entry)) continue;
490
- const id = sanitizeUpstreamLabel(entry["slug"] ?? entry["id"]);
491
- if (id === undefined) continue;
492
- const contextLimit = boundedUpstreamNumber(entry["context_window"], { min: 1, max: 1_000_000_000 });
493
- const outputLimit = boundedUpstreamNumber(entry["max_output_tokens"], { min: 1, max: 1_000_000_000 });
494
- byId.set(
495
- id,
496
- codexResponsesModel(
497
- id,
498
- contextLimit ?? DEFAULT_CODEX_DISCOVERY_CONTEXT_LIMIT,
499
- outputLimit ?? DEFAULT_CODEX_DISCOVERY_OUTPUT_LIMIT,
500
- ),
501
- );
483
+ // Each candidate is tried in order and the first usable roster wins. The
484
+ // loop is what the donor does (`packages/catalog/src/discovery/codex.ts:138`);
485
+ // a non-200 or an unparseable body just advances to the next path, and
486
+ // exhausting them is the same "no roster" outcome as before.
487
+ for (const path of CODEX_MODELS_PATHS) {
488
+ let response: Response;
489
+ try {
490
+ response = await fetcher(buildCodexModelsUrl(root, path, version), {
491
+ method: "GET",
492
+ headers,
493
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
494
+ });
495
+ } catch {
496
+ continue;
497
+ }
498
+ if (!response.ok) continue;
499
+ let payload: unknown;
500
+ try {
501
+ payload = (await response.json()) as unknown;
502
+ } catch {
503
+ continue;
504
+ }
505
+ const found = boundedUpstreamArray(isRecord(payload) ? payload["models"] : undefined);
506
+ if (found === undefined) continue;
507
+ entries = found;
508
+ break;
502
509
  }
503
- return [...byId.values()].sort((a, b) => a.modelId.localeCompare(b.modelId));
504
510
  } catch {
505
511
  return null;
506
512
  }
513
+ if (entries === undefined) return null;
514
+ const byId = new Map<string, ModelDefinition>();
515
+ for (const entry of entries) {
516
+ if (!isRecord(entry)) continue;
517
+ const id = sanitizeUpstreamLabel(entry["slug"] ?? entry["id"]);
518
+ if (id === undefined) continue;
519
+ const contextLimit = boundedUpstreamNumber(entry["context_window"], { min: 1, max: 1_000_000_000 });
520
+ const outputLimit = boundedUpstreamNumber(entry["max_output_tokens"], { min: 1, max: 1_000_000_000 });
521
+ byId.set(
522
+ id,
523
+ codexResponsesModel(
524
+ id,
525
+ contextLimit ?? DEFAULT_CODEX_DISCOVERY_CONTEXT_LIMIT,
526
+ outputLimit ?? DEFAULT_CODEX_DISCOVERY_OUTPUT_LIMIT,
527
+ ),
528
+ );
529
+ }
530
+ return [...byId.values()].sort((a, b) => a.modelId.localeCompare(b.modelId));
507
531
  }
508
532
 
509
533
  /** Reads just enough of an untyped body to tell SSE from JSON, then replays it. */