cartethyia 2.7.4 → 2.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (595) hide show
  1. package/.env.example +5 -0
  2. package/README.md +18 -6
  3. package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
  7. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
  8. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
  19. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
  20. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
  37. package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
  38. package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
  40. package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
  42. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
  46. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  47. package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
  48. package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
  49. package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
  50. package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
  51. package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
  52. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  53. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
  54. package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
  55. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
  56. package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
  57. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  58. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  59. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  60. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  61. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
  62. package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
  63. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  64. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
  66. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  69. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
  70. package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
  71. package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
  72. package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
  73. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
  74. package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
  75. package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
  76. package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
  78. package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
  79. package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
  80. package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
  81. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
  82. package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
  83. package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
  84. package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
  85. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
  86. package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
  87. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
  88. package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
  89. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
  90. package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
  91. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
  92. package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
  93. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
  94. package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
  95. package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
  96. package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
  97. package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
  98. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  99. package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
  100. package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
  101. package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
  102. package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
  103. package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
  104. package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
  105. package/dist/dashboard/assets/providers/zai.webp +0 -0
  106. package/dist/dashboard/assets/providers/zcode.webp +0 -0
  107. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
  108. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
  109. package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
  110. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
  112. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
  114. package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
  116. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  117. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  118. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  119. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
  120. package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
  121. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
  122. package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
  123. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
  124. package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
  125. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
  126. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  127. package/dist/dashboard/console-backgrounds.jpg +0 -0
  128. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  129. package/dist/dashboard/index.html +2 -2
  130. package/dist/dashboard/index.html.gz +0 -0
  131. package/dist/dashboard/precompressed-assets.json +60 -59
  132. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  133. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  134. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  135. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  136. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  137. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  138. package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
  139. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  140. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  141. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  142. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  143. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  144. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  145. package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
  146. package/package.json +4 -6
  147. package/scripts/cli/update.ts +15 -41
  148. package/src/boot/app.ts +58 -2
  149. package/src/boot/dependencies.ts +794 -732
  150. package/src/boot/lifecycle.ts +11 -12
  151. package/src/config/cooldowns.ts +34 -55
  152. package/src/config/network.ts +1 -1
  153. package/src/config/package-paths.ts +1 -1
  154. package/src/config/providers.ts +19 -14
  155. package/src/config/resources.ts +0 -5
  156. package/src/config/runtime.ts +2 -5
  157. package/src/config/spec.ts +33 -6
  158. package/src/config/usage.ts +27 -0
  159. package/src/console/api-keys/ban-constants.ts +38 -0
  160. package/src/console/api-keys/contracts.ts +71 -62
  161. package/src/console/api-keys/routes.ts +56 -24
  162. package/src/console/auth/access.ts +4 -4
  163. package/src/console/auth/account-reset.ts +1 -1
  164. package/src/console/auth/audit-recorder.ts +1 -1
  165. package/src/console/auth/lockout.ts +7 -3
  166. package/src/console/auth/row-coercion.ts +57 -12
  167. package/src/console/auth/session-resolver.ts +1 -1
  168. package/src/console/auth/session-service.ts +8 -2
  169. package/src/console/auth/session.ts +37 -31
  170. package/src/console/auth/setup-service.ts +4 -3
  171. package/src/console/auth/user-store.ts +1 -1
  172. package/src/console/backup/9router-map.ts +0 -3
  173. package/src/console/backup/contracts.ts +9 -9
  174. package/src/console/backup/export-endpoint.ts +5 -6
  175. package/src/console/backup/nine-router.ts +1 -7
  176. package/src/console/backup/routes.ts +29 -33
  177. package/src/console/backup/service.ts +468 -468
  178. package/src/console/backup/store.ts +47 -29
  179. package/src/console/backup/validate.ts +11 -1
  180. package/src/console/cli-tools/contracts.ts +43 -299
  181. package/src/console/cli-tools/fs-ops.ts +29 -0
  182. package/src/console/cli-tools/injectors/claude.ts +3 -3
  183. package/src/console/cli-tools/injectors/codex.ts +181 -181
  184. package/src/console/cli-tools/injectors/driver.ts +150 -247
  185. package/src/console/cli-tools/injectors/kilo.ts +146 -21
  186. package/src/console/cli-tools/injectors/opencode.ts +176 -153
  187. package/src/console/cli-tools/routes.ts +7 -1
  188. package/src/console/cli-tools/service.ts +8 -7
  189. package/src/console/cli-tools/store.ts +2 -2
  190. package/src/console/console-router.ts +32 -0
  191. package/src/console/dashboard-assets.ts +9 -9
  192. package/src/console/domain-registration.ts +16 -2
  193. package/src/console/inferhub/client.ts +0 -7
  194. package/src/console/inferhub/key-store.ts +1 -1
  195. package/src/console/inferhub/normalize.ts +2 -2
  196. package/src/console/inferhub/routes.ts +1 -1
  197. package/src/console/inferhub/types.ts +1 -1
  198. package/src/console/observability/contracts.ts +7 -1
  199. package/src/console/observability/live.ts +31 -18
  200. package/src/console/observability/logs.ts +1 -1
  201. package/src/console/observability/sse.ts +62 -8
  202. package/src/console/observability/store.ts +23 -4
  203. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  204. package/src/console/providers/catalog/contracts.ts +39 -0
  205. package/src/console/providers/catalog/model-operations.ts +1 -1
  206. package/src/console/providers/catalog/provider-operations.ts +95 -17
  207. package/src/console/providers/catalog/public-model-store.ts +0 -0
  208. package/src/console/providers/catalog/routes.ts +27 -3
  209. package/src/console/providers/catalog/store.ts +147 -8
  210. package/src/console/providers/detail/routes.ts +1 -1
  211. package/src/console/providers/oauth/callback-listener.ts +100 -18
  212. package/src/console/providers/oauth/routes.ts +92 -13
  213. package/src/console/quota/account-quota-global.ts +41 -3
  214. package/src/console/quota/account-quota-shared.ts +10 -3
  215. package/src/console/quota/account-quota-tenant.ts +259 -66
  216. package/src/console/quota/daily-growth-job.ts +308 -0
  217. package/src/console/quota/native-identity.ts +2 -2
  218. package/src/console/quota/quota-view.ts +1 -1
  219. package/src/console/remote-support/routes.ts +1 -1
  220. package/src/console/remote-support/service.ts +2 -2
  221. package/src/console/routing/model/contracts.ts +1 -1
  222. package/src/console/routing/pools/contracts.ts +2 -2
  223. package/src/console/routing/pools/import-parse.ts +3 -3
  224. package/src/console/routing/pools/relay-deploy.ts +3 -3
  225. package/src/console/settings/contracts.ts +29 -2
  226. package/src/console/settings/store.ts +20 -0
  227. package/src/console/share/share-router.ts +100 -32
  228. package/src/console/share/share-stats.ts +11 -13
  229. package/src/console/share/share-usage.ts +1 -1
  230. package/src/console/shared/errors.ts +2 -2
  231. package/src/console/shared/object.ts +18 -0
  232. package/src/console/shared/read-through-cache.ts +4 -4
  233. package/src/console/shared/transaction.ts +14 -0
  234. package/src/console/system-prompt/contracts.ts +89 -12
  235. package/src/console/system-prompt/routes.ts +98 -45
  236. package/src/console/updates/apply-update.ts +244 -0
  237. package/src/console/updates/routes.ts +252 -97
  238. package/src/main.ts +11 -5
  239. package/src/network/http2-bidi.ts +1 -1
  240. package/src/network/ip-utils.ts +298 -0
  241. package/src/network/outbound-fetch.ts +1 -1
  242. package/src/network/pool/agent.ts +7 -6
  243. package/src/network/pool/byte-accounting.ts +1 -1
  244. package/src/network/pool/resolver.ts +16 -12
  245. package/src/network/pool/selector.ts +150 -136
  246. package/src/network/pool-health-machine.ts +1 -1
  247. package/src/network/rate-limit-verdict.ts +1 -1
  248. package/src/network/retry.ts +1 -1
  249. package/src/network/ssrf.ts +20 -71
  250. package/src/observability/event-log.ts +9 -2
  251. package/src/observability/event-query.ts +61 -291
  252. package/src/observability/log-sanitize.ts +1 -1
  253. package/src/observability/metrics.ts +4 -4
  254. package/src/observability/payload-capture.ts +6 -5
  255. package/src/observability/payload-store.ts +1 -1
  256. package/src/observability/runtime-metrics.ts +1 -1
  257. package/src/observability/telemetry-buffer.ts +19 -3
  258. package/src/observability/telemetry-reconciliation.ts +3 -3
  259. package/src/observability/token-speed.ts +1 -1
  260. package/src/persistence/connection-url.ts +1 -1
  261. package/src/persistence/db-handle.ts +1 -1
  262. package/src/persistence/db-pg.ts +2 -2
  263. package/src/persistence/db-sqlite.ts +3 -3
  264. package/src/persistence/dialect.ts +5 -1
  265. package/src/persistence/readiness.ts +1 -1
  266. package/src/persistence/redis.ts +1 -1
  267. package/src/persistence/schema.ts +121 -40
  268. package/src/persistence/shared-cache.ts +2 -2
  269. package/src/persistence/sort-order.ts +1 -1
  270. package/src/persistence/stores/api-key-store.ts +29 -2
  271. package/src/persistence/stores/share-store.ts +20 -8
  272. package/src/persistence/system-prompt-store.ts +63 -18
  273. package/src/persistence/tenant-preferences.ts +1 -1
  274. package/src/persistence/ttl-cache.ts +3 -3
  275. package/src/persistence/usage-flush.ts +7 -2
  276. package/src/protocol/canonical-model.ts +1 -1
  277. package/src/protocol/codecs.ts +6 -10
  278. package/src/protocol/dsml.ts +8 -125
  279. package/src/protocol/image-clamp.ts +2 -2
  280. package/src/protocol/reasoning-text.ts +1 -1
  281. package/src/protocol/reasoning.ts +7 -52
  282. package/src/protocol/registry.ts +50 -15
  283. package/src/protocol/request/chat.ts +46 -19
  284. package/src/protocol/request/codex.ts +1 -1
  285. package/src/protocol/request/messages.ts +46 -14
  286. package/src/protocol/request/responses.ts +19 -10
  287. package/src/protocol/response/chat.ts +25 -10
  288. package/src/protocol/response/codex.ts +1 -1
  289. package/src/protocol/response/messages.ts +1 -1
  290. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  291. package/src/protocol/response/responses.ts +1 -1
  292. package/src/protocol/sse-decoder.ts +50 -1
  293. package/src/protocol/stop-reason.ts +2 -2
  294. package/src/protocol/think-split.ts +347 -0
  295. package/src/protocol/translation/attachment-integrity.ts +3 -3
  296. package/src/protocol/translation/cache-controls.ts +8 -20
  297. package/src/protocol/translation/capabilities.ts +6 -6
  298. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  299. package/src/protocol/translation/dialect/inband.ts +39 -0
  300. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  301. package/src/protocol/translation/dialect/qwen.ts +114 -0
  302. package/src/protocol/translation/dialect/resolver.ts +83 -0
  303. package/src/protocol/translation/dialect/rules.ts +93 -0
  304. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  305. package/src/protocol/translation/dialect/types.ts +58 -0
  306. package/src/protocol/translation/quirks.ts +18 -4
  307. package/src/protocol/translation/thinking.ts +12 -252
  308. package/src/protocol/translation/tool-id.ts +3 -3
  309. package/src/protocol/translation/tool-repair.ts +1 -1
  310. package/src/protocol/upstream/post-json.ts +6 -1
  311. package/src/providers/adapters/compatible-adapter.ts +71 -4
  312. package/src/providers/authentication/jwt-validator.ts +0 -5
  313. package/src/providers/authentication/oauth-client.ts +254 -253
  314. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  315. package/src/providers/bundled-discovery-providers.ts +6 -30
  316. package/src/providers/configured-openai-providers.ts +82 -97
  317. package/src/providers/default-registry.ts +35 -37
  318. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  319. package/src/providers/discovery/discovery-types.ts +0 -1
  320. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  321. package/src/providers/discovery/probe-phases.ts +48 -13
  322. package/src/providers/discovery/probing-service.ts +6 -8
  323. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  324. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  325. package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
  326. package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
  327. package/src/providers/integrations/antigravity/antigravity.ts +22 -13
  328. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  329. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  330. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  331. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  332. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  333. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  334. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  335. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  336. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  337. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  338. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  339. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  340. package/src/providers/integrations/cline/cline.ts +1 -1
  341. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  342. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  343. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  344. package/src/providers/integrations/commandcode/quota.ts +131 -51
  345. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  346. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  347. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  348. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  349. package/src/providers/integrations/cursor/cursor.ts +35 -3
  350. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  351. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  352. package/src/providers/integrations/factory/factory.ts +9 -9
  353. package/src/providers/integrations/github/github-discovery.ts +70 -12
  354. package/src/providers/integrations/github/github-oauth.ts +184 -14
  355. package/src/providers/integrations/github/github.ts +134 -26
  356. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  357. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  358. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  359. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  360. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  361. package/src/providers/integrations/grok/grok.ts +86 -36
  362. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  363. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  364. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  365. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  366. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  367. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  368. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  369. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  370. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  371. package/src/providers/integrations/kiro/kiro.ts +80 -68
  372. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  373. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  374. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  375. package/src/providers/integrations/opencode/provider.ts +17 -24
  376. package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
  377. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  378. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  379. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  380. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  381. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  382. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  383. package/src/providers/integrations/qoder/qoder.ts +422 -71
  384. package/src/providers/integrations/shared/connect.ts +0 -54
  385. package/src/providers/integrations/shared/hash.ts +15 -0
  386. package/src/providers/integrations/shared/session-cache.ts +136 -0
  387. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  388. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  389. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  390. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  391. package/src/providers/integrations/trae/trae.ts +51 -8
  392. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  393. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  394. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  395. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  396. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  397. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  398. package/src/providers/operations/account-error-classification.ts +198 -133
  399. package/src/providers/operations/account-health-service.ts +31 -39
  400. package/src/providers/operations/account-reset-service.ts +58 -1
  401. package/src/providers/operations/byok-wire-profile.ts +19 -15
  402. package/src/providers/operations/cli-platform.ts +31 -5
  403. package/src/providers/operations/client-version-resolver.ts +4 -6
  404. package/src/providers/operations/client-versions.ts +10 -7
  405. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  406. package/src/providers/operations/provider-catalog-service.ts +18 -2
  407. package/src/providers/operations/provider-credential-service.ts +19 -2
  408. package/src/providers/provider-contracts.ts +18 -7
  409. package/src/providers/provider-metadata.ts +17 -2
  410. package/src/providers/provider-model-definition.ts +22 -1
  411. package/src/providers/provider-probe.ts +4 -2
  412. package/src/providers/provider-usage.ts +50 -19
  413. package/src/providers/quota/cache.ts +29 -0
  414. package/src/providers/quota/quota-contracts.ts +4 -0
  415. package/src/providers/quota/refresh.ts +27 -4
  416. package/src/providers/quota/snapshot.ts +94 -0
  417. package/src/providers/search/search-provider.ts +1 -3
  418. package/src/providers/search/search-providers.ts +2 -1
  419. package/src/security/abuse.ts +4 -4
  420. package/src/security/admission/service.ts +13 -6
  421. package/src/security/admission/ttl.ts +61 -61
  422. package/src/security/api-key-auth.ts +17 -3
  423. package/src/security/client-router-fingerprint.ts +4 -4
  424. package/src/security/credential-codec.ts +3 -3
  425. package/src/security/csrf.ts +2 -2
  426. package/src/security/ip-boundary.ts +16 -189
  427. package/src/security/model-access-rule.ts +1 -1
  428. package/src/security/outbound-headers.ts +18 -0
  429. package/src/shared/invoke-guard.ts +47 -0
  430. package/src/transport/contracts.ts +13 -4
  431. package/src/transport/dispatch/attempt-finalize.ts +70 -22
  432. package/src/transport/dispatch/attempt-loop.ts +22 -0
  433. package/src/transport/dispatch/capture-policy.ts +34 -37
  434. package/src/transport/dispatch/empty-stream.ts +23 -20
  435. package/src/transport/dispatch/generation.ts +25 -45
  436. package/src/transport/dispatch/native-shared.ts +127 -0
  437. package/src/transport/dispatch/proxy-request.ts +3 -1
  438. package/src/transport/dispatch/responses-compact.ts +25 -46
  439. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  440. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  441. package/src/transport/dispatch/streaming/stream.ts +7 -3
  442. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  443. package/src/transport/dispatch/systemone.ts +20 -48
  444. package/src/transport/dispatch/token-count.ts +41 -38
  445. package/src/transport/dispatch/websearch.ts +19 -45
  446. package/src/transport/drain-endpoint.ts +1 -1
  447. package/src/transport/failure-policy.ts +21 -1
  448. package/src/transport/guards/auth-guard.ts +0 -6
  449. package/src/transport/guards/body-policy.ts +3 -3
  450. package/src/transport/guards/error-lifecycle.ts +4 -0
  451. package/src/transport/guards/headroom-guard.ts +1 -1
  452. package/src/transport/guards/pipeline.ts +1 -1
  453. package/src/transport/guards/request-context.ts +12 -17
  454. package/src/transport/request/preparer.ts +286 -7
  455. package/src/transport/request/rtk/compress-request.ts +3 -3
  456. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  457. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  458. package/src/transport/request/state.ts +7 -13
  459. package/src/transport/request/system-prompt-injection.ts +74 -11
  460. package/src/transport/request/tenant-preferences.ts +22 -0
  461. package/src/transport/routing/candidate-index.ts +2 -2
  462. package/src/transport/routing/round-robin.ts +1 -1
  463. package/src/transport/routing/route-catalog.ts +17 -18
  464. package/src/transport/routing/route-model.ts +126 -33
  465. package/src/transport/routing/router.ts +159 -37
  466. package/src/transport/shutdown-notice.ts +2 -2
  467. package/src/transport/surface/adapters.ts +3 -3
  468. package/src/transport/surface/chat/encode.ts +7 -7
  469. package/src/transport/surface/chat/parse.ts +7 -7
  470. package/src/transport/surface/completion.ts +2 -2
  471. package/src/transport/surface/content-parts.ts +1 -1
  472. package/src/transport/surface/dialects.ts +2 -2
  473. package/src/transport/surface/messages/parse.ts +3 -3
  474. package/src/transport/surface/reasoning-summary.ts +9 -14
  475. package/src/transport/surface/responses/adapter.ts +1 -1
  476. package/src/transport/surface/responses/errors.ts +0 -9
  477. package/src/transport/vision-handoff-cache.ts +58 -0
  478. package/src/transport/vision-handoff.ts +431 -0
  479. package/src/workers/account-identity-sweep.ts +2 -2
  480. package/src/workers/buddy-growth-tasks.ts +392 -346
  481. package/src/workers/daily-checkin.ts +102 -49
  482. package/src/workers/maintenance-lease.ts +2 -35
  483. package/src/workers/oauth-refresh-sweep.ts +8 -1
  484. package/src/workers/quota-refresh-sweep.ts +120 -82
  485. package/src/workers/scheduler.ts +67 -4
  486. package/src/workers/sweep-runner.ts +24 -5
  487. package/src/workers/zcode-gift-claim.ts +108 -0
  488. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  489. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  490. package/dist/dashboard/assets/BannedPage-ChG5svFb.js +0 -1
  491. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  492. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
  493. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  494. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
  495. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  496. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  497. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  498. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  499. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  500. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
  501. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  502. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  503. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  504. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
  505. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  506. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  507. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  508. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  509. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  510. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  511. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  512. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  513. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  514. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  515. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  516. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  517. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  518. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  519. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  520. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
  521. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  522. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  523. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  524. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  525. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
  526. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  527. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  528. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  529. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  530. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  531. package/dist/dashboard/assets/app-BayTTy01.js +0 -9
  532. package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
  533. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  534. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  535. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  536. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  537. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  538. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  539. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  540. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  541. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  542. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  543. package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
  544. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  545. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  546. package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
  547. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  548. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  549. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  550. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  551. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  552. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  553. package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
  554. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  555. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  556. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  557. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  558. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  559. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  560. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  561. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  562. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  563. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  564. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  565. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  566. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  567. package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
  568. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  569. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  570. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  571. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  572. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  573. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  574. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  575. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  576. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  577. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  578. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  579. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  580. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  581. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  582. package/src/console/cli-tools/injectors/cline.ts +0 -110
  583. package/src/console/cli-tools/injectors/copilot.ts +0 -108
  584. package/src/console/cli-tools/injectors/cowork.ts +0 -64
  585. package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
  586. package/src/console/cli-tools/injectors/droid.ts +0 -96
  587. package/src/console/cli-tools/injectors/grok-build.ts +0 -70
  588. package/src/console/cli-tools/injectors/hermes.ts +0 -101
  589. package/src/console/cli-tools/injectors/jcode.ts +0 -115
  590. package/src/console/cli-tools/injectors/openclaw.ts +0 -161
  591. package/src/network/response-headers.ts +0 -39
  592. package/src/protocol/response/dsml.ts +0 -38
  593. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  594. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  595. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -0,0 +1,298 @@
1
+ // Shared IP parsing primitives: IPv4 → number, IPv6 → groups/bytes/BigInt,
2
+ // CIDR containment, and loopback detection.
3
+ //
4
+ // Consolidates the two independent copies that used to live in `ssrf.ts` and
5
+ // `security/ip-boundary.ts`. The two modules' semantics differ **on purpose**,
6
+ // so every shared entry point carries the divergence as an explicit opt-in
7
+ // rather than silently picking one behavior:
8
+ //
9
+ // - **Dotted-quad IPv6** (`::ffff:1.2.3.4`): the SSRF guard accepts it (the DNS
10
+ // layer can hand it one); the client-identity layer deliberately fail-closes
11
+ // its BigInt key path on a dotted tail so an unparseable spelling can never
12
+ // mint a database uniqueness key.
13
+ // - **CIDR**: the SSRF guard requires an explicit `/prefix` (a bare address
14
+ // matches nothing — fail-closed on an allowlist) and does not unwrap
15
+ // IPv4-mapped spellings; the trusted-proxy boundary treats a bare address as
16
+ // "this exact host" and unwraps mapped spellings, because Bun reports an
17
+ // IPv4 loopback peer as `::ffff:127.0.0.1` on Windows.
18
+ import { isIP } from "node:net";
19
+
20
+ /**
21
+ * IPv4 literal as a 32-bit unsigned number, or `undefined` when `address` is
22
+ * not a plain dotted quad. Decimal octets only — the exotic spellings `Number`
23
+ * accepts (`0x`, leading `+`, octal) are refused, because this value feeds
24
+ * allowlist comparisons where a lenient parse would widen the match.
25
+ */
26
+ export function ipv4ToNumber(address: string): number | undefined {
27
+ const parts = address.split(".");
28
+ if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part))) return undefined;
29
+ const octets = parts.map(Number);
30
+ if (octets.some((part) => part < 0 || part > 255)) return undefined;
31
+ const [a, b, c, d] = octets;
32
+ if (a === undefined || b === undefined || c === undefined || d === undefined) return undefined;
33
+ return ((a * 256 + b) * 256 + c) * 256 + d;
34
+ }
35
+
36
+ /**
37
+ * Same parse as {@link ipv4ToNumber}, but a refusal is an error. The SSRF
38
+ * guard's internal call sites only ever reach the parser with an `isIP`-validated
39
+ * string, so a malformed octet there is an invariant breach, not input handling.
40
+ */
41
+ export function ipv4ToNumberStrict(address: string): number {
42
+ const value = ipv4ToNumber(address);
43
+ if (value === undefined) throw new Error("invalid IPv4");
44
+ return value;
45
+ }
46
+
47
+ /**
48
+ * An IPv6 literal as exactly eight lowercase 16-bit hex groups, or `undefined`
49
+ * when it is not one. Expands a `::` run (which must stand for at least one
50
+ * group) and — with `allowDottedQuad` — converts a trailing dotted quad into
51
+ * the two groups it represents.
52
+ *
53
+ * `allowDottedQuad` is the preserved SSRF/boundary divergence: the SSRF guard
54
+ * passes `true` (DNS answers can arrive as `::ffff:1.2.3.4`), the client-identity
55
+ * key path leaves the default (`false`) so a dotted spelling never reaches a
56
+ * database uniqueness key. The zone id (`%eth0`) is stripped; it identifies an
57
+ * interface, not an address.
58
+ */
59
+ export function normalizeIpv6Groups(
60
+ value: string,
61
+ opts: { allowDottedQuad?: boolean } = {},
62
+ ): string[] | undefined {
63
+ const allowDottedQuad = opts.allowDottedQuad ?? false;
64
+ const text = value.toLowerCase().trim().split("%", 1)[0] ?? "";
65
+ if (text.length === 0) return undefined;
66
+ const doubleIndex = text.indexOf("::");
67
+ // More than one `::` is invalid, as is a `::` inside a group.
68
+ if (doubleIndex !== text.lastIndexOf("::")) return undefined;
69
+
70
+ const headText = doubleIndex >= 0 ? text.slice(0, doubleIndex) : text;
71
+ const tailText = doubleIndex >= 0 ? text.slice(doubleIndex + 2) : "";
72
+ const head = headText.length > 0 ? headText.split(":") : [];
73
+ const tail = tailText.length > 0 ? tailText.split(":") : [];
74
+
75
+ // A dotted quad is a single token that fills two groups.
76
+ const expandDotted = (tokens: string[]): string[] | undefined => {
77
+ const last = tokens[tokens.length - 1];
78
+ if (last === undefined || !last.includes(".")) return tokens;
79
+ if (!allowDottedQuad) return undefined;
80
+ const quad = last.split(".");
81
+ if (quad.length !== 4) return undefined;
82
+ const octets = quad.map((part) => Number(part));
83
+ if (octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return undefined;
84
+ const [a, b, c, d] = octets;
85
+ if (a === undefined || b === undefined || c === undefined || d === undefined) return undefined;
86
+ const high = ((a << 8) | b).toString(16);
87
+ const low = ((c << 8) | d).toString(16);
88
+ return [...tokens.slice(0, -1), high, low];
89
+ };
90
+
91
+ const expandedHead = expandDotted(head);
92
+ const expandedTail = expandDotted(tail);
93
+ if (expandedHead === undefined || expandedTail === undefined) return undefined;
94
+
95
+ let full: string[];
96
+ if (doubleIndex >= 0) {
97
+ const missing = 8 - expandedHead.length - expandedTail.length;
98
+ // `::` must stand for at least one group, so exactly-8 without it is invalid.
99
+ if (missing < 1) return undefined;
100
+ full = [...expandedHead, ...Array.from({ length: missing }, () => "0"), ...expandedTail];
101
+ } else {
102
+ full = expandedHead;
103
+ }
104
+ if (full.length !== 8) return undefined;
105
+ const normalized: string[] = [];
106
+ for (const group of full) {
107
+ if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;
108
+ normalized.push(Number.parseInt(group, 16).toString(16));
109
+ }
110
+ return normalized;
111
+ }
112
+
113
+ /** The 16 bytes of an IPv6 literal, or `undefined` when it is not one. */
114
+ export function ipv6GroupsToBytes(groups: readonly string[]): Uint8Array | undefined {
115
+ if (groups.length !== 8) return undefined;
116
+ const bytes = new Uint8Array(16);
117
+ for (let index = 0; index < 8; index += 1) {
118
+ const group = groups[index]!;
119
+ if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;
120
+ const n = Number.parseInt(group, 16);
121
+ bytes[index * 2] = n >>> 8;
122
+ bytes[index * 2 + 1] = n & 255;
123
+ }
124
+ return bytes;
125
+ }
126
+
127
+ /** The 128-bit value of an IPv6 literal, or `undefined` when it is not one. */
128
+ export function ipv6GroupsToBigInt(groups: readonly string[]): bigint | undefined {
129
+ if (groups.length !== 8) return undefined;
130
+ let result = 0n;
131
+ for (const group of groups) {
132
+ if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;
133
+ result = (result << 16n) | BigInt(Number.parseInt(group, 16));
134
+ }
135
+ return result;
136
+ }
137
+
138
+ /** 128-bit value of an IPv6 literal; `allowDottedQuad` opts into the dotted tail. */
139
+ export function ipv6ToBigInt(address: string, opts: { allowDottedQuad?: boolean } = {}): bigint | undefined {
140
+ const groups = normalizeIpv6Groups(address, opts);
141
+ return groups === undefined ? undefined : ipv6GroupsToBigInt(groups);
142
+ }
143
+
144
+ /**
145
+ * The 32-bit tail of an IPv4-mapped IPv6 address, or `undefined` when `value` is
146
+ * not one. Accepts every spelling of the same address: `::ffff:1.2.3.4`,
147
+ * `::ffff:102:304`, `0:0:0:0:0:ffff:102:304`, and `::ffff:0:102:304`.
148
+ *
149
+ * Works by normalizing to exactly eight 16-bit groups first. That is the only way
150
+ * to compare spellings: a dotted tail is written as ONE colon-separated token but
151
+ * occupies TWO group slots, so any check that counts `split(":")` entries gets the
152
+ * position of the `ffff` marker wrong.
153
+ */
154
+ function mappedIpv4Tail(value: string): string | undefined {
155
+ const groups = normalizeIpv6Groups(value, { allowDottedQuad: true });
156
+ if (groups === undefined) return undefined;
157
+ // The first five groups (80 bits) must be zero, and the sixth must be `ffff`.
158
+ for (let index = 0; index < 5; index += 1) {
159
+ if (groups[index] !== "0") return undefined;
160
+ }
161
+ if (groups[5] !== "ffff") return undefined;
162
+ const high = groups[6];
163
+ const low = groups[7];
164
+ if (high === undefined || low === undefined) return undefined;
165
+ return `${high}:${low}`;
166
+ }
167
+
168
+ /**
169
+ * Unwraps an IPv4-mapped IPv6 address to plain IPv4.
170
+ *
171
+ * Bun reports an IPv4 loopback peer as `::ffff:127.0.0.1` on Windows, and
172
+ * `isIP` classifies it as IPv6 — so a parser that bails on a dotted quad never
173
+ * matched `127.0.0.1/32`. Accepts both the dotted and the hex (`::ffff:7f00:1`)
174
+ * spelling.
175
+ */
176
+ export function unwrapMappedIpv4(address: string): string {
177
+ const value = address.toLowerCase().split("%", 1)[0] ?? "";
178
+ // Recognize the mapped range STRUCTURALLY, not by its compressed spelling. The
179
+ // old `startsWith("::ffff:")` test missed any form that writes the leading zero
180
+ // groups out — `0:0:0:0:0:ffff:7f00:1` is the same host as `::ffff:127.0.0.1`,
181
+ // but it failed the prefix test, fell through to the generic IPv6 parse, and
182
+ // was treated as an unrelated IPv6 address. The trust check then refused a peer
183
+ // the allowlist covered, and `canonicalClientIpKey` minted a second key for one
184
+ // host — which is stored on issued child keys for consistent usage reporting
185
+ // and audit.
186
+ //
187
+ // A mapped address is `::ffff:<32 bits>`, i.e. the first 80 bits are zero and the
188
+ // next 16 are `0xffff`. Matching that is the same test `isIP` uses internally.
189
+ const tail = mappedIpv4Tail(value);
190
+ if (tail === undefined) return address;
191
+ // `mappedIpv4Tail` always returns two hex groups, so the dotted and hex
192
+ // spellings have already converged by here.
193
+ const groups = tail.split(":");
194
+ if (groups.length !== 2) return address;
195
+ const high = Number.parseInt(groups[0] ?? "", 16);
196
+ const low = Number.parseInt(groups[1] ?? "", 16);
197
+ if (
198
+ !Number.isInteger(high) ||
199
+ !Number.isInteger(low) ||
200
+ high < 0 ||
201
+ high > 0xffff ||
202
+ low < 0 ||
203
+ low > 0xffff
204
+ )
205
+ return address;
206
+ return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
207
+ }
208
+
209
+ export interface CidrContainsOptions {
210
+ /**
211
+ * `true` (SSRF guard): the CIDR must carry an explicit `/prefix` — a bare
212
+ * address matches nothing, so a misconfigured allowlist entry fails closed.
213
+ * `false` (trusted-proxy boundary): a bare address means "this exact host".
214
+ */
215
+ requirePrefix?: boolean;
216
+ /**
217
+ * `true` (trusted-proxy boundary): unwrap IPv4-mapped spellings on both the
218
+ * subject and the network before comparing, so `::ffff:127.0.0.1` and
219
+ * `127.0.0.1/32` describe the same host. `false` (SSRF guard): compare the
220
+ * literal as written.
221
+ */
222
+ unwrapMapped?: boolean;
223
+ /**
224
+ * The SSRF guard's IPv6 parse accepts the dotted-quad spelling (`::ffff:1.2.3.4`,
225
+ * which DNS answers can take); the boundary path refuses it. Defaults to
226
+ * `false`; the SSRF call site passes `true`.
227
+ */
228
+ allowDottedQuad?: boolean;
229
+ }
230
+
231
+ /**
232
+ * Whether `address` falls inside `cidr`. The blank-prefix refusal
233
+ * (`"10.0.0.0/"` must NOT mean /0) is shared by both former implementations and
234
+ * is preserved unconditionally: `Number("")` is 0 and /0 would admit every
235
+ * address of the family — for the SSRF guard that includes the link-local
236
+ * metadata endpoint the guard exists to refuse, and for the trusted-proxy
237
+ * boundary it would believe any caller's `X-Forwarded-For`.
238
+ */
239
+ export function cidrContainsAddress(
240
+ address: string,
241
+ cidr: string,
242
+ opts: CidrContainsOptions = {},
243
+ ): boolean {
244
+ const requirePrefix = opts.requirePrefix ?? true;
245
+ const unwrapMapped = opts.unwrapMapped ?? false;
246
+ const allowDottedQuad = opts.allowDottedQuad ?? false;
247
+ const [network, rawPrefix] = cidr.split("/");
248
+ if (!network) return false;
249
+ const prefixText = rawPrefix === undefined ? undefined : rawPrefix.trim();
250
+ // Requiring a run of digits also refuses the exotic spellings `Number`
251
+ // accepts for zero (`-0`, `+0`, `0x0`, `0b0`, `0o0`, `0.0`) — typos, not a
252
+ // deliberate "match everything".
253
+ if (prefixText !== undefined && !/^\d+$/.test(prefixText)) return false;
254
+ if (requirePrefix && prefixText === undefined) return false;
255
+ const subject = unwrapMapped ? unwrapMappedIpv4(address) : address;
256
+ const base = unwrapMapped ? unwrapMappedIpv4(network) : network;
257
+ const family = isIP(subject);
258
+ if (family !== isIP(base)) return false;
259
+ const maxPrefix = family === 4 ? 32 : 128;
260
+ const prefix = prefixText === undefined ? maxPrefix : Number(prefixText);
261
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > maxPrefix) return false;
262
+ if (family === 4) {
263
+ const value = ipv4ToNumber(subject);
264
+ const networkValue = ipv4ToNumber(base);
265
+ if (value === undefined || networkValue === undefined) return false;
266
+ if (prefix === 0) return true;
267
+ const mask = (0xffffffff << (32 - prefix)) >>> 0;
268
+ return ((value >>> 0) & mask) === ((networkValue >>> 0) & mask);
269
+ }
270
+ if (family !== 6) return false;
271
+ const value = ipv6ToBigInt(subject, { allowDottedQuad });
272
+ const networkValue = ipv6ToBigInt(base, { allowDottedQuad });
273
+ if (value === undefined || networkValue === undefined) return false;
274
+ const shift = BigInt(128 - prefix);
275
+ return (value >> shift) === (networkValue >> shift);
276
+ }
277
+
278
+ /**
279
+ * True when `address` is a loopback address.
280
+ *
281
+ * Structural, not a string-prefix test: `127.0.0.1`, `::1`, and the mapped
282
+ * `::ffff:127.0.0.1` a Windows/Bun peer arrives as are all the same host, and
283
+ * a prefix test would miss the mapped form — the exact shape that hid this
284
+ * class of bug before.
285
+ *
286
+ * `transport/drain-endpoint.ts` keeps its own weaker `isLoopbackPeer` (a
287
+ * `::ffff:` prefix strip that misses the `0:0:0:0:0:ffff:7f00:1` spelling);
288
+ * unifying it widens the admin gate's acceptance set and is deferred — switch
289
+ * it to this predicate as a deliberate, separately-landed behavior widening.
290
+ */
291
+ export function isLoopbackAddress(address: string): boolean {
292
+ const normalized = unwrapMappedIpv4(address.trim().split("%", 1)[0] ?? "");
293
+ if (normalized === "::1") return true;
294
+ if (isIP(normalized) !== 4) return false;
295
+ const value = ipv4ToNumber(normalized);
296
+ // 127.0.0.0/8 — the whole loopback block, not just 127.0.0.1.
297
+ return value !== undefined && ((value >>> 0) & 0xff000000) === (127 << 24);
298
+ }
@@ -10,7 +10,7 @@ import { isProxyAgentPair, type ProxyAgentPair } from "./pool/agent";
10
10
  import { metrics } from "../observability/metrics";
11
11
  import { trackNetworkCall } from "../observability/performance-metrics";
12
12
  // Validated fetch — single egress with ProxyAgent support
13
- export interface ValidatedFetchOptions {
13
+ interface ValidatedFetchOptions {
14
14
  readonly fetchFn?: typeof fetch;
15
15
  readonly policy?: SsrfPolicy;
16
16
  readonly maxRedirects?: number;
@@ -44,7 +44,7 @@ export type TransportKind = (typeof TRANSPORT_KINDS)[number];
44
44
 
45
45
 
46
46
  /** Config keys reserved for endpoint/label; every other key is opaque non-secret transport config. */
47
- export const RESERVED_ENDPOINT_CONFIG_KEYS: Record<string, true> = {
47
+ const RESERVED_ENDPOINT_CONFIG_KEYS: Record<string, true> = {
48
48
  endpoint: true,
49
49
  label: true,
50
50
  };
@@ -122,9 +122,11 @@ function createSsrfLookup(policy: SsrfPolicy): NonNullable<RequestOptions["looku
122
122
  family?: number,
123
123
  ) => void,
124
124
  ): void => {
125
- const abort = new AbortController();
126
- const timer = setTimeout(() => abort.abort(), 5_000);
127
- void resolveAllAddresses(hostname, abort.signal)
125
+ // No outer AbortController/timer here: `resolveAllAddresses` already races
126
+ // its own 5s deadline against the OS lookup and maps the failure to a
127
+ // deterministic `transport_unavailable` 502. A second timer would only
128
+ // re-race it with a different error class (499/504 depending on timing).
129
+ void resolveAllAddresses(hostname)
128
130
  .then((addrs) => {
129
131
  try {
130
132
  validateResolvedAddresses(addrs, policy);
@@ -145,8 +147,7 @@ function createSsrfLookup(policy: SsrfPolicy): NonNullable<RequestOptions["looku
145
147
  }
146
148
  cb(null, first, family);
147
149
  })
148
- .catch((error: unknown) => cb(error as Error))
149
- .finally(() => clearTimeout(timer));
150
+ .catch((error: unknown) => cb(error as Error));
150
151
  }) as unknown as NonNullable<RequestOptions["lookup"]>;
151
152
  }
152
153
 
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  /** Running totals for one pool. */
17
- export interface PoolByteTotals {
17
+ interface PoolByteTotals {
18
18
  /** Bytes sent to the proxy (uploads, TLS handshake, CONNECT). */
19
19
  readonly sent: number;
20
20
  /** Bytes received from the proxy (downloads). */
@@ -50,7 +50,7 @@ const HTTP2_MAX_BUFFERED_BYTES = 64 * 1024 * 1024;
50
50
  */
51
51
  const HTTP2_SESSION_MAX_MEMORY_MB = 256;
52
52
 
53
- export interface PoolAgentResolverDeps {
53
+ interface PoolAgentResolverDeps {
54
54
  readonly ssrfPolicy?: SsrfPolicy;
55
55
  readonly resolveFn?: (hostname: string, signal: AbortSignal) => Promise<readonly string[]>;
56
56
  /** Database used to load pool rows. */
@@ -256,11 +256,6 @@ export class PoolAgentResolver {
256
256
 
257
257
  }
258
258
 
259
- /** Observable cache size for the runtime metrics sampler. */
260
- agentCount(): number {
261
- return this.agents.size;
262
- }
263
-
264
259
  async releasePool(poolId: string, tenantId: string): Promise<void> {
265
260
  const cacheKey = `${tenantId}:${poolId}`;
266
261
  const pending = this.agents.get(cacheKey);
@@ -274,10 +269,6 @@ export class PoolAgentResolver {
274
269
  }
275
270
  }
276
271
  }
277
- async resolveWebSocketAgent(poolId: string, tenantId: string): Promise<PoolAgent> {
278
- return this.resolveAgent(poolId, tenantId);
279
- }
280
-
281
272
  private async buildAgent(poolId: string, tenantId: string): Promise<PoolAgent> {
282
273
  const row = await this.loader.load(poolId);
283
274
  if (!row) throw new PoolBindingError(`network pool ${poolId} not found`);
@@ -959,6 +950,14 @@ class WsSession implements ProviderWebSocketSession {
959
950
  }
960
951
  export class ValidatedNetworkBindingFactory {
961
952
  private readonly directFetch: ValidatedFetch;
953
+ /**
954
+ * One `ValidatedFetch` per resolved pool agent. Building the closure (dialer,
955
+ * relay setup, redirect loop) on every request through a pool was pure
956
+ * churn; keying by the agent object means a released agent is naturally
957
+ * collected (new builds mint a new agent) so no explicit invalidation is
958
+ * needed.
959
+ */
960
+ private readonly pooledFetchCache = new WeakMap<PoolAgent, ValidatedFetch>();
962
961
 
963
962
  constructor(
964
963
  private readonly policy: SsrfPolicy = {},
@@ -991,7 +990,12 @@ export class ValidatedNetworkBindingFactory {
991
990
  return async (input, init) => {
992
991
  try {
993
992
  const agent = await poolResolver.resolveAgent(networkPoolId, tenantId);
994
- return await createValidatedFetch({ policy, agent })(input, init);
993
+ let validated = this.pooledFetchCache.get(agent);
994
+ if (validated === undefined) {
995
+ validated = createValidatedFetch({ policy, agent });
996
+ this.pooledFetchCache.set(agent, validated);
997
+ }
998
+ return await validated(input, init);
995
999
  } catch (error) {
996
1000
  if (error instanceof PoolBindingError) {
997
1001
  throw new GatewayError(
@@ -1026,7 +1030,7 @@ export class ValidatedNetworkBindingFactory {
1026
1030
  throw new GatewayError("proxy_pool_unhealthy", 503, "configured WebSocket proxy pool could not be established (request {requestId}). This bridge looks shaky — repair or reroute 🔧");
1027
1031
  }
1028
1032
  try {
1029
- agent = await this.poolResolver.resolveWebSocketAgent(networkPoolId, tenantId);
1033
+ agent = await this.poolResolver.resolveAgent(networkPoolId, tenantId);
1030
1034
  } catch (error) {
1031
1035
  if (error instanceof PoolBindingError) {
1032
1036
  throw new GatewayError("proxy_pool_unhealthy", 503, "configured WebSocket proxy pool could not be established (request {requestId}). This bridge looks shaky — repair or reroute 🔧");