cartethyia 2.4.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +45 -13
  2. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  3. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  8. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  9. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  25. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  32. package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
  33. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  34. package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
  35. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
  37. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
  39. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  44. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  45. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  47. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
  49. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
  51. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
  53. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
  61. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
  63. package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
  64. package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
  65. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  67. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
  71. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
  73. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
  75. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
  77. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
  81. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  84. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  85. package/dist/dashboard/assets/providers/trae.webp +0 -0
  86. package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
  87. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  88. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  89. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  90. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  91. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  92. package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
  93. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  94. package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
  95. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  96. package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
  97. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  98. package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
  99. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  100. package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
  101. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  102. package/dist/dashboard/index.html +1 -1
  103. package/dist/dashboard/index.html.gz +0 -0
  104. package/dist/dashboard/precompressed-assets.json +51 -52
  105. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  106. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  107. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  108. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  109. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  110. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  111. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  112. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  113. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  114. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  115. package/package.json +3 -2
  116. package/scripts/cli/command/console-menu.ts +30 -11
  117. package/scripts/cli/command/start.ts +77 -13
  118. package/scripts/cli/command/update.ts +1 -1
  119. package/scripts/cli/gateway/instance-lock.ts +105 -0
  120. package/scripts/cli/gateway/spawn.ts +173 -101
  121. package/scripts/cli/ui/background.ts +110 -49
  122. package/src/boot/app.ts +20 -6
  123. package/src/boot/dependencies.ts +19 -0
  124. package/src/boot/elysia-typebox-bridge.ts +37 -0
  125. package/src/boot/lifecycle.ts +2 -0
  126. package/src/config/cooldowns.ts +138 -138
  127. package/src/config/providers.ts +5 -5
  128. package/src/config/runtime.ts +19 -3
  129. package/src/config/spec.ts +9 -0
  130. package/src/console/api-keys/contracts.ts +414 -391
  131. package/src/console/api-keys/routes.ts +880 -870
  132. package/src/console/auth/account-reset-store.ts +14 -0
  133. package/src/console/auth/account-reset.ts +3 -2
  134. package/src/console/auth/setup-service.ts +6 -3
  135. package/src/console/domain-registration.ts +9 -2
  136. package/src/console/observability/contracts.ts +475 -455
  137. package/src/console/observability/store.ts +1015 -952
  138. package/src/console/providers/catalog/contracts.ts +31 -0
  139. package/src/console/providers/catalog/provider-operations.ts +8 -0
  140. package/src/console/providers/catalog/store.ts +1532 -1525
  141. package/src/console/providers/oauth/callback-listener.ts +18 -3
  142. package/src/console/providers/oauth/routes.ts +38 -15
  143. package/src/console/quota/refresh.ts +19 -0
  144. package/src/console/updates/routes.ts +162 -4
  145. package/src/main.ts +3 -0
  146. package/src/network/http2-bidi.ts +85 -0
  147. package/src/network/outbound-fetch.ts +1 -26
  148. package/src/network/pool/agent.ts +478 -460
  149. package/src/network/pool/resolver.ts +1104 -544
  150. package/src/network/pool-health-machine.ts +12 -5
  151. package/src/observability/payload-capture.ts +344 -324
  152. package/src/persistence/db-pg.ts +9 -3
  153. package/src/persistence/db-sqlite.ts +497 -495
  154. package/src/persistence/health-event-flush.ts +137 -0
  155. package/src/persistence/migrate.ts +181 -129
  156. package/src/persistence/schema.ts +1360 -1295
  157. package/src/persistence/settings-revision.ts +25 -0
  158. package/src/persistence/shared-cache.ts +217 -0
  159. package/src/persistence/stores/share-store.ts +556 -528
  160. package/src/persistence/usage-flush.ts +61 -1
  161. package/src/protocol/canonical-model.ts +23 -1
  162. package/src/protocol/response/chat.ts +143 -10
  163. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  164. package/src/providers/default-registry.ts +173 -1
  165. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  166. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  167. package/src/providers/discovery/probing-service.ts +1022 -1001
  168. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
  169. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  170. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
  171. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  172. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  173. package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
  174. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  175. package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
  176. package/src/providers/integrations/claude/claude.ts +606 -582
  177. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  178. package/src/providers/integrations/cline/cline.ts +539 -468
  179. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  180. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  181. package/src/providers/integrations/commandcode/quota.ts +160 -0
  182. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  183. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  184. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  185. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  186. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  187. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  188. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  189. package/src/providers/integrations/cursor/cursor.ts +960 -0
  190. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  191. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  192. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  193. package/src/providers/integrations/github/github.ts +4 -4
  194. package/src/providers/integrations/hermes.ts +79 -8
  195. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  196. package/src/providers/integrations/kilo/kilo.ts +153 -146
  197. package/src/providers/integrations/opencode/provider.ts +532 -538
  198. package/src/providers/integrations/openrouter/provider.ts +139 -281
  199. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  200. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
  201. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  202. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  203. package/src/providers/integrations/qoder/qoder.ts +865 -730
  204. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  205. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  206. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  207. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  208. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  209. package/src/providers/integrations/trae/trae.ts +458 -0
  210. package/src/providers/operations/account-health-service.ts +107 -10
  211. package/src/providers/operations/client-versions.ts +133 -6
  212. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  213. package/src/providers/operations/provider-credential-service.ts +46 -7
  214. package/src/providers/provider-contracts.ts +76 -0
  215. package/src/providers/provider-metadata.ts +78 -6
  216. package/src/providers/provider-model-definition.ts +373 -349
  217. package/src/security/api-key-auth.ts +126 -8
  218. package/src/transport/contracts.ts +3 -0
  219. package/src/transport/dispatch/attempt-finalize.ts +32 -1
  220. package/src/transport/dispatch/attempt-loop.ts +530 -515
  221. package/src/transport/dispatch/generation.ts +88 -21
  222. package/src/transport/dispatch/leases.ts +99 -2
  223. package/src/transport/dispatch/proxy-request.ts +60 -3
  224. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  225. package/src/transport/dispatch/streaming/stream.ts +22 -2
  226. package/src/transport/dispatch/upstream.ts +19 -1
  227. package/src/transport/guards/auth-guard.ts +26 -1
  228. package/src/transport/guards/body-policy.ts +5 -4
  229. package/src/transport/request/preparer.ts +103 -0
  230. package/src/transport/request/state.ts +14 -0
  231. package/src/transport/routing/plan-gate.ts +45 -0
  232. package/src/transport/routing/route-catalog.ts +799 -777
  233. package/src/transport/routing/route-model.ts +52 -1
  234. package/src/transport/routing/router.ts +51 -0
  235. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
  236. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  237. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  238. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  239. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
  240. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  241. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  242. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  243. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  244. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  245. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  246. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
  247. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  248. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
  249. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  250. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  251. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  252. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
  253. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  254. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
  255. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  256. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
  257. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  258. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  259. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  260. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
  261. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  262. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  263. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
  264. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  265. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  266. package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
  267. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  268. package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
  269. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  270. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  271. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  272. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  273. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  274. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  275. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  276. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  277. package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
  278. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  279. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  280. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  281. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  282. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  283. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  284. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  285. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  286. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  287. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  288. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  289. package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
  290. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  291. package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
  292. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  293. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  294. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  295. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  296. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  297. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  298. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  299. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  300. package/src/network/tls/bun-client-hello.ts +0 -112
  301. /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
  302. /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
@@ -25,6 +25,7 @@
25
25
  * already spent.
26
26
  */
27
27
  import { log } from "../../../observability/logger";
28
+ import { ConsoleDomainError } from "../../shared/errors";
28
29
 
29
30
  /** A loopback endpoint a callback can actually be delivered to. */
30
31
  export interface LoopbackCallbackEndpoint {
@@ -224,10 +225,15 @@ export class OAuthCallbackListener {
224
225
  try {
225
226
  servers.push(this.#serve({ hostname: "127.0.0.1", port: endpoint.port, fetch: handler }));
226
227
  } catch (cause) {
227
- throw new Error(
228
+ // A domain error, not a bare Error: the console route maps an untyped
229
+ // failure to a generic `internal_error`, which hid an actionable "free
230
+ // the port" message behind "Failed to start OAuth".
231
+ throw new ConsoleDomainError(
232
+ "oauth_callback_port_unavailable",
233
+ 409,
228
234
  `OAuth callback port ${endpoint.port} is in use, so the login redirect to ${endpoint.path} cannot be delivered. ` +
229
235
  `Free port ${endpoint.port} and retry.`,
230
- { cause },
236
+ { port: endpoint.port, path: endpoint.path },
231
237
  );
232
238
  }
233
239
  try {
@@ -253,7 +259,16 @@ export class OAuthCallbackListener {
253
259
  }
254
260
  const error = url.searchParams.get("error");
255
261
  const state = url.searchParams.get("state") ?? "";
256
- const code = url.searchParams.get("code") ?? "";
262
+ /**
263
+ * Not every authorization server hands back a `code`. Trae's page redirects
264
+ * with the refresh token already in the query (`?isRedirect=true&
265
+ * refreshToken=…`), and the client's parser reads the whole query. So an
266
+ * absent `code` falls back to the query itself — the client decides whether
267
+ * it can redeem it, and one that cannot still fails with its own message
268
+ * rather than a bare 400 here.
269
+ */
270
+ const codeParam = url.searchParams.get("code") ?? "";
271
+ const code = codeParam.length > 0 ? codeParam : url.search.slice(1);
257
272
  if (error !== null) {
258
273
  // A denial carries the state only when the server echoes one; clear it
259
274
  // when present so a retry starts a fresh flow.
@@ -28,14 +28,22 @@ import type { ConsoleAccessResolver } from "../../auth/access";
28
28
  * identity the provider itself reports (an email or org name), which is stable
29
29
  * across logins, so it is what a re-login replaces.
30
30
  *
31
- * A provider that reports no label falls back to the refresh-token fingerprint,
32
- * which is the old behaviour and still catches an exact replay. Renaming an
33
- * account in the console changes the stored label, so a later login is treated
34
- * as a new account rather than silently overwriting the rename — the same
35
- * tradeoff the unique index has always had, now stated where it is decided.
31
+ * A label the provider never resolved is *not* that identity. Callers fill the
32
+ * gap with the provider id (`cursor`, `github`), and hashing that made every
33
+ * login of every account of that provider collide: the second login matched the
34
+ * first row and replaced it, so adding an account silently consumed the one
35
+ * already there. Only a label the provider actually reported — or the refresh
36
+ * token, which still catches an exact replay — may be the identity.
37
+ *
38
+ * Test seam: exported so the merge rule can be pinned without a database.
36
39
  */
37
- function oauthIdentityFingerprint(input: { readonly label: string } & OAuthExchangeResult): string {
38
- return hashSecret(input.label.trim().length > 0 ? input.label.trim() : input.refresh);
40
+ export function oauthIdentityFingerprint(input: {
41
+ readonly label: string;
42
+ readonly providerId: string;
43
+ } & OAuthExchangeResult): string {
44
+ const label = input.label.trim();
45
+ const resolved = label.length > 0 && label.toLowerCase() !== input.providerId.trim().toLowerCase();
46
+ return hashSecret(resolved ? label : input.refresh);
39
47
  }
40
48
 
41
49
  export class DrizzleOAuthAccountStore implements OAuthAccountStore {
@@ -46,7 +54,7 @@ export class DrizzleOAuthAccountStore implements OAuthAccountStore {
46
54
  providerId: string,
47
55
  input: { readonly label: string } & OAuthExchangeResult,
48
56
  ): Promise<PersistedOAuthAccount> {
49
- const credentialFingerprint = oauthIdentityFingerprint(input);
57
+ const credentialFingerprint = oauthIdentityFingerprint({ ...input, providerId });
50
58
  const sameAccount = and(
51
59
  eq(providerAccounts.providerId, providerId),
52
60
  // The identity index coalesces a null tenant to a sentinel, so "same
@@ -375,12 +383,32 @@ export function createOAuthLoginOperations(config: OAuthLoginConfig) {
375
383
  }
376
384
  const state = generateOAuthState();
377
385
  const { codeVerifier, codeChallenge } = generatePkcePair();
386
+ /**
387
+ * A client whose authorize URL depends on an upstream discovery runs it
388
+ * here, before the URL is built: `buildAuthorizeUrl` is synchronous by
389
+ * contract, so a client cannot fetch inside it.
390
+ *
391
+ * Trae is the reason this is wired up: its login host comes from
392
+ * `GetLoginGuidance`, and without this step the URL was built with no
393
+ * host and the login failed at the first click. Kiro declares the hook
394
+ * as a no-op, so nothing that already worked changes.
395
+ */
396
+ const authorizeRequest = {
397
+ state,
398
+ codeChallenge,
399
+ redirectUri: client.browserRedirectUri ?? browserAuthorizeRedirectUri(),
400
+ ...(parameters === undefined ? {} : { parameters }),
401
+ };
402
+ const prepared =
403
+ typeof client.prepareAuthorize === "function"
404
+ ? await client.prepareAuthorize(authorizeRequest)
405
+ : authorizeRequest;
378
406
  // A client that names its own allowlisted redirect URI wins over the
379
407
  // gateway default: OpenAI registers exactly `http://localhost:1455/auth/callback`
380
408
  // for the Codex client and answers any other value with
381
409
  // `invalid_request` before consent, so the shared default cannot serve
382
410
  // every provider.
383
- const redirectUri = client.browserRedirectUri ?? browserAuthorizeRedirectUri();
411
+ const redirectUri = authorizeRequest.redirectUri;
384
412
  // Bind the redirect's loopback port before the flow is recorded. The
385
413
  // advertised URI names the operator's own machine, so a login that only
386
414
  // advertises it leaves the browser on a dead page and the code stranded
@@ -404,12 +432,7 @@ export function createOAuthLoginOperations(config: OAuthLoginConfig) {
404
432
  // deliver itself; a non-loopback redirect (a custom scheme, a remote
405
433
  // host) returns false and keeps the manual path.
406
434
  return {
407
- authorizeUrl: client.buildAuthorizeUrl({
408
- state,
409
- codeChallenge,
410
- redirectUri,
411
- ...(parameters === undefined ? {} : { parameters }),
412
- }),
435
+ authorizeUrl: client.buildAuthorizeUrl(prepared),
413
436
  state,
414
437
  };
415
438
  },
@@ -377,6 +377,25 @@ async function runQuotaRefresh(
377
377
  // unsupported") is itself the answer the page must render, and caching it
378
378
  // stops every page open from re-hitting a provider that just refused us.
379
379
  await setCachedQuota(targetLens(target), target.accountId, quota, deps.redis);
380
+ // Persist the plan the provider reported, because routing gates on it: some
381
+ // upstreams refuse a model on a free plan and nothing but the account's own
382
+ // plan says which. The cache above is a short in-process TTL and empty after
383
+ // a restart, so it cannot be the only copy — the row is what survives one.
384
+ //
385
+ // A failed fetch leaves the last known plan in place rather than clearing
386
+ // it. The refresh failing is evidence about the network or the credential,
387
+ // not evidence that the account's plan changed, and clearing on failure
388
+ // would drop the router back to "unknown" on every transient error.
389
+ if (quota.plan !== null) {
390
+ try {
391
+ await deps.db
392
+ .update(providerAccounts)
393
+ .set({ quotaPlan: quota.plan })
394
+ .where(eq(providerAccounts.id, target.accountId));
395
+ } catch (error) {
396
+ log.warn(`[quota] failed to record plan for account=${target.accountId}`, error as Error);
397
+ }
398
+ }
380
399
  // A quota surface that names the account adopts the identity — but only over
381
400
  // a default label. An operator's custom rename is never overwritten, and the
382
401
  // CASE keeps the read and the write in one statement so a concurrent rename
@@ -1,6 +1,73 @@
1
1
  import { Elysia } from "elysia";
2
- import { consoleErrorHandler } from "../shared/errors";
3
- import { fetchLatestRelease } from "./repository-updates";
2
+ import { consoleErrorHandler, requireTenantScope } from "../shared/errors";
3
+ import { consoleSseResponse, createConsoleSseStream } from "../observability/sse";
4
+ import type { ObservabilityStore } from "../observability/contracts";
5
+ import type { ConsoleAccessResolver } from "../auth/access";
6
+ import { fetchLatestRelease, type LatestRelease } from "./repository-updates";
7
+ import { isNewerRelease } from "./version-compare";
8
+
9
+ const MEMORY_SAMPLE_INTERVAL_MS = 10_000;
10
+ const UPDATE_SAMPLE_INTERVAL_MS = 60_000;
11
+ const HIGH_MEMORY_PERCENT = 85;
12
+ const HIGH_MEMORY_CLEAR_PERCENT = 80;
13
+ const MEMORY_GROWTH_WINDOW_MS = 60_000;
14
+ const MEMORY_GROWTH_RATIO = 1.2;
15
+ const MAX_MEMORY_SAMPLES = 8;
16
+ const RELEASES_URL = "https://github.com/risunCodex/Cartethyia/releases";
17
+
18
+ export interface NotificationEvent {
19
+ readonly id: string;
20
+ readonly kind: "memory-high" | "memory-growth" | "update-available";
21
+ readonly title: string;
22
+ readonly message: string;
23
+ readonly createdAt: string;
24
+ readonly level: "warning" | "info";
25
+ readonly href?: string;
26
+ }
27
+
28
+ export function memoryNotifications(
29
+ samples: readonly { readonly at: number; readonly rss: number; readonly percent: number }[],
30
+ previous: { high: boolean; growth: boolean },
31
+ ): { notifications: NotificationEvent[]; high: boolean; growth: boolean } {
32
+ const latest = samples.at(-1);
33
+ if (latest === undefined) return { notifications: [], ...previous };
34
+ const notifications: NotificationEvent[] = [];
35
+ const high = previous.high
36
+ ? latest.percent >= HIGH_MEMORY_CLEAR_PERCENT
37
+ : latest.percent >= HIGH_MEMORY_PERCENT;
38
+ if (high && !previous.high) {
39
+ notifications.push({
40
+ id: "memory-high",
41
+ kind: "memory-high",
42
+ title: "High gateway memory",
43
+ message: `Gateway RSS is using ${latest.percent.toFixed(1)}% of system memory.`,
44
+ createdAt: new Date(latest.at).toISOString(),
45
+ level: "warning",
46
+ });
47
+ }
48
+
49
+ const oldest = samples.find((sample) => latest.at - sample.at >= MEMORY_GROWTH_WINDOW_MS);
50
+ const growth = oldest !== undefined &&
51
+ latest.rss >= oldest.rss * MEMORY_GROWTH_RATIO &&
52
+ samples.filter((sample) => sample.at >= oldest.at).every((sample, index, window) =>
53
+ index === 0 || sample.rss >= (window[index - 1]?.rss ?? sample.rss),
54
+ );
55
+ if (growth && !previous.growth && !high) {
56
+ notifications.push({
57
+ id: "memory-growth",
58
+ kind: "memory-growth",
59
+ title: "Sustained memory growth",
60
+ message: `Gateway RSS grew ${Math.round((latest.rss / oldest.rss - 1) * 100)}% over the last minute.`,
61
+ createdAt: new Date(latest.at).toISOString(),
62
+ level: "warning",
63
+ });
64
+ }
65
+ return {
66
+ notifications,
67
+ high: high && latest.percent >= HIGH_MEMORY_CLEAR_PERCENT,
68
+ growth,
69
+ };
70
+ }
4
71
 
5
72
  /**
6
73
  * Read-only update discovery for the console sidebar.
@@ -10,8 +77,99 @@ import { fetchLatestRelease } from "./repository-updates";
10
77
  * answers `null` rather than an error: an absent update badge is the honest
11
78
  * result when the upstream cannot be reached.
12
79
  */
13
- export function createUpdateRoutes(): Elysia {
80
+ export function createUpdateRoutes(config: {
81
+ readonly accessResolver: ConsoleAccessResolver;
82
+ readonly observabilityStore: ObservabilityStore;
83
+ readonly fetchRelease?: () => Promise<LatestRelease | null>;
84
+ }): Elysia {
85
+ const fetchRelease = config.fetchRelease ?? fetchLatestRelease;
14
86
  return new Elysia()
15
87
  .error(consoleErrorHandler("Update check failed"))
16
- .get("/updates/release", async () => await fetchLatestRelease()) as unknown as Elysia;
88
+ .get("/updates/release", async () => await fetchRelease())
89
+ .get("/notifications/stream", ({ request }) => {
90
+ const tenantId = requireTenantScope(
91
+ config.accessResolver(request),
92
+ "dashboard:read",
93
+ ).tenantId;
94
+ return consoleSseResponse(
95
+ createConsoleSseStream(request.signal, (sender) => {
96
+ let closed = false;
97
+ let memoryTimer: ReturnType<typeof setTimeout> | undefined;
98
+ let updateTimer: ReturnType<typeof setTimeout> | undefined;
99
+ let latestTag: string | undefined;
100
+ let memoryState = { high: false, growth: false };
101
+ let samples: Array<{ at: number; rss: number; percent: number }> = [];
102
+
103
+ const publishMemory = async (): Promise<void> => {
104
+ if (closed) return;
105
+ try {
106
+ const at = Date.now();
107
+ // Fresh, not `health`: that read is cached for 10 s and this
108
+ // samples on that exact cadence, so the cached bytes would make
109
+ // consecutive samples identical and a growth trend unreadable.
110
+ const live = await config.observabilityStore.liveMemory();
111
+ samples = [
112
+ ...samples,
113
+ { at, rss: live.memory_bytes, percent: live.memory_percent },
114
+ ].filter((sample) => at - sample.at <= MEMORY_GROWTH_WINDOW_MS * 2)
115
+ .slice(-MAX_MEMORY_SAMPLES);
116
+ const computed = memoryNotifications(samples, memoryState);
117
+ memoryState = { high: computed.high, growth: computed.growth };
118
+ for (const notification of computed.notifications) sender.send("notification", notification);
119
+ } catch {
120
+ // A transient check must not terminate the feed or hide later events.
121
+ }
122
+ };
123
+
124
+ const publishUpdate = async (): Promise<void> => {
125
+ if (closed) return;
126
+ try {
127
+ const release = await fetchRelease();
128
+ if (release === null || release.tag_name === latestTag) return;
129
+ const health = (await config.observabilityStore.health(tenantId)).value;
130
+ if (!isNewerRelease(health.version, release.tag_name)) return;
131
+ latestTag = release.tag_name;
132
+ sender.send("notification", {
133
+ id: `update-${release.tag_name}`,
134
+ kind: "update-available",
135
+ title: "Cartethyia update available",
136
+ message: `Version ${release.tag_name} is available; this gateway runs ${health.version}.`,
137
+ createdAt: new Date().toISOString(),
138
+ level: "info",
139
+ href: RELEASES_URL,
140
+ } satisfies NotificationEvent);
141
+ } catch {
142
+ // Release discovery is best-effort and cannot interrupt memory sampling.
143
+ }
144
+ };
145
+
146
+ const scheduleMemory = (): void => {
147
+ memoryTimer = setTimeout(() => {
148
+ void publishMemory().finally(() => {
149
+ if (!closed) scheduleMemory();
150
+ });
151
+ }, MEMORY_SAMPLE_INTERVAL_MS);
152
+ };
153
+ const scheduleUpdate = (): void => {
154
+ updateTimer = setTimeout(() => {
155
+ void publishUpdate().finally(() => {
156
+ if (!closed) scheduleUpdate();
157
+ });
158
+ }, UPDATE_SAMPLE_INTERVAL_MS);
159
+ };
160
+
161
+ void publishMemory().finally(() => {
162
+ if (!closed) scheduleMemory();
163
+ });
164
+ void publishUpdate().finally(() => {
165
+ if (!closed) scheduleUpdate();
166
+ });
167
+ return () => {
168
+ closed = true;
169
+ clearTimeout(memoryTimer);
170
+ clearTimeout(updateTimer);
171
+ };
172
+ }),
173
+ );
174
+ }) as unknown as Elysia;
17
175
  }
package/src/main.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // Must run before any module that registers a route: it wires Elysia's TypeBox
2
+ // bridge statically, which the source-run path needs under NODE_ENV=production.
3
+ import "./boot/elysia-typebox-bridge";
1
4
  import { createGatewayApp, createGatewayShell } from "./boot/app";
2
5
  import { bootstrap } from "./boot/lifecycle";
3
6
  import type { CartethyiaBoot } from "./boot/lifecycle";
@@ -0,0 +1,85 @@
1
+ // Transport-neutral bidirectional HTTP/2 egress contract.
2
+ //
3
+ // Cursor's Run is a Connect-RPC bidi stream: one long-lived writable request
4
+ // whose response body is an unbounded stream of protobuf frames. Neither
5
+ // `fetch` nor the WebSocket session contract can express it — `fetch` needs the
6
+ // whole body up front, and a WebSocket is a different protocol — so the egress
7
+ // boundary gets its own contract.
8
+ //
9
+ // This module is transport-neutral on purpose: it declares *what* a validated
10
+ // bidi stream is, and `network/pool/resolver.ts` decides *how* one is dialed
11
+ // (direct TLS to a pinned, SSRF-validated address, or through the selected
12
+ // tenant pool's CONNECT/SOCKS tunnel). A provider adapter consumes the contract
13
+ // and never opens a socket of its own.
14
+
15
+ /** Response metadata of one opened bidi stream. */
16
+ export interface Http2BidiResponse {
17
+ /** HTTP/2 `:status`; `0` before the response headers arrive. */
18
+ readonly status: number;
19
+ readonly headers: Readonly<Record<string, string>>;
20
+ }
21
+
22
+ /** Terminal reason a bidi stream ended. */
23
+ export type Http2BidiEndReason =
24
+ /** The server closed the stream cleanly. */
25
+ | { readonly kind: "remote-clean-close" }
26
+ /** The server failed it: an HTTP status, a gRPC trailer, or a transport error. */
27
+ | { readonly kind: "remote-error"; readonly error: Error }
28
+ /** The client closed or aborted it. */
29
+ | { readonly kind: "local-close" };
30
+
31
+ /**
32
+ * One validated bidirectional HTTP/2 stream.
33
+ *
34
+ * Bytes in are an async iterable (so a provider pumps frames naturally and
35
+ * backpressure comes from the consumer); bytes out are `write`, which resolves
36
+ * once the chunk has drained — a bidi Run can carry megabytes, and an
37
+ * unawaited write is how a stream overruns its session memory and dies with
38
+ * `ENHANCE_YOUR_CALM`.
39
+ */
40
+ export interface ValidatedHttp2BidiStream {
41
+ readonly response: Http2BidiResponse;
42
+ /** Response trailers, populated when the server sends them (gRPC status). */
43
+ readonly trailers: Readonly<Record<string, string>>;
44
+ /** Response body bytes, in arrival order. */
45
+ readonly body: AsyncIterable<Uint8Array>;
46
+ /** Writes one request chunk, resolving when it has drained. */
47
+ write(chunk: Uint8Array): Promise<void>;
48
+ /** Half-closes the request side; the response may still be streaming. */
49
+ end(): void;
50
+ /** Tears the stream down immediately. Idempotent. */
51
+ abort(reason?: unknown): void;
52
+ /** Registers a terminal listener; the returned function unregisters it. */
53
+ onTerminal(listener: (reason: Http2BidiEndReason) => void): () => void;
54
+ }
55
+
56
+ /** Options for opening one validated bidi stream. */
57
+ export interface Http2BidiOpenOptions {
58
+ readonly signal?: AbortSignal;
59
+ /** Bytes read idle before the stream is declared dead. */
60
+ readonly readIdleMs?: number;
61
+ /** Upper bound on buffered inbound bytes before the stream is refused. */
62
+ readonly maxBufferedBytes?: number;
63
+ /**
64
+ * Host allowlist applied on every new connection, in addition to the SSRF
65
+ * policy. A long-lived stream carries the account credential for its whole
66
+ * life, so "is this host one the caller may reach at all" is a per-provider
67
+ * decision the binding enforces rather than trusts the adapter to apply.
68
+ */
69
+ readonly allowHostname?: (hostname: string) => boolean;
70
+ }
71
+
72
+ /**
73
+ * The dialer contract a provider adapter is handed.
74
+ *
75
+ * Declared here rather than in the provider layer so the dependency runs one
76
+ * way: `provider-contracts` imports this module, never the reverse. It used to
77
+ * be declared there and re-exported from here, which made the two import each
78
+ * other — type-only, so it never deadlocked, but it meant this
79
+ * transport-neutral contract had to know about providers.
80
+ */
81
+ export type ValidatedOutboundHttp2Bidi = (
82
+ url: URL,
83
+ headers: Readonly<Record<string, string>>,
84
+ options?: Http2BidiOpenOptions,
85
+ ) => Promise<ValidatedHttp2BidiStream>;
@@ -9,7 +9,6 @@ import type { SsrfPolicy } from "../config/network";
9
9
  import { isProxyAgentPair, type ProxyAgentPair } from "./pool/agent";
10
10
  import { metrics } from "../observability/metrics";
11
11
  import { trackNetworkCall } from "../observability/performance-metrics";
12
- import { bunClientHelloOptions } from "./tls/bun-client-hello";
13
12
  import { stripResponseHeaders } from "./response-headers";
14
13
  // Validated fetch — single egress with ProxyAgent support
15
14
  export interface ValidatedFetchOptions {
@@ -18,14 +17,6 @@ export interface ValidatedFetchOptions {
18
17
  readonly maxRedirects?: number;
19
18
  readonly resolveFn?: (hostname: string, signal: AbortSignal) => Promise<readonly string[]>;
20
19
  readonly agent?: HttpAgent | HttpsAgent | ProxyAgentPair;
21
- /**
22
- * Handshake with Bun's cipher list, curve list and ALPN instead of
23
- * OpenSSL's defaults. Opt-in, and off by default: see
24
- * `network/tls/bun-client-hello.ts` — ciphers and curves match exactly, but
25
- * the extension list cannot be shaped from Node, so this is a closer hello
26
- * rather than an identical one.
27
- */
28
- readonly bunClientHello?: boolean;
29
20
  }
30
21
  export type ValidatedFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
31
22
 
@@ -164,7 +155,6 @@ async function pinnedFetch(
164
155
  init: RequestInit,
165
156
  resolvedAddress: string | undefined,
166
157
  agent?: HttpAgent | HttpsAgent,
167
- useBunHello = false,
168
158
  ): Promise<Response> {
169
159
  const request = url.protocol === "https:" ? httpsRequest : httpRequest;
170
160
  const headers = new Headers(init.headers);
@@ -232,15 +222,6 @@ async function pinnedFetch(
232
222
  signal: init.signal ?? undefined,
233
223
  ...(agent ? { agent } : { agent: directAgent, lookup }),
234
224
  ...(url.protocol === "https:" ? { servername: url.hostname } : {}),
235
- // Bun's cipher list, curve list, ALPN and version span, when this
236
- // request opted into it. See `network/tls/bun-client-hello.ts` for
237
- // what this does and does not match: ciphers and curves land exactly;
238
- // the extension list cannot be shaped from Node at all.
239
- //
240
- // Opt-in, and off by default. It changes the TLS handshake of every
241
- // request it touches, and no provider needs it until the free-tier
242
- // gate is shown to reject the default hello.
243
- ...(useBunHello && url.protocol === "https:" ? bunClientHelloOptions() : {}),
244
225
  },
245
226
  (response) => {
246
227
  const responseHeaders = stripResponseHeaders(response.headers);
@@ -345,13 +326,7 @@ export function createValidatedFetch(options: ValidatedFetchOptions = {}): Valid
345
326
  response = await options.fetchFn(url, requestInit);
346
327
  } else {
347
328
  const agent = agentForTarget(configuredAgent, url.protocol);
348
- response = await pinnedFetch(
349
- url,
350
- requestInit,
351
- resolvedAddress!,
352
- agent,
353
- options.bunClientHello === true,
354
- );
329
+ response = await pinnedFetch(url, requestInit, resolvedAddress!, agent);
355
330
  }
356
331
  if (response.status < 300 || response.status >= 400) {
357
332
  trackNetworkCall(url.hostname, performance.now() - startedAt);