cartethyia 2.5.9 → 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 (386) hide show
  1. package/.env.example +15 -42
  2. package/README.md +11 -10
  3. package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  27. package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
  28. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
  38. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  39. package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
  40. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  41. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  42. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  43. package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
  44. package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  46. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  48. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
  50. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
  52. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
  54. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
  60. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
  62. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  66. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  68. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
  72. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
  74. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
  76. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  77. package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
  78. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
  80. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  82. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  86. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  92. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
  94. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  96. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
  98. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
  100. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
  102. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  105. package/dist/dashboard/index.html +1 -1
  106. package/dist/dashboard/index.html.gz +0 -0
  107. package/dist/dashboard/precompressed-assets.json +51 -51
  108. package/migrations/README.md +52 -52
  109. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  110. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  111. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  112. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  113. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  114. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  115. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  116. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  117. package/package.json +96 -97
  118. package/scripts/cli/admin.ts +126 -125
  119. package/scripts/cli/autostart.ts +237 -237
  120. package/scripts/cli/background.ts +120 -110
  121. package/scripts/cli/bin/carte.cjs +8 -8
  122. package/scripts/cli/bin/cartethyia.cjs +167 -167
  123. package/scripts/cli/console-menu.ts +308 -275
  124. package/scripts/cli/controller.ts +145 -0
  125. package/scripts/cli/doctor.ts +1 -1
  126. package/scripts/cli/env.ts +99 -99
  127. package/scripts/cli/gateway.ts +264 -261
  128. package/scripts/{health.ts → cli/health.ts} +4 -4
  129. package/scripts/cli/instance.ts +234 -224
  130. package/scripts/cli/main.ts +188 -164
  131. package/scripts/cli/platform.ts +64 -64
  132. package/scripts/cli/prompt.ts +275 -107
  133. package/scripts/cli/services.ts +88 -88
  134. package/scripts/cli/setup.ts +445 -445
  135. package/scripts/cli/start.ts +85 -82
  136. package/scripts/cli/startup-signal.ts +23 -23
  137. package/scripts/cli/telemetry.ts +70 -0
  138. package/scripts/cli/tray/index.ts +263 -263
  139. package/scripts/cli/tray/windows.ps1 +107 -107
  140. package/scripts/cli/tray.ts +192 -208
  141. package/scripts/cli/update.ts +39 -39
  142. package/scripts/cli/version.ts +67 -67
  143. package/scripts/cli/wordmark.ts +58 -58
  144. package/src/boot/app.ts +16 -1
  145. package/src/boot/dependencies.ts +128 -25
  146. package/src/config/package-paths.ts +24 -0
  147. package/src/config/resources.ts +137 -0
  148. package/src/config/runtime.ts +133 -76
  149. package/src/config/spec.ts +30 -29
  150. package/src/config/telemetry.ts +0 -25
  151. package/src/console/api-keys/bucket-spend.ts +14 -17
  152. package/src/console/api-keys/contracts.ts +410 -410
  153. package/src/console/api-keys/routes.ts +12 -0
  154. package/src/console/audit/store.ts +2 -3
  155. package/src/console/auth/setup-service.ts +8 -1
  156. package/src/console/backup/routes.ts +7 -0
  157. package/src/console/backup/service.ts +463 -463
  158. package/src/console/backup/store.ts +16 -11
  159. package/src/console/backup/validate.ts +1 -4
  160. package/src/console/cli-tools/fs-ops.ts +18 -2
  161. package/src/console/data-management/contracts.ts +1 -8
  162. package/src/console/data-management/operations.ts +61 -48
  163. package/src/console/data-management/routes.ts +4 -2
  164. package/src/console/domain-registration.ts +25 -3
  165. package/src/console/inferhub/client.ts +139 -0
  166. package/src/console/inferhub/key-store.ts +129 -0
  167. package/src/console/inferhub/normalize.ts +347 -0
  168. package/src/console/inferhub/routes.ts +411 -0
  169. package/src/console/inferhub/store.ts +109 -0
  170. package/src/console/inferhub/types.ts +183 -0
  171. package/src/console/observability/contracts.ts +2 -41
  172. package/src/console/observability/store.ts +176 -371
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/store.ts +23 -36
  175. package/src/console/quota/account-quota-shared.ts +52 -52
  176. package/src/console/quota/account-quota-tenant.ts +16 -24
  177. package/src/console/quota/refresh.ts +441 -441
  178. package/src/console/remote-support/service.ts +270 -114
  179. package/src/console/routing/pools/import-parse.ts +206 -206
  180. package/src/console/routing/pools/operations.ts +543 -543
  181. package/src/console/routing/pools/probe-verdict.ts +132 -132
  182. package/src/console/routing/pools/routes.ts +181 -181
  183. package/src/console/routing/pools/store.ts +1 -1
  184. package/src/console/settings/contracts.ts +42 -40
  185. package/src/console/settings/store.ts +25 -25
  186. package/src/console/share/share-stats.ts +22 -4
  187. package/src/console/share/share-usage.ts +30 -4
  188. package/src/console/shared/cursor.ts +15 -0
  189. package/src/console/updates/repository-updates.ts +40 -39
  190. package/src/console/updates/routes.ts +12 -0
  191. package/src/main.ts +13 -5
  192. package/src/network/pool/resolver.ts +1104 -1104
  193. package/src/observability/client-name.ts +22 -0
  194. package/src/observability/event-log.ts +5 -29
  195. package/src/observability/event-query.ts +690 -0
  196. package/src/observability/log-ring.ts +26 -2
  197. package/src/observability/log-sanitize.ts +118 -0
  198. package/src/observability/logger.ts +17 -13
  199. package/src/observability/metrics.ts +35 -0
  200. package/src/observability/payload-capture.ts +114 -41
  201. package/src/observability/payload-store.ts +249 -24
  202. package/src/observability/raw-event-writer.ts +118 -0
  203. package/src/observability/runtime-metrics.ts +71 -1
  204. package/src/observability/telemetry-buffer.ts +100 -17
  205. package/src/observability/telemetry-reconciliation.ts +417 -0
  206. package/src/persistence/boot.ts +20 -9
  207. package/src/persistence/db-pg.ts +13 -0
  208. package/src/persistence/db-sqlite.ts +569 -497
  209. package/src/persistence/dialect.ts +3 -2
  210. package/src/persistence/migrate.ts +181 -181
  211. package/src/persistence/schema.ts +1496 -1406
  212. package/src/persistence/shared-cache.ts +5 -1
  213. package/src/persistence/stores/api-key-store.ts +581 -581
  214. package/src/persistence/stores/telemetry-store.ts +124 -63
  215. package/src/protocol/reasoning-text.ts +30 -0
  216. package/src/protocol/registry.ts +2 -0
  217. package/src/protocol/response/chat.ts +33 -7
  218. package/src/protocol/response/codex.ts +14 -2
  219. package/src/protocol/response/messages.ts +5 -2
  220. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  221. package/src/providers/bundled-discovery-providers.ts +122 -122
  222. package/src/providers/default-registry.ts +14 -0
  223. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  224. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  225. package/src/providers/discovery/probe-phases.ts +43 -7
  226. package/src/providers/discovery/probing-service.ts +70 -32
  227. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  228. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  231. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  232. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  233. package/src/providers/integrations/buddy/capabilities.ts +4 -0
  234. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  235. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  236. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  237. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  238. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  239. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  240. package/src/providers/integrations/claude/claude.ts +3 -4
  241. package/src/providers/integrations/cline/cline.ts +94 -8
  242. package/src/providers/integrations/codex/codex.ts +61 -37
  243. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  244. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  245. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  246. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  247. package/src/providers/integrations/cursor/cursor.ts +29 -17
  248. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  249. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  250. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  251. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  252. package/src/providers/integrations/github/github.ts +3 -1
  253. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  254. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  255. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  256. package/src/providers/integrations/hermes.ts +7 -1
  257. package/src/providers/integrations/inferhub.ts +201 -73
  258. package/src/providers/integrations/kilo/kilo.ts +152 -152
  259. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  260. package/src/providers/integrations/opencode/provider.ts +2 -0
  261. package/src/providers/integrations/openrouter/provider.ts +129 -129
  262. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  263. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  264. package/src/providers/integrations/trae/trae-headers.ts +17 -48
  265. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  266. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  267. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  268. package/src/providers/integrations/trae/trae.ts +22 -24
  269. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  270. package/src/providers/integrations/zcode/oauth.ts +1 -1
  271. package/src/providers/operations/account-health-service.ts +605 -279
  272. package/src/providers/provider-metadata.ts +12 -1
  273. package/src/providers/provider-model-definition.ts +373 -373
  274. package/src/providers/provider-probe.ts +3 -0
  275. package/src/providers/quota/quota-contracts.ts +3 -7
  276. package/src/providers/search/search-provider.ts +2 -6
  277. package/src/security/admission/contracts.ts +10 -0
  278. package/src/security/admission/in-memory-store.ts +89 -14
  279. package/src/security/admission/lua.ts +191 -182
  280. package/src/security/admission/redis-store.ts +30 -0
  281. package/src/security/admission/service.ts +18 -0
  282. package/src/security/api-key-auth-cache.ts +96 -0
  283. package/src/security/api-key-auth.ts +11 -100
  284. package/src/security/client-router-fingerprint.ts +3 -7
  285. package/src/security/ip-boundary.ts +7 -3
  286. package/src/shared/coerce.ts +67 -0
  287. package/src/shared/hash.ts +6 -0
  288. package/src/shared/time.ts +7 -0
  289. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  290. package/src/transport/dispatch/attempt-loop.ts +103 -81
  291. package/src/transport/dispatch/capture-policy.ts +78 -0
  292. package/src/transport/dispatch/proxy-request.ts +4 -2
  293. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  294. package/src/transport/dispatch/streaming/stream.ts +19 -2
  295. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  296. package/src/transport/failure-policy.ts +27 -0
  297. package/src/transport/guards/abuse-guard.ts +2 -2
  298. package/src/transport/guards/auth-guard.ts +17 -0
  299. package/src/transport/guards/body-policy.ts +3 -2
  300. package/src/transport/guards/concurrency-guard.ts +89 -13
  301. package/src/transport/guards/early-rejection-capture.ts +43 -3
  302. package/src/transport/guards/error-lifecycle.ts +10 -2
  303. package/src/transport/guards/guard-errors.ts +23 -0
  304. package/src/transport/guards/headroom-guard.ts +41 -7
  305. package/src/transport/guards/pipeline.ts +24 -0
  306. package/src/transport/guards/request-context.ts +2 -1
  307. package/src/transport/routing/admission.ts +51 -9
  308. package/src/transport/routing/bounded-lru.ts +49 -0
  309. package/src/transport/routing/candidate-index.ts +88 -0
  310. package/src/transport/routing/reservations.ts +121 -7
  311. package/src/transport/routing/round-robin.ts +10 -22
  312. package/src/transport/routing/route-catalog.ts +26 -13
  313. package/src/transport/routing/route-model.ts +22 -2
  314. package/src/transport/routing/router.ts +53 -31
  315. package/src/transport/surface/chat/encode.ts +2 -3
  316. package/src/transport/surface/chat/parse.ts +2 -1
  317. package/src/transport/surface/dialects.ts +11 -3
  318. package/src/transport/surface/messages/encode.ts +2 -1
  319. package/src/transport/surface/messages/parse.ts +3 -1
  320. package/src/transport/surface/messages/stream.ts +2 -6
  321. package/src/workers/maintenance-lease.ts +200 -0
  322. package/src/workers/scheduler.ts +101 -1
  323. package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
  324. package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
  325. package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
  326. package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
  327. package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
  328. package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
  329. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
  330. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
  331. package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
  332. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  333. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
  334. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
  335. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
  336. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
  337. package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
  338. package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
  339. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
  340. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
  341. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
  342. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
  343. package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
  344. package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
  345. package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
  346. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  347. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  348. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  349. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  350. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  351. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  352. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  353. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  354. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  355. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  356. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  357. package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
  358. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
  360. package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
  361. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  363. package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  365. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  367. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  368. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  369. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  370. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  371. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  372. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  373. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  374. package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
  375. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  376. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  377. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  378. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  379. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  380. package/scripts/build/aot.ts +0 -143
  381. package/scripts/build/build.ts +0 -24
  382. package/scripts/build/compile.ts +0 -88
  383. package/scripts/build/restart.ts +0 -120
  384. package/scripts/build/start.ts +0 -25
  385. package/src/observability/event-compaction.ts +0 -317
  386. package/src/observability/event-day-index.ts +0 -440
@@ -1,152 +1,152 @@
1
- /**
2
- * Kilo Code adapter.
3
- *
4
- * Kilo Code resells the OpenRouter catalog behind a bearer device token on an
5
- * OpenAI-compatible chat surface, so this adapter reuses the shared factory for
6
- * payload translation, transport, and SSE decoding.
7
- *
8
- * One thing sits outside the declarative `ApiKeyProviderSpec`: the stored
9
- * credential is a JSON envelope (`{ accessToken, orgId }`) because the gateway
10
- * scopes every request to the account's organization and the console stores one
11
- * opaque string per account. The factory forwards that string as the bearer
12
- * verbatim, so the envelope is decoded at the auth boundary here — the same
13
- * arrangement `kimi/kimi.ts` uses for its own envelope credential.
14
- *
15
- * The base URL is the completions path itself, because that is the path Kilo
16
- * Code publishes (`/api/openrouter/chat/completions`) and it is not derivable
17
- * from an origin plus a wire-family suffix.
18
- */
19
- import { defineModel } from "../../provider-model-definition";
20
- import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
21
- import type { ModelDefinition } from "../../provider-model-definition";
22
- import type { ProviderAdapter, ProviderDispatchContext, ProviderDispatchTarget } from "../../provider-contracts";
23
- import type { CanonicalRequest } from "../../../protocol/canonical-model";
24
- import { OpenAICompatibleAdapter, withBearerAuthentication } from "../../adapters/compatible-adapter";
25
- import { providerBaseUrl } from "../../provider-metadata";
26
- import { decodeKiloCredential } from "../../../security/credential-codec";
27
-
28
- export const KILO_PROVIDER_ID = "kilo" as const;
29
- export const KILO_BASE_URL = providerBaseUrl("kilo");
30
-
31
- /**
32
- * Endpoint path recorded on this provider's model rows and used by the adapter.
33
- *
34
- * The base URL carries Kilo Code's API prefix (`/api/openrouter`), so this is
35
- * the wire suffix that completes it — the same split Cline uses for
36
- * `/api/v1` + `/chat/completions`.
37
- */
38
- export const KILO_ENDPOINT_PATH = "/chat/completions" as const;
39
-
40
- interface KiloModelOptions {
41
- readonly vision?: boolean;
42
- readonly document?: boolean;
43
- readonly audio?: boolean;
44
- readonly reasoning?: boolean;
45
- readonly toolCall?: boolean;
46
- }
47
-
48
- function kiloModel(
49
- modelId: string,
50
- contextLimit: number,
51
- outputLimit: number,
52
- options: KiloModelOptions = {},
53
- ): ModelDefinition {
54
- return defineModel({
55
- id: modelId,
56
- providerId: KILO_PROVIDER_ID,
57
- wireFamily: "chat",
58
- endpoint: KILO_ENDPOINT_PATH,
59
- ctx: contextLimit,
60
- out: outputLimit,
61
- vision: options.vision ?? false,
62
- document: options.document ?? false,
63
- audio: options.audio ?? false,
64
- reasoning: options.reasoning ?? false,
65
- toolCall: options.toolCall ?? true,
66
- });
67
- }
68
-
69
- /**
70
- * Static fallback catalog, read from the live `/api/gateway/models` directory.
71
- *
72
- * Live discovery replaces this list whenever an account is available, so these
73
- * rows are the offline floor: the gateway's own routing aliases plus one
74
- * current model per family it resells. Limits and capability come from the
75
- * directory entry's own `top_provider` / `architecture` /
76
- * `supported_parameters` fields rather than from the base catalog, which prices
77
- * the underlying model and not this reseller's serving.
78
- */
79
- export const KILO_MODELS: readonly ModelDefinition[] = [
80
- // Three routers, and nothing else. A committed catalog of specific ids was a
81
- // snapshot of one afternoon: this provider resells the OpenRouter catalog,
82
- // which adds and pulls models within days, so discovery is the authority and
83
- // every hand-kept row outside these drifts out of truth.
84
- //
85
- // `kilo-auto/free` bills zero and `kilo-auto/small` bills a fraction of a
86
- // cent; both route on the provider's own side, so their limits are the
87
- // window the router may select into rather than one model's.
88
- kiloModel("kilo-auto/free", 256_000, 32_768, { reasoning: true }),
89
- kiloModel("kilo-auto/small", 262_144, 32_768, { vision: true, reasoning: true }),
90
- ];
91
-
92
- /**
93
- * The shared free roster, spelled for this provider's endpoint.
94
- *
95
- * The same 17 `:free` ids Cline and OpenRouter serve, because this is the same
96
- * catalog behind a different origin. Kept separate from `KILO_MODELS` so the
97
- * free tier is its own statement: those three rows are routers, these are the
98
- * zero-cost models, and the model list groups them differently.
99
- */
100
- export const KILO_FREE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
101
- providerId: KILO_PROVIDER_ID,
102
- endpoint: KILO_ENDPOINT_PATH,
103
- });
104
-
105
- /**
106
- * Adapter that decodes the credential envelope before the shared pipeline
107
- * authenticates the request.
108
- *
109
- * `prepareHeaders` is corrected rather than rebuilt: every other header the
110
- * factory contributes (content type, gateway identity, operator custom headers,
111
- * route user-agent) stays exactly as every other provider gets it.
112
- */
113
- class KiloAdapter extends OpenAICompatibleAdapter {
114
- constructor(fetchImpl?: typeof fetch) {
115
- super(
116
- withBearerAuthentication({
117
- provider_id: KILO_PROVIDER_ID,
118
- base_url: KILO_BASE_URL,
119
- endpoint_paths_by_wire_family: { chat: KILO_ENDPOINT_PATH },
120
- ...(fetchImpl ? { fetchImpl } : {}),
121
- }),
122
- );
123
- }
124
-
125
- protected override async prepareHeaders(
126
- context: ProviderDispatchContext,
127
- request: CanonicalRequest,
128
- candidate: ProviderDispatchTarget,
129
- ): Promise<Record<string, string>> {
130
- const headers = await super.prepareHeaders(context, request, candidate);
131
- const secret = context.credential.secret;
132
- if (secret === undefined || secret.length === 0) return headers;
133
- // A credential that does not decode is left as the factory sent it: the
134
- // request then fails closed at the upstream auth boundary, and the parse
135
- // error belongs to the credential boundary rather than to this hook.
136
- let credential: ReturnType<typeof decodeKiloCredential>;
137
- try {
138
- credential = decodeKiloCredential(secret);
139
- } catch {
140
- return headers;
141
- }
142
- headers.authorization = `Bearer ${credential.accessToken}`;
143
- // The upstream's own header name, not ours: renaming the Cartethyia
144
- // provider id does not rename a field the upstream reads.
145
- if (credential.orgId !== undefined) headers["X-Kilocode-OrganizationID"] = credential.orgId;
146
- return headers;
147
- }
148
- }
149
-
150
- /** Kilo Code provider adapter. */
151
- export const kiloAdapter: ProviderAdapter = new KiloAdapter();
152
-
1
+ /**
2
+ * Kilo Code adapter.
3
+ *
4
+ * Kilo Code resells the OpenRouter catalog behind a bearer device token on an
5
+ * OpenAI-compatible chat surface, so this adapter reuses the shared factory for
6
+ * payload translation, transport, and SSE decoding.
7
+ *
8
+ * One thing sits outside the declarative `ApiKeyProviderSpec`: the stored
9
+ * credential is a JSON envelope (`{ accessToken, orgId }`) because the gateway
10
+ * scopes every request to the account's organization and the console stores one
11
+ * opaque string per account. The factory forwards that string as the bearer
12
+ * verbatim, so the envelope is decoded at the auth boundary here — the same
13
+ * arrangement `kimi/kimi.ts` uses for its own envelope credential.
14
+ *
15
+ * The base URL is the completions path itself, because that is the path Kilo
16
+ * Code publishes (`/api/openrouter/chat/completions`) and it is not derivable
17
+ * from an origin plus a wire-family suffix.
18
+ */
19
+ import { defineModel } from "../../provider-model-definition";
20
+ import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
21
+ import type { ModelDefinition } from "../../provider-model-definition";
22
+ import type { ProviderAdapter, ProviderDispatchContext, ProviderDispatchTarget } from "../../provider-contracts";
23
+ import type { CanonicalRequest } from "../../../protocol/canonical-model";
24
+ import { OpenAICompatibleAdapter, withBearerAuthentication } from "../../adapters/compatible-adapter";
25
+ import { providerBaseUrl } from "../../provider-metadata";
26
+ import { decodeKiloCredential } from "../../../security/credential-codec";
27
+
28
+ export const KILO_PROVIDER_ID = "kilo" as const;
29
+ export const KILO_BASE_URL = providerBaseUrl("kilo");
30
+
31
+ /**
32
+ * Endpoint path recorded on this provider's model rows and used by the adapter.
33
+ *
34
+ * The base URL carries Kilo Code's API prefix (`/api/openrouter`), so this is
35
+ * the wire suffix that completes it — the same split Cline uses for
36
+ * `/api/v1` + `/chat/completions`.
37
+ */
38
+ export const KILO_ENDPOINT_PATH = "/chat/completions" as const;
39
+
40
+ interface KiloModelOptions {
41
+ readonly vision?: boolean;
42
+ readonly document?: boolean;
43
+ readonly audio?: boolean;
44
+ readonly reasoning?: boolean;
45
+ readonly toolCall?: boolean;
46
+ }
47
+
48
+ function kiloModel(
49
+ modelId: string,
50
+ contextLimit: number,
51
+ outputLimit: number,
52
+ options: KiloModelOptions = {},
53
+ ): ModelDefinition {
54
+ return defineModel({
55
+ id: modelId,
56
+ providerId: KILO_PROVIDER_ID,
57
+ wireFamily: "chat",
58
+ endpoint: KILO_ENDPOINT_PATH,
59
+ ctx: contextLimit,
60
+ out: outputLimit,
61
+ vision: options.vision ?? false,
62
+ document: options.document ?? false,
63
+ audio: options.audio ?? false,
64
+ reasoning: options.reasoning ?? false,
65
+ toolCall: options.toolCall ?? true,
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Static fallback catalog, read from the live `/api/gateway/models` directory.
71
+ *
72
+ * Live discovery replaces this list whenever an account is available, so these
73
+ * rows are the offline floor: the gateway's own routing aliases plus one
74
+ * current model per family it resells. Limits and capability come from the
75
+ * directory entry's own `top_provider` / `architecture` /
76
+ * `supported_parameters` fields rather than from the base catalog, which prices
77
+ * the underlying model and not this reseller's serving.
78
+ */
79
+ export const KILO_MODELS: readonly ModelDefinition[] = [
80
+ // Three routers, and nothing else. A committed catalog of specific ids was a
81
+ // snapshot of one afternoon: this provider resells the OpenRouter catalog,
82
+ // which adds and pulls models within days, so discovery is the authority and
83
+ // every hand-kept row outside these drifts out of truth.
84
+ //
85
+ // `kilo-auto/free` bills zero and `kilo-auto/small` bills a fraction of a
86
+ // cent; both route on the provider's own side, so their limits are the
87
+ // window the router may select into rather than one model's.
88
+ kiloModel("kilo-auto/free", 256_000, 32_768, { reasoning: true }),
89
+ kiloModel("kilo-auto/small", 262_144, 32_768, { vision: true, reasoning: true }),
90
+ ];
91
+
92
+ /**
93
+ * The shared free roster, spelled for this provider's endpoint.
94
+ *
95
+ * The same 17 `:free` ids Cline and OpenRouter serve, because this is the same
96
+ * catalog behind a different origin. Kept separate from `KILO_MODELS` so the
97
+ * free tier is its own statement: those three rows are routers, these are the
98
+ * zero-cost models, and the model list groups them differently.
99
+ */
100
+ export const KILO_FREE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
101
+ providerId: KILO_PROVIDER_ID,
102
+ endpoint: KILO_ENDPOINT_PATH,
103
+ });
104
+
105
+ /**
106
+ * Adapter that decodes the credential envelope before the shared pipeline
107
+ * authenticates the request.
108
+ *
109
+ * `prepareHeaders` is corrected rather than rebuilt: every other header the
110
+ * factory contributes (content type, gateway identity, operator custom headers,
111
+ * route user-agent) stays exactly as every other provider gets it.
112
+ */
113
+ class KiloAdapter extends OpenAICompatibleAdapter {
114
+ constructor(fetchImpl?: typeof fetch) {
115
+ super(
116
+ withBearerAuthentication({
117
+ provider_id: KILO_PROVIDER_ID,
118
+ base_url: KILO_BASE_URL,
119
+ endpoint_paths_by_wire_family: { chat: KILO_ENDPOINT_PATH },
120
+ ...(fetchImpl ? { fetchImpl } : {}),
121
+ }),
122
+ );
123
+ }
124
+
125
+ protected override async prepareHeaders(
126
+ context: ProviderDispatchContext,
127
+ request: CanonicalRequest,
128
+ candidate: ProviderDispatchTarget,
129
+ ): Promise<Record<string, string>> {
130
+ const headers = await super.prepareHeaders(context, request, candidate);
131
+ const secret = context.credential.secret;
132
+ if (secret === undefined || secret.length === 0) return headers;
133
+ // A credential that does not decode is left as the factory sent it: the
134
+ // request then fails closed at the upstream auth boundary, and the parse
135
+ // error belongs to the credential boundary rather than to this hook.
136
+ let credential: ReturnType<typeof decodeKiloCredential>;
137
+ try {
138
+ credential = decodeKiloCredential(secret);
139
+ } catch {
140
+ return headers;
141
+ }
142
+ headers.authorization = `Bearer ${credential.accessToken}`;
143
+ // The upstream's own header name, not ours: renaming the Cartethyia
144
+ // provider id does not rename a field the upstream reads.
145
+ if (credential.orgId !== undefined) headers["X-Kilocode-OrganizationID"] = credential.orgId;
146
+ return headers;
147
+ }
148
+ }
149
+
150
+ /** Kilo Code provider adapter. */
151
+ export const kiloAdapter: ProviderAdapter = new KiloAdapter();
152
+
@@ -1,3 +1,4 @@
1
+ import { sha256Hex } from "../../../shared/hash";
1
2
  /**
2
3
  * Kiro machine identity.
3
4
  *
@@ -21,7 +22,6 @@
21
22
  * They are deliberately different per credential family, because an API key and
22
23
  * a refresh token for the same account must not derive the same device id.
23
24
  */
24
- import { createHash } from "node:crypto";
25
25
 
26
26
  /** Prefix the observed client hashes for an OAuth (refresh-token) credential. */
27
27
  const OAUTH_SALT = "KotlinNativeAPI/";
@@ -33,9 +33,7 @@ const FALLBACK_SALT = "KiroFallback/";
33
33
  /** Length of a machine id, in hex characters. */
34
34
  const MACHINE_ID_LENGTH = 64;
35
35
 
36
- function sha256Hex(input: string): string {
37
- return createHash("sha256").update(input).digest("hex");
38
- }
36
+
39
37
 
40
38
  /**
41
39
  * Normalizes a machine id supplied from outside the derivation.
@@ -401,6 +401,8 @@ export async function discoverOpenCodeFreeModels(options: {
401
401
  readonly baseUrl: string;
402
402
  readonly fetcher?: typeof fetch;
403
403
  }): Promise<readonly ModelDefinition[] | null> {
404
+ // dynamic-import: cycle break -- the discovery helper imports this file's
405
+ // OPENCODE_FREE_SPEC to decide its request shape.
404
406
  const { fetchOpenAICompatibleModels } = await import("../../discovery/openai-model-discovery");
405
407
  const listed = await fetchOpenAICompatibleModels({
406
408
  baseUrl: options.baseUrl,
@@ -1,129 +1,129 @@
1
- import type { ApiKeyProviderSpec } from "../../configured-provider";
2
- import type { ModelDefinition } from "../../provider-model-definition";
3
- import { fetchOpenAICompatibleModels } from "../../discovery/openai-model-discovery";
4
- import { providerBaseUrl } from "../../provider-metadata";
5
- import type { DiscoveryInput } from "../../discovery/discovery-types";
6
- import { defineModel } from "../../provider-model-definition";
7
- import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
8
-
9
- const OPENROUTER_PROVIDER_ID = "openrouter" as const;
10
-
11
- /** OpenRouter requires these on every request for ranking/attribution. */
12
- const OPENROUTER_REQUIRED_HEADERS: Readonly<Record<string, string>> = {
13
- "HTTP-Referer": "https://endpoint-proxy.local",
14
- "X-Title": "Endpoint Proxy",
15
- };
16
-
17
- /**
18
- * OpenRouter's non-chat service models.
19
- *
20
- * OpenRouter fronts the System One decision API (`/api/v1/systemone`) as
21
- * `typesafe/jev-1.13`, which is not a chat model: it answers `{answers}` to a
22
- * `{state, questions}` body. `/api/v1/models` does not list it under a wire the
23
- * generic fetcher can classify, so it is declared here — a `serviceKind` row
24
- * served by the native System One route, never by the chat surface.
25
- */
26
- export const OPENROUTER_MODELS: readonly ModelDefinition[] = [
27
- // No committed chat rows. OpenRouter adds and pulls models within days, so a
28
- // hand-kept list of ids drifts out of truth and discovery is the authority;
29
- // the free roster below stays as the offline floor. `openrouter/auto` was the
30
- // last committed chat row and went with the rest of the static catalog — it
31
- // publishes no limits of its own anyway, since a router's pricing is -1.
32
- // Not a chat row: it answers `{answers}` to a `{state, questions}` body, and
33
- // is served by the native System One route. Kept because deleting it removes
34
- // that route, which is a capability, not a stale catalog entry.
35
- defineModel({
36
- id: "typesafe/jev-1.13",
37
- providerId: "openrouter",
38
- serviceKind: "systemone",
39
- wireFamily: "chat",
40
- endpoint: "/systemone",
41
- ctx: 200_000,
42
- out: 8_192,
43
- toolCall: false,
44
- }),
45
- ];
46
-
47
-
48
- /**
49
- * OpenRouter's `:free` tier, as a default catalog.
50
- *
51
- * OpenRouter runs live `/api/v1/models` discovery, so this is a floor rather
52
- * than the only source — but without it the provider lists nothing at all
53
- * until a discovery run happens to fetch it, and the free tier is the one part
54
- * of OpenRouter an operator can use with no spend.
55
- *
56
- * Fetched 2026-10-12 from `https://openrouter.ai/api/v1/models`, which served
57
- * 15 `:free` rows; the 14 that the base catalog also files under `openrouter`
58
- * are listed here. `apodex/apodex-1.1-mini:free` is absent because the base
59
- * catalog has no row for it under any provider, so its limits and price would
60
- * have to be guessed — and a published guess reads as a measurement. Discovery
61
- * still picks it up, which is the correct path for a row with no catalog data.
62
- *
63
- * Every row is marked `freeTier` as well as `free`, because the two answer
64
- * different questions: `free` says this bills zero per token, and `freeTier` is
65
- * what the model list groups on so the free roster is labelled as such rather
66
- * than mixed in with the billed catalog.
67
- */
68
- export const OPENROUTER_FREE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
69
- providerId: OPENROUTER_PROVIDER_ID,
70
- endpoint: "/chat/completions",
71
- });
72
-
73
-
74
- /**
75
- * OpenRouter's `:free` rows, discovered live.
76
- *
77
- * OpenRouter rotates its free tier constantly — models are added and pulled
78
- * within days — so a committed list is only ever a snapshot of one afternoon.
79
- * This is the same shape as the OpenCode Zen free fetch: filter the provider's
80
- * own live listing down to the free rows and mark each one `freeTier`, which is
81
- * what the model list groups under "Free models (auto)".
82
- *
83
- * The rows are the fetcher's own definitions, spread rather than rebuilt: it
84
- * has already resolved limits, pricing, and the wire family from the id, and
85
- * re-deriving that here would discard the resolution and let the two drift.
86
- */
87
- export async function discoverOpenrouterFreeModels(
88
- input: DiscoveryInput,
89
- ): Promise<readonly ModelDefinition[] | null> {
90
- const listed = await discoverOpenrouterModels(input);
91
- if (listed === null) return null;
92
- return (
93
- listed
94
- // OpenRouter's free-tier spelling is a `:free` id suffix, matched exactly
95
- // rather than as a substring so a model whose own name contains "free" is
96
- // not swept in by accident.
97
- .filter((model) => model.modelId.endsWith(":free"))
98
- .map((model) => ({
99
- ...model,
100
- // `free` is a billing statement and `freeTier` a catalog one. The
101
- // `:free` suffix answers both, so both are set; the cost is left as the
102
- // fetcher resolved it because OpenRouter publishes a real zero price.
103
- free: true,
104
- freeTier: true,
105
- }))
106
- );
107
- }
108
-
109
-
110
- /** OpenRouter adapter spec; the registry builds it via `createApiKeyAdapter`. */
111
- export const OPENROUTER_SPEC: ApiKeyProviderSpec = {
112
- provider_id: OPENROUTER_PROVIDER_ID,
113
- endpoint_paths_by_wire_family: {},
114
- extra_headers: OPENROUTER_REQUIRED_HEADERS,
115
- };
116
-
117
- export async function discoverOpenrouterModels(
118
- input: DiscoveryInput,
119
- ): Promise<readonly ModelDefinition[] | null> {
120
- return fetchOpenAICompatibleModels({
121
- baseUrl: providerBaseUrl("openrouter"),
122
- headers: {
123
- authorization: `Bearer ${input.credential}`,
124
- ...OPENROUTER_REQUIRED_HEADERS,
125
- },
126
- ...(input.signal ? { signal: input.signal } : {}),
127
- ...(input.fetcher ? { fetcher: input.fetcher } : {}),
128
- });
129
- }
1
+ import type { ApiKeyProviderSpec } from "../../configured-provider";
2
+ import type { ModelDefinition } from "../../provider-model-definition";
3
+ import { fetchOpenAICompatibleModels } from "../../discovery/openai-model-discovery";
4
+ import { providerBaseUrl } from "../../provider-metadata";
5
+ import type { DiscoveryInput } from "../../discovery/discovery-types";
6
+ import { defineModel } from "../../provider-model-definition";
7
+ import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
8
+
9
+ const OPENROUTER_PROVIDER_ID = "openrouter" as const;
10
+
11
+ /** OpenRouter requires these on every request for ranking/attribution. */
12
+ const OPENROUTER_REQUIRED_HEADERS: Readonly<Record<string, string>> = {
13
+ "HTTP-Referer": "https://endpoint-proxy.local",
14
+ "X-Title": "Endpoint Proxy",
15
+ };
16
+
17
+ /**
18
+ * OpenRouter's non-chat service models.
19
+ *
20
+ * OpenRouter fronts the System One decision API (`/api/v1/systemone`) as
21
+ * `typesafe/jev-1.13`, which is not a chat model: it answers `{answers}` to a
22
+ * `{state, questions}` body. `/api/v1/models` does not list it under a wire the
23
+ * generic fetcher can classify, so it is declared here — a `serviceKind` row
24
+ * served by the native System One route, never by the chat surface.
25
+ */
26
+ export const OPENROUTER_MODELS: readonly ModelDefinition[] = [
27
+ // No committed chat rows. OpenRouter adds and pulls models within days, so a
28
+ // hand-kept list of ids drifts out of truth and discovery is the authority;
29
+ // the free roster below stays as the offline floor. `openrouter/auto` was the
30
+ // last committed chat row and went with the rest of the static catalog — it
31
+ // publishes no limits of its own anyway, since a router's pricing is -1.
32
+ // Not a chat row: it answers `{answers}` to a `{state, questions}` body, and
33
+ // is served by the native System One route. Kept because deleting it removes
34
+ // that route, which is a capability, not a stale catalog entry.
35
+ defineModel({
36
+ id: "typesafe/jev-1.13",
37
+ providerId: "openrouter",
38
+ serviceKind: "systemone",
39
+ wireFamily: "chat",
40
+ endpoint: "/systemone",
41
+ ctx: 200_000,
42
+ out: 8_192,
43
+ toolCall: false,
44
+ }),
45
+ ];
46
+
47
+
48
+ /**
49
+ * OpenRouter's `:free` tier, as a default catalog.
50
+ *
51
+ * OpenRouter runs live `/api/v1/models` discovery, so this is a floor rather
52
+ * than the only source — but without it the provider lists nothing at all
53
+ * until a discovery run happens to fetch it, and the free tier is the one part
54
+ * of OpenRouter an operator can use with no spend.
55
+ *
56
+ * Fetched 2026-10-12 from `https://openrouter.ai/api/v1/models`, which served
57
+ * 15 `:free` rows; the 14 that the base catalog also files under `openrouter`
58
+ * are listed here. `apodex/apodex-1.1-mini:free` is absent because the base
59
+ * catalog has no row for it under any provider, so its limits and price would
60
+ * have to be guessed — and a published guess reads as a measurement. Discovery
61
+ * still picks it up, which is the correct path for a row with no catalog data.
62
+ *
63
+ * Every row is marked `freeTier` as well as `free`, because the two answer
64
+ * different questions: `free` says this bills zero per token, and `freeTier` is
65
+ * what the model list groups on so the free roster is labelled as such rather
66
+ * than mixed in with the billed catalog.
67
+ */
68
+ export const OPENROUTER_FREE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
69
+ providerId: OPENROUTER_PROVIDER_ID,
70
+ endpoint: "/chat/completions",
71
+ });
72
+
73
+
74
+ /**
75
+ * OpenRouter's `:free` rows, discovered live.
76
+ *
77
+ * OpenRouter rotates its free tier constantly — models are added and pulled
78
+ * within days — so a committed list is only ever a snapshot of one afternoon.
79
+ * This is the same shape as the OpenCode Zen free fetch: filter the provider's
80
+ * own live listing down to the free rows and mark each one `freeTier`, which is
81
+ * what the model list groups under "Free models (auto)".
82
+ *
83
+ * The rows are the fetcher's own definitions, spread rather than rebuilt: it
84
+ * has already resolved limits, pricing, and the wire family from the id, and
85
+ * re-deriving that here would discard the resolution and let the two drift.
86
+ */
87
+ export async function discoverOpenrouterFreeModels(
88
+ input: DiscoveryInput,
89
+ ): Promise<readonly ModelDefinition[] | null> {
90
+ const listed = await discoverOpenrouterModels(input);
91
+ if (listed === null) return null;
92
+ return (
93
+ listed
94
+ // OpenRouter's free-tier spelling is a `:free` id suffix, matched exactly
95
+ // rather than as a substring so a model whose own name contains "free" is
96
+ // not swept in by accident.
97
+ .filter((model) => model.modelId.endsWith(":free"))
98
+ .map((model) => ({
99
+ ...model,
100
+ // `free` is a billing statement and `freeTier` a catalog one. The
101
+ // `:free` suffix answers both, so both are set; the cost is left as the
102
+ // fetcher resolved it because OpenRouter publishes a real zero price.
103
+ free: true,
104
+ freeTier: true,
105
+ }))
106
+ );
107
+ }
108
+
109
+
110
+ /** OpenRouter adapter spec; the registry builds it via `createApiKeyAdapter`. */
111
+ export const OPENROUTER_SPEC: ApiKeyProviderSpec = {
112
+ provider_id: OPENROUTER_PROVIDER_ID,
113
+ endpoint_paths_by_wire_family: {},
114
+ extra_headers: OPENROUTER_REQUIRED_HEADERS,
115
+ };
116
+
117
+ export async function discoverOpenrouterModels(
118
+ input: DiscoveryInput,
119
+ ): Promise<readonly ModelDefinition[] | null> {
120
+ return fetchOpenAICompatibleModels({
121
+ baseUrl: providerBaseUrl("openrouter"),
122
+ headers: {
123
+ authorization: `Bearer ${input.credential}`,
124
+ ...OPENROUTER_REQUIRED_HEADERS,
125
+ },
126
+ ...(input.signal ? { signal: input.signal } : {}),
127
+ ...(input.fetcher ? { fetcher: input.fetcher } : {}),
128
+ });
129
+ }