cartethyia 2.7.4 → 2.7.5

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 (553) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-ChG5svFb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/{CustomizationPage-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/assert-C0K-c4eo.js +1 -0
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js +1 -0
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-BSAsYsmV.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-CPK47xQx.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  92. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  93. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  94. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  95. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  96. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  97. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  98. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  99. package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
  100. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  101. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
  102. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  103. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  104. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  105. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  106. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  107. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
  108. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  109. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
  110. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  111. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
  112. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  113. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
  114. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  115. package/dist/dashboard/console-backgrounds.jpg +0 -0
  116. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  117. package/dist/dashboard/index.html +2 -2
  118. package/dist/dashboard/index.html.gz +0 -0
  119. package/dist/dashboard/precompressed-assets.json +56 -55
  120. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  121. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  122. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  123. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  124. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  125. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  126. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  127. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  128. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  129. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  130. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  131. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  132. package/package.json +4 -6
  133. package/scripts/cli/update.ts +15 -41
  134. package/src/boot/app.ts +58 -2
  135. package/src/boot/dependencies.ts +786 -732
  136. package/src/boot/lifecycle.ts +11 -12
  137. package/src/config/cooldowns.ts +34 -55
  138. package/src/config/network.ts +1 -1
  139. package/src/config/package-paths.ts +1 -1
  140. package/src/config/providers.ts +13 -14
  141. package/src/config/resources.ts +0 -5
  142. package/src/config/runtime.ts +2 -5
  143. package/src/config/spec.ts +26 -6
  144. package/src/console/api-keys/ban-constants.ts +38 -0
  145. package/src/console/api-keys/contracts.ts +71 -62
  146. package/src/console/api-keys/routes.ts +48 -12
  147. package/src/console/auth/access.ts +4 -4
  148. package/src/console/auth/account-reset.ts +1 -1
  149. package/src/console/auth/audit-recorder.ts +1 -1
  150. package/src/console/auth/lockout.ts +7 -3
  151. package/src/console/auth/row-coercion.ts +57 -12
  152. package/src/console/auth/session-resolver.ts +1 -1
  153. package/src/console/auth/session-service.ts +8 -2
  154. package/src/console/auth/session.ts +37 -31
  155. package/src/console/auth/setup-service.ts +4 -3
  156. package/src/console/auth/user-store.ts +1 -1
  157. package/src/console/backup/9router-map.ts +0 -3
  158. package/src/console/backup/contracts.ts +9 -9
  159. package/src/console/backup/export-endpoint.ts +5 -6
  160. package/src/console/backup/nine-router.ts +1 -7
  161. package/src/console/backup/routes.ts +29 -33
  162. package/src/console/backup/service.ts +468 -468
  163. package/src/console/backup/store.ts +47 -29
  164. package/src/console/backup/validate.ts +11 -1
  165. package/src/console/cli-tools/contracts.ts +7 -7
  166. package/src/console/cli-tools/injectors/cline.ts +110 -110
  167. package/src/console/cli-tools/injectors/codex.ts +181 -181
  168. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  169. package/src/console/cli-tools/injectors/driver.ts +247 -247
  170. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  171. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  172. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  173. package/src/console/cli-tools/routes.ts +1 -1
  174. package/src/console/cli-tools/service.ts +1 -1
  175. package/src/console/cli-tools/store.ts +2 -2
  176. package/src/console/console-router.ts +32 -0
  177. package/src/console/dashboard-assets.ts +9 -9
  178. package/src/console/domain-registration.ts +16 -2
  179. package/src/console/inferhub/client.ts +0 -7
  180. package/src/console/inferhub/key-store.ts +1 -1
  181. package/src/console/inferhub/normalize.ts +2 -2
  182. package/src/console/inferhub/routes.ts +1 -1
  183. package/src/console/inferhub/types.ts +1 -1
  184. package/src/console/observability/contracts.ts +1 -1
  185. package/src/console/observability/live.ts +31 -18
  186. package/src/console/observability/logs.ts +1 -1
  187. package/src/console/observability/sse.ts +62 -8
  188. package/src/console/observability/store.ts +20 -4
  189. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  190. package/src/console/providers/catalog/contracts.ts +39 -0
  191. package/src/console/providers/catalog/model-operations.ts +1 -1
  192. package/src/console/providers/catalog/provider-operations.ts +95 -17
  193. package/src/console/providers/catalog/public-model-store.ts +0 -0
  194. package/src/console/providers/catalog/routes.ts +27 -3
  195. package/src/console/providers/catalog/store.ts +147 -8
  196. package/src/console/providers/detail/routes.ts +1 -1
  197. package/src/console/providers/oauth/callback-listener.ts +100 -18
  198. package/src/console/providers/oauth/routes.ts +92 -13
  199. package/src/console/quota/account-quota-global.ts +41 -3
  200. package/src/console/quota/account-quota-shared.ts +10 -3
  201. package/src/console/quota/account-quota-tenant.ts +259 -66
  202. package/src/console/quota/daily-growth-job.ts +308 -0
  203. package/src/console/quota/native-identity.ts +2 -2
  204. package/src/console/quota/quota-view.ts +1 -1
  205. package/src/console/remote-support/routes.ts +1 -1
  206. package/src/console/remote-support/service.ts +2 -2
  207. package/src/console/routing/model/contracts.ts +1 -1
  208. package/src/console/routing/pools/contracts.ts +2 -2
  209. package/src/console/routing/pools/import-parse.ts +3 -3
  210. package/src/console/routing/pools/relay-deploy.ts +3 -3
  211. package/src/console/settings/contracts.ts +29 -2
  212. package/src/console/settings/store.ts +20 -0
  213. package/src/console/share/share-router.ts +100 -32
  214. package/src/console/share/share-stats.ts +4 -4
  215. package/src/console/share/share-usage.ts +1 -1
  216. package/src/console/shared/errors.ts +2 -2
  217. package/src/console/shared/object.ts +18 -0
  218. package/src/console/shared/read-through-cache.ts +4 -4
  219. package/src/console/shared/transaction.ts +14 -0
  220. package/src/console/system-prompt/contracts.ts +89 -12
  221. package/src/console/system-prompt/routes.ts +98 -45
  222. package/src/console/updates/apply-update.ts +244 -0
  223. package/src/console/updates/routes.ts +252 -97
  224. package/src/main.ts +11 -5
  225. package/src/network/http2-bidi.ts +1 -1
  226. package/src/network/ip-utils.ts +298 -0
  227. package/src/network/outbound-fetch.ts +1 -1
  228. package/src/network/pool/agent.ts +7 -6
  229. package/src/network/pool/byte-accounting.ts +1 -1
  230. package/src/network/pool/resolver.ts +16 -12
  231. package/src/network/pool/selector.ts +150 -136
  232. package/src/network/pool-health-machine.ts +1 -1
  233. package/src/network/rate-limit-verdict.ts +1 -1
  234. package/src/network/retry.ts +1 -1
  235. package/src/network/ssrf.ts +20 -71
  236. package/src/observability/event-log.ts +2 -2
  237. package/src/observability/event-query.ts +59 -291
  238. package/src/observability/log-sanitize.ts +1 -1
  239. package/src/observability/metrics.ts +4 -4
  240. package/src/observability/payload-capture.ts +6 -5
  241. package/src/observability/payload-store.ts +1 -1
  242. package/src/observability/runtime-metrics.ts +1 -1
  243. package/src/observability/telemetry-buffer.ts +12 -3
  244. package/src/observability/telemetry-reconciliation.ts +3 -3
  245. package/src/observability/token-speed.ts +1 -1
  246. package/src/persistence/connection-url.ts +1 -1
  247. package/src/persistence/db-handle.ts +1 -1
  248. package/src/persistence/db-pg.ts +2 -2
  249. package/src/persistence/db-sqlite.ts +3 -3
  250. package/src/persistence/dialect.ts +5 -1
  251. package/src/persistence/readiness.ts +1 -1
  252. package/src/persistence/redis.ts +1 -1
  253. package/src/persistence/schema.ts +113 -40
  254. package/src/persistence/shared-cache.ts +2 -2
  255. package/src/persistence/sort-order.ts +1 -1
  256. package/src/persistence/stores/api-key-store.ts +10 -1
  257. package/src/persistence/stores/share-store.ts +20 -8
  258. package/src/persistence/system-prompt-store.ts +63 -18
  259. package/src/persistence/tenant-preferences.ts +1 -1
  260. package/src/persistence/ttl-cache.ts +3 -3
  261. package/src/persistence/usage-flush.ts +7 -2
  262. package/src/protocol/canonical-model.ts +1 -1
  263. package/src/protocol/codecs.ts +6 -10
  264. package/src/protocol/dsml.ts +3 -49
  265. package/src/protocol/image-clamp.ts +2 -2
  266. package/src/protocol/reasoning-text.ts +1 -1
  267. package/src/protocol/reasoning.ts +7 -52
  268. package/src/protocol/registry.ts +50 -15
  269. package/src/protocol/request/chat.ts +46 -19
  270. package/src/protocol/request/codex.ts +1 -1
  271. package/src/protocol/request/messages.ts +46 -14
  272. package/src/protocol/request/responses.ts +19 -10
  273. package/src/protocol/response/chat.ts +25 -10
  274. package/src/protocol/response/codex.ts +1 -1
  275. package/src/protocol/response/messages.ts +1 -1
  276. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  277. package/src/protocol/response/responses.ts +1 -1
  278. package/src/protocol/sse-decoder.ts +50 -1
  279. package/src/protocol/stop-reason.ts +2 -2
  280. package/src/protocol/think-split.ts +347 -0
  281. package/src/protocol/translation/attachment-integrity.ts +3 -3
  282. package/src/protocol/translation/cache-controls.ts +8 -20
  283. package/src/protocol/translation/capabilities.ts +6 -6
  284. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  285. package/src/protocol/translation/dialect/inband.ts +39 -0
  286. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  287. package/src/protocol/translation/dialect/qwen.ts +114 -0
  288. package/src/protocol/translation/dialect/resolver.ts +83 -0
  289. package/src/protocol/translation/dialect/rules.ts +93 -0
  290. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  291. package/src/protocol/translation/dialect/types.ts +58 -0
  292. package/src/protocol/translation/quirks.ts +18 -4
  293. package/src/protocol/translation/thinking.ts +12 -252
  294. package/src/protocol/translation/tool-id.ts +3 -3
  295. package/src/protocol/translation/tool-repair.ts +1 -1
  296. package/src/protocol/upstream/post-json.ts +6 -1
  297. package/src/providers/adapters/compatible-adapter.ts +42 -3
  298. package/src/providers/authentication/jwt-validator.ts +0 -5
  299. package/src/providers/authentication/oauth-client.ts +254 -253
  300. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  301. package/src/providers/bundled-discovery-providers.ts +6 -30
  302. package/src/providers/configured-openai-providers.ts +82 -97
  303. package/src/providers/default-registry.ts +23 -35
  304. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  305. package/src/providers/discovery/discovery-types.ts +0 -1
  306. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  307. package/src/providers/discovery/probe-phases.ts +48 -13
  308. package/src/providers/discovery/probing-service.ts +6 -8
  309. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  310. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  311. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  312. package/src/providers/integrations/antigravity/antigravity.ts +2 -2
  313. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  314. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  315. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  316. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  317. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  318. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  319. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  320. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  321. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  322. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  323. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  324. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  325. package/src/providers/integrations/cline/cline.ts +1 -1
  326. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  327. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  328. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  329. package/src/providers/integrations/commandcode/quota.ts +131 -51
  330. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  331. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  332. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  333. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  334. package/src/providers/integrations/cursor/cursor.ts +38 -5
  335. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  336. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  337. package/src/providers/integrations/factory/factory.ts +9 -9
  338. package/src/providers/integrations/github/github-discovery.ts +55 -10
  339. package/src/providers/integrations/github/github-oauth.ts +6 -5
  340. package/src/providers/integrations/github/github.ts +41 -14
  341. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  342. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  343. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  344. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  345. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  346. package/src/providers/integrations/grok/grok.ts +86 -36
  347. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  348. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  349. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  350. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  351. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  352. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  353. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  354. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  355. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  356. package/src/providers/integrations/kiro/kiro.ts +81 -68
  357. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  358. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  359. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  360. package/src/providers/integrations/opencode/provider.ts +17 -24
  361. package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
  362. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  363. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  364. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  365. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  366. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  367. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  368. package/src/providers/integrations/qoder/qoder.ts +354 -62
  369. package/src/providers/integrations/shared/connect.ts +0 -54
  370. package/src/providers/integrations/shared/hash.ts +15 -0
  371. package/src/providers/integrations/shared/session-cache.ts +136 -0
  372. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  373. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  374. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  375. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  376. package/src/providers/integrations/trae/trae.ts +53 -10
  377. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  378. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  379. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  380. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  381. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  382. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  383. package/src/providers/operations/account-error-classification.ts +131 -116
  384. package/src/providers/operations/account-health-service.ts +31 -39
  385. package/src/providers/operations/account-reset-service.ts +58 -1
  386. package/src/providers/operations/cli-platform.ts +31 -5
  387. package/src/providers/operations/client-version-resolver.ts +4 -6
  388. package/src/providers/operations/client-versions.ts +10 -7
  389. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  390. package/src/providers/operations/provider-catalog-service.ts +18 -2
  391. package/src/providers/operations/provider-credential-service.ts +19 -2
  392. package/src/providers/provider-contracts.ts +18 -7
  393. package/src/providers/provider-metadata.ts +17 -2
  394. package/src/providers/provider-model-definition.ts +22 -1
  395. package/src/providers/provider-probe.ts +4 -2
  396. package/src/providers/provider-usage.ts +81 -16
  397. package/src/providers/quota/cache.ts +29 -0
  398. package/src/providers/quota/quota-contracts.ts +4 -0
  399. package/src/providers/quota/refresh.ts +27 -4
  400. package/src/providers/quota/snapshot.ts +94 -0
  401. package/src/providers/search/search-provider.ts +1 -3
  402. package/src/providers/search/search-providers.ts +2 -1
  403. package/src/security/abuse.ts +4 -4
  404. package/src/security/admission/service.ts +2 -2
  405. package/src/security/admission/ttl.ts +61 -61
  406. package/src/security/api-key-auth.ts +16 -4
  407. package/src/security/client-router-fingerprint.ts +4 -4
  408. package/src/security/credential-codec.ts +3 -3
  409. package/src/security/csrf.ts +2 -2
  410. package/src/security/ip-boundary.ts +16 -189
  411. package/src/security/model-access-rule.ts +1 -1
  412. package/src/security/outbound-headers.ts +18 -0
  413. package/src/shared/invoke-guard.ts +47 -0
  414. package/src/transport/contracts.ts +13 -4
  415. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  416. package/src/transport/dispatch/capture-policy.ts +34 -37
  417. package/src/transport/dispatch/empty-stream.ts +23 -20
  418. package/src/transport/dispatch/generation.ts +25 -45
  419. package/src/transport/dispatch/native-shared.ts +127 -0
  420. package/src/transport/dispatch/proxy-request.ts +3 -1
  421. package/src/transport/dispatch/responses-compact.ts +25 -46
  422. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  423. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  424. package/src/transport/dispatch/streaming/stream.ts +7 -3
  425. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  426. package/src/transport/dispatch/systemone.ts +20 -48
  427. package/src/transport/dispatch/token-count.ts +41 -38
  428. package/src/transport/dispatch/websearch.ts +19 -45
  429. package/src/transport/drain-endpoint.ts +1 -1
  430. package/src/transport/failure-policy.ts +6 -0
  431. package/src/transport/guards/auth-guard.ts +0 -6
  432. package/src/transport/guards/body-policy.ts +3 -3
  433. package/src/transport/guards/headroom-guard.ts +1 -1
  434. package/src/transport/guards/pipeline.ts +1 -1
  435. package/src/transport/guards/request-context.ts +12 -17
  436. package/src/transport/request/preparer.ts +286 -7
  437. package/src/transport/request/rtk/compress-request.ts +3 -3
  438. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  439. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  440. package/src/transport/request/state.ts +1 -13
  441. package/src/transport/request/system-prompt-injection.ts +74 -11
  442. package/src/transport/request/tenant-preferences.ts +22 -0
  443. package/src/transport/routing/candidate-index.ts +2 -2
  444. package/src/transport/routing/round-robin.ts +1 -1
  445. package/src/transport/routing/route-catalog.ts +17 -18
  446. package/src/transport/routing/route-model.ts +126 -33
  447. package/src/transport/routing/router.ts +152 -37
  448. package/src/transport/shutdown-notice.ts +2 -2
  449. package/src/transport/surface/adapters.ts +3 -3
  450. package/src/transport/surface/chat/encode.ts +7 -7
  451. package/src/transport/surface/chat/parse.ts +7 -7
  452. package/src/transport/surface/completion.ts +2 -2
  453. package/src/transport/surface/content-parts.ts +1 -1
  454. package/src/transport/surface/dialects.ts +2 -2
  455. package/src/transport/surface/messages/parse.ts +3 -3
  456. package/src/transport/surface/responses/adapter.ts +1 -1
  457. package/src/transport/surface/responses/errors.ts +0 -9
  458. package/src/transport/vision-handoff-cache.ts +58 -0
  459. package/src/transport/vision-handoff.ts +376 -0
  460. package/src/workers/account-identity-sweep.ts +2 -2
  461. package/src/workers/buddy-growth-tasks.ts +392 -346
  462. package/src/workers/daily-checkin.ts +102 -49
  463. package/src/workers/maintenance-lease.ts +2 -35
  464. package/src/workers/oauth-refresh-sweep.ts +8 -1
  465. package/src/workers/quota-refresh-sweep.ts +120 -82
  466. package/src/workers/scheduler.ts +67 -4
  467. package/src/workers/sweep-runner.ts +24 -5
  468. package/src/workers/zcode-gift-claim.ts +108 -0
  469. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  470. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  471. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  472. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  473. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  474. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  475. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  476. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  477. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  478. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  479. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  480. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  481. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  482. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  483. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  484. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  485. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  486. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  487. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  488. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  489. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  490. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  491. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  492. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  493. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  494. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  495. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  496. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  497. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  498. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  499. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  500. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  501. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  502. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  503. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  504. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  505. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  506. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  507. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  508. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  509. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  510. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  511. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  512. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  513. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  514. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  515. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  516. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  517. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  518. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  519. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  520. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  521. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  522. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  523. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  524. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  525. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  526. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  527. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  528. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  529. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  530. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  531. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  532. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  533. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  534. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  535. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  536. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  537. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  538. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  539. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  540. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  541. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  542. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  543. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  544. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  545. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  546. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  547. package/src/network/response-headers.ts +0 -39
  548. package/src/protocol/response/dsml.ts +0 -38
  549. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  550. /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
  551. /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  552. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  553. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -36,8 +36,7 @@ import {
36
36
  type ProxyCooldownEntry,
37
37
  } from "./pool-state";
38
38
 
39
- export type { PoolRotation, PoolUsageSnapshot, ProxyCooldownEntry };
40
- export { parseCooldownEntry };
39
+ export type { PoolRotation, ProxyCooldownEntry };
41
40
 
42
41
  // Pool selector
43
42
  /**
@@ -51,7 +50,7 @@ export { parseCooldownEntry };
51
50
  */
52
51
  export const PROVIDER_BLOCK_MS = 10 * 365 * 24 * 60 * 60 * 1000;
53
52
 
54
- export const DEFAULT_PROXY_CONCURRENCY = 10;
53
+ const DEFAULT_PROXY_CONCURRENCY = 10;
55
54
  const DEFAULT_PROXY_WEIGHT = 100;
56
55
  const MAX_PROXY_WEIGHT = 1000;
57
56
  // Crash-recovery bound, not a lease duration: normal releases DECR/DEL the
@@ -63,7 +62,7 @@ const MAX_PROXY_WEIGHT = 1000;
63
62
  function poolInflightTtlSeconds(): number {
64
63
  return resolveInflightTtlSeconds();
65
64
  }
66
- export function effectiveConcurrency(limit: number): number {
65
+ function effectiveConcurrency(limit: number): number {
67
66
  if (!Number.isInteger(limit) || limit < 1) return 0;
68
67
  return limit;
69
68
  }
@@ -100,7 +99,7 @@ function decrementLocal(counter: Map<string, number>, key: string): void {
100
99
  else counter.set(key, value - 1);
101
100
  }
102
101
 
103
- export type PoolSelectionFailureReason =
102
+ type PoolSelectionFailureReason =
104
103
  | "at_capacity"
105
104
  | "provider_at_capacity"
106
105
  | "cooldown"
@@ -109,7 +108,7 @@ export type PoolSelectionFailureReason =
109
108
  | "coordination_unavailable"
110
109
  | "no_active_pool";
111
110
 
112
- export interface PoolCapacitySnapshot {
111
+ interface PoolCapacitySnapshot {
113
112
  readonly poolId: string;
114
113
  readonly maxInflight: number;
115
114
  readonly currentInflight: number;
@@ -206,67 +205,67 @@ export class NetworkPoolSelector {
206
205
  }
207
206
 
208
207
  /**
209
- * Raise this provider's share of a pool, enforcing both the pool ceiling and
210
- * the provider ceiling. The pool half is still checked here so a caller that
211
- * already admitted the pool through `acquire` does not double-count it — use
212
- * this instead of `acquire` whenever a provider ceiling applies.
208
+ * One local slot acquisition: checks every map's ceiling, bumps each counter,
209
+ * and returns a release that decrements all of them exactly once. Shared by
210
+ * `acquire` (single pool counter) and `acquireProvider` (pool + provider
211
+ * share) so the release closure and the local bookkeeping live in one place.
213
212
  */
214
- acquireProvider(
215
- poolId: string,
216
- providerId: string,
217
- poolCapacity: number,
218
- providerCapacity: number,
213
+ private acquireLocalSlot(
214
+ increments: ReadonlyArray<{
215
+ readonly map: Map<string, number>;
216
+ readonly key: string;
217
+ readonly capacity: number;
218
+ }>,
219
219
  ): { acquired: boolean; release: () => void } {
220
- const current = this.getInflight(poolId);
221
- if (current >= poolCapacity) return { acquired: false, release: () => {} };
222
- const key = providerInflightStateKey(poolId, providerId);
223
- const providerCurrent = this.state.providerInflight.get(key) ?? 0;
224
- if (providerCurrent >= providerCapacity) return { acquired: false, release: () => {} };
225
-
226
- enforceInflightLimit(this.state.inflight);
227
- enforceInflightLimit(this.state.providerInflight);
228
- this.state.inflight.set(poolId, current + 1);
229
- this.state.providerInflight.set(key, providerCurrent + 1);
220
+ for (const increment of increments) {
221
+ const current = increment.map.get(increment.key) ?? 0;
222
+ if (current >= increment.capacity) return { acquired: false, release: () => {} };
223
+ }
224
+ for (const increment of increments) {
225
+ enforceInflightLimit(increment.map);
226
+ increment.map.set(increment.key, (increment.map.get(increment.key) ?? 0) + 1);
227
+ }
230
228
  this.notifyPoolUsage();
231
229
 
232
230
  let released = false;
233
231
  const release = () => {
234
232
  if (released) return;
235
233
  released = true;
236
- decrementLocal(this.state.inflight, poolId);
237
- decrementLocal(this.state.providerInflight, key);
234
+ for (const increment of increments) decrementLocal(increment.map, increment.key);
238
235
  this.notifyPoolUsage();
239
236
  };
240
237
  return { acquired: true, release };
241
238
  }
242
239
 
240
+ /**
241
+ * Raise this provider's share of a pool, enforcing both the pool ceiling and
242
+ * the provider ceiling. The pool half is still checked here so a caller that
243
+ * already admitted the pool through `acquire` does not double-count it — use
244
+ * this instead of `acquire` whenever a provider ceiling applies.
245
+ */
246
+ acquireProvider(
247
+ poolId: string,
248
+ providerId: string,
249
+ poolCapacity: number,
250
+ providerCapacity: number,
251
+ ): { acquired: boolean; release: () => void } {
252
+ return this.acquireLocalSlot([
253
+ { map: this.state.inflight, key: poolId, capacity: poolCapacity },
254
+ {
255
+ map: this.state.providerInflight,
256
+ key: providerInflightStateKey(poolId, providerId),
257
+ capacity: providerCapacity,
258
+ },
259
+ ]);
260
+ }
261
+
243
262
  acquire(
244
263
  poolId: string,
245
264
  maxInflight = DEFAULT_PROXY_CONCURRENCY,
246
265
  ): { acquired: boolean; release: () => void } {
247
- const current = this.getInflight(poolId);
248
- if (current >= maxInflight) {
249
- return { acquired: false, release: () => {} };
250
- }
251
-
252
- enforceInflightLimit(this.state.inflight);
253
- this.state.inflight.set(poolId, current + 1);
254
- this.notifyPoolUsage();
255
- let released = false;
256
-
257
- const release = () => {
258
- if (released) return;
259
- released = true;
260
- const val = this.state.inflight.get(poolId) ?? 1;
261
- if (val <= 1) {
262
- this.state.inflight.delete(poolId);
263
- } else {
264
- this.state.inflight.set(poolId, val - 1);
265
- }
266
- this.notifyPoolUsage();
267
- };
268
-
269
- return { acquired: true, release };
266
+ return this.acquireLocalSlot([
267
+ { map: this.state.inflight, key: poolId, capacity: maxInflight },
268
+ ]);
270
269
  }
271
270
 
272
271
  private cooldownKey(poolId: string, providerId: string): string {
@@ -577,6 +576,98 @@ export class NetworkPoolSelector {
577
576
  await this.redis.del(indexKey);
578
577
  }
579
578
 
579
+ /**
580
+ * Tries one local candidate: bump the local counters (pool, and provider
581
+ * share when a ceiling is set) and return its release. `undefined` when the
582
+ * candidate is full or the provider share is exhausted.
583
+ */
584
+ private localAcquire(
585
+ candidate: { readonly poolId: string; readonly capacity: number },
586
+ providerId: string,
587
+ providerCapacity: number | undefined,
588
+ ): { poolId: string; release: () => void } | undefined {
589
+ const slot =
590
+ providerCapacity === undefined
591
+ ? this.acquire(candidate.poolId, candidate.capacity)
592
+ : this.acquireProvider(candidate.poolId, providerId, candidate.capacity, providerCapacity);
593
+ return slot.acquired ? { poolId: candidate.poolId, release: slot.release } : undefined;
594
+ }
595
+
596
+ /**
597
+ * One atomic Redis admit (the compare-and-increment script) for a candidate:
598
+ * bumps both the remote and local counters on success and returns a release
599
+ * that decrements both. `undefined` when the pool or provider share is full
600
+ * (1 admitted; 0 pool full; 2 provider full — either full result fails over
601
+ * to the next pool, which is the whole point of the ceiling).
602
+ */
603
+ private async redisAdmit(
604
+ redis: RedisClient,
605
+ candidate: { readonly poolId: string; readonly capacity: number },
606
+ providerId: string,
607
+ providerCapacity: number | undefined,
608
+ ): Promise<{ poolId: string; release: () => void } | undefined> {
609
+ const providerKey =
610
+ providerCapacity === undefined
611
+ ? undefined
612
+ : proxyProviderInflightKey(candidate.poolId, providerId);
613
+ const result =
614
+ providerKey === undefined
615
+ ? await redisEvalNumber(
616
+ redis,
617
+ POOL_ADMIT_SCRIPT,
618
+ 1,
619
+ proxyInflightKey(candidate.poolId),
620
+ String(candidate.capacity),
621
+ String(poolInflightTtlSeconds()),
622
+ )
623
+ : await redisEvalNumber(
624
+ redis,
625
+ POOL_PROVIDER_ADMIT_SCRIPT,
626
+ 2,
627
+ proxyInflightKey(candidate.poolId),
628
+ providerKey,
629
+ String(candidate.capacity),
630
+ String(providerCapacity),
631
+ String(poolInflightTtlSeconds()),
632
+ );
633
+ if (result !== 1) return undefined;
634
+ enforceInflightLimit(this.state.inflight);
635
+ this.state.inflight.set(candidate.poolId, this.getInflight(candidate.poolId) + 1);
636
+ if (providerKey !== undefined) {
637
+ enforceInflightLimit(this.state.providerInflight);
638
+ const localProviderKey = providerInflightStateKey(candidate.poolId, providerId);
639
+ this.state.providerInflight.set(
640
+ localProviderKey,
641
+ this.getProviderInflight(candidate.poolId, providerId) + 1,
642
+ );
643
+ }
644
+ this.notifyPoolUsage();
645
+ let released = false;
646
+ return {
647
+ poolId: candidate.poolId,
648
+ release: () => {
649
+ if (released) return;
650
+ released = true;
651
+ decrementLocal(this.state.inflight, candidate.poolId);
652
+ if (providerKey !== undefined) {
653
+ decrementLocal(
654
+ this.state.providerInflight,
655
+ providerInflightStateKey(candidate.poolId, providerId),
656
+ );
657
+ }
658
+ this.notifyPoolUsage();
659
+ const script = providerKey === undefined ? POOL_RELEASE_SCRIPT : POOL_PROVIDER_RELEASE_SCRIPT;
660
+ const keys =
661
+ providerKey === undefined
662
+ ? [proxyInflightKey(candidate.poolId)]
663
+ : [proxyInflightKey(candidate.poolId), providerKey];
664
+ void redisEvalNumber(redis, script, keys.length, ...keys).catch((error: unknown) => {
665
+ log.error("[pool-selector] failed to release distributed slot", error as Error);
666
+ });
667
+ },
668
+ };
669
+ }
670
+
580
671
  /**
581
672
  * Distributed pool admission — the selection algorithm:
582
673
  *
@@ -652,17 +743,8 @@ export class NetworkPoolSelector {
652
743
  shuffleArray(scored);
653
744
  for (const candidate of scored) {
654
745
  if (this.getInflight(candidate.poolId) >= candidate.capacity) continue;
655
- const slot =
656
- providerCapacity === undefined
657
- ? this.acquire(candidate.poolId, candidate.capacity)
658
- : this.acquireProvider(
659
- candidate.poolId,
660
- providerId,
661
- candidate.capacity,
662
- providerCapacity,
663
- );
664
- if (!slot.acquired) continue;
665
- return { poolId: candidate.poolId, release: slot.release };
746
+ const slot = this.localAcquire(candidate, providerId, providerCapacity);
747
+ if (slot) return slot;
666
748
  }
667
749
  return undefined;
668
750
  }
@@ -677,18 +759,10 @@ export class NetworkPoolSelector {
677
759
  const candidate = scored[absolute];
678
760
  if (!candidate) continue;
679
761
  if (this.getInflight(candidate.poolId) >= candidate.capacity) continue;
680
- const slot =
681
- providerCapacity === undefined
682
- ? this.acquire(candidate.poolId, candidate.capacity)
683
- : this.acquireProvider(
684
- candidate.poolId,
685
- providerId,
686
- candidate.capacity,
687
- providerCapacity,
688
- );
689
- if (!slot.acquired) continue;
762
+ const slot = this.localAcquire(candidate, providerId, providerCapacity);
763
+ if (!slot) continue;
690
764
  advanceRotation(this.state.rotationCursors, rotation.key, scored.length, absolute, rotation.rotateCount);
691
- return { poolId: candidate.poolId, release: slot.release };
765
+ return slot;
692
766
  }
693
767
  return undefined;
694
768
  }
@@ -716,11 +790,7 @@ export class NetworkPoolSelector {
716
790
  }
717
791
  }
718
792
  if (!bestPool) return undefined;
719
- const slot =
720
- providerCapacity === undefined
721
- ? this.acquire(bestPool.poolId, bestPool.capacity)
722
- : this.acquireProvider(bestPool.poolId, providerId, bestPool.capacity, providerCapacity);
723
- return slot.acquired ? { poolId: bestPool.poolId, release: slot.release } : undefined;
793
+ return this.localAcquire(bestPool, providerId, providerCapacity);
724
794
  }
725
795
 
726
796
  const redis = this.redis;
@@ -795,34 +865,8 @@ export class NetworkPoolSelector {
795
865
  });
796
866
  }
797
867
  for (const candidate of withCounts) {
798
- const providerKey =
799
- providerCapacity === undefined
800
- ? undefined
801
- : proxyProviderInflightKey(candidate.poolId, providerId);
802
- const result =
803
- providerKey === undefined
804
- ? await redisEvalNumber(
805
- redis,
806
- POOL_ADMIT_SCRIPT,
807
- 1,
808
- proxyInflightKey(candidate.poolId),
809
- String(candidate.capacity),
810
- String(poolInflightTtlSeconds()),
811
- )
812
- : await redisEvalNumber(
813
- redis,
814
- POOL_PROVIDER_ADMIT_SCRIPT,
815
- 2,
816
- proxyInflightKey(candidate.poolId),
817
- providerKey,
818
- String(candidate.capacity),
819
- String(providerCapacity),
820
- String(poolInflightTtlSeconds()),
821
- );
822
- // 1 admitted; 0 pool full; 2 provider full. Either full result fails over
823
- // to the next pool, which is the whole point of the ceiling: the request
824
- // moves to an egress that still has room for this provider.
825
- if (result !== 1) continue;
868
+ const admitted = await this.redisAdmit(redis, candidate, providerId, providerCapacity);
869
+ if (admitted === undefined) continue;
826
870
  if (rotation && rotation.strategy !== "random") {
827
871
  advanceRotation(
828
872
  this.state.rotationCursors,
@@ -832,37 +876,7 @@ export class NetworkPoolSelector {
832
876
  rotation.rotateCount,
833
877
  );
834
878
  }
835
- enforceInflightLimit(this.state.inflight);
836
- this.state.inflight.set(candidate.poolId, this.getInflight(candidate.poolId) + 1);
837
- if (providerKey !== undefined) {
838
- enforceInflightLimit(this.state.providerInflight);
839
- const localProviderKey = providerInflightStateKey(candidate.poolId, providerId);
840
- this.state.providerInflight.set(
841
- localProviderKey,
842
- this.getProviderInflight(candidate.poolId, providerId) + 1,
843
- );
844
- }
845
- this.notifyPoolUsage();
846
- let released = false;
847
- return {
848
- poolId: candidate.poolId,
849
- release: () => {
850
- if (released) return;
851
- released = true;
852
- decrementLocal(this.state.inflight, candidate.poolId);
853
- if (providerKey !== undefined) {
854
- decrementLocal(this.state.providerInflight, providerInflightStateKey(candidate.poolId, providerId));
855
- }
856
- this.notifyPoolUsage();
857
- const script = providerKey === undefined ? POOL_RELEASE_SCRIPT : POOL_PROVIDER_RELEASE_SCRIPT;
858
- const keys = providerKey === undefined ? [proxyInflightKey(candidate.poolId)] : [proxyInflightKey(candidate.poolId), providerKey];
859
- void redisEvalNumber(redis, script, keys.length, ...keys).catch(
860
- (error: unknown) => {
861
- log.error("[pool-selector] failed to release distributed slot", error as Error);
862
- },
863
- );
864
- },
865
- };
879
+ return admitted;
866
880
  }
867
881
  return undefined;
868
882
  }
@@ -16,7 +16,7 @@ export interface PoolHealthEvent {
16
16
  readonly createdAt: string;
17
17
  }
18
18
 
19
- export interface PoolHealthSnapshot {
19
+ interface PoolHealthSnapshot {
20
20
  readonly poolId: string;
21
21
  readonly tenantId: string;
22
22
  readonly status: "active" | "cooldown" | "disabled";
@@ -21,7 +21,7 @@ import { GatewayError } from "../protocol/gateway-error";
21
21
  import { providerRateLimitIsIpScoped } from "../providers/provider-metadata";
22
22
 
23
23
  /** What one upstream failure means for the egress that carried it. */
24
- export type EgressVerdict =
24
+ type EgressVerdict =
25
25
  | { kind: "none" }
26
26
  | { kind: "cooldown"; retryAfterMs?: number }
27
27
  | { kind: "blocked" };
@@ -9,7 +9,7 @@
9
9
  import { exponentialBackoff, type BackoffOptions } from "./backoff";
10
10
  import { sleep as defaultSleep } from "../shared/sleep";
11
11
 
12
- export interface RetryOptions {
12
+ interface RetryOptions {
13
13
  readonly maxRetries?: number;
14
14
  readonly baseDelayMs?: number;
15
15
  readonly maxDelayMs?: number;
@@ -3,6 +3,12 @@ import { lookup } from "node:dns/promises";
3
3
  import { isIP } from "node:net";
4
4
  import { GatewayError } from "../protocol/gateway-error";
5
5
  import type { SsrfPolicy } from "../config/network";
6
+ import {
7
+ cidrContainsAddress,
8
+ ipv4ToNumberStrict,
9
+ ipv6GroupsToBytes,
10
+ normalizeIpv6Groups,
11
+ } from "./ip-utils";
6
12
 
7
13
  const DNS_CACHE_TTL_MS = 60_000;
8
14
  const DNS_CACHE_MAX = 64;
@@ -31,87 +37,24 @@ type DnsLookup = (
31
37
  options: { all: true },
32
38
  ) => Promise<readonly { address: string; family: number }[]>;
33
39
 
34
- function ipv4Number(address: string): number {
35
- const parts = address.split(".");
36
- if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part)))
37
- throw new Error("invalid IPv4");
38
- const octets = parts.map(Number);
39
- if (octets.some((part) => part < 0 || part > 255)) throw new Error("invalid IPv4");
40
- return (((octets[0]! << 24) >>> 0) | (octets[1]! << 16) | (octets[2]! << 8) | octets[3]!) >>> 0;
41
- }
42
-
43
40
  function ipv6Bytes(address: string): Uint8Array {
44
- const value = address.toLowerCase().split("%", 1)[0]!;
45
- if (value.includes(".")) {
46
- const split = value.lastIndexOf(":");
47
- if (split < 0) throw new Error("invalid IPv6");
48
- const mapped = ipv4Number(value.slice(split + 1));
49
- return ipv6Bytes(
50
- `${value.slice(0, split)}:${(mapped >>> 16).toString(16)}:${(mapped & 0xffff).toString(16)}`,
51
- );
52
- }
53
- const halves = value.split("::");
54
- if (halves.length > 2) throw new Error("invalid IPv6");
55
- const left = halves[0] ? halves[0].split(":") : [];
56
- const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
57
- if (halves.length === 1 && left.length !== 8) throw new Error("invalid IPv6");
58
- const missing = 8 - left.length - right.length;
59
- if (missing < 0 || (halves.length === 2 && missing < 1)) throw new Error("invalid IPv6");
60
- const groups = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
61
- if (groups.length !== 8 || groups.some((part) => !/^[0-9a-f]{1,4}$/.test(part)))
62
- throw new Error("invalid IPv6");
63
- const bytes = new Uint8Array(16);
64
- groups.forEach((part, index) => {
65
- const n = Number.parseInt(part, 16);
66
- bytes[index * 2] = n >>> 8;
67
- bytes[index * 2 + 1] = n & 255;
68
- });
41
+ const bytes = ipv6GroupsToBytes(normalizeIpv6Groups(address, { allowDottedQuad: true }) ?? []);
42
+ if (bytes === undefined) throw new Error("invalid IPv6");
69
43
  return bytes;
70
44
  }
71
45
 
72
46
  function cidrContains(address: string, cidr: string): boolean {
73
- const [network, prefixText] = cidr.split("/");
74
- if (!network || prefixText === undefined) return false;
75
- // A present-but-blank prefix (`"10.0.0.0/"`) must be refused, not coerced.
76
- // `Number("")` is 0 and `""` is not `undefined`, so a plain truthiness check
77
- // here would leave `prefix = 0` — and a /0 admits every address of its family.
78
- // An operator's typo would therefore silently widen the allowlist to the whole
79
- // IPv4 space, including the link-local metadata endpoint this guard exists to
80
- // refuse.
81
- //
82
- // Requiring a run of digits (after trimming) also refuses the exotic spellings
83
- // `Number` accepts for zero — `-0`, `+0`, `0x0`, `0b0`, `0o0`, `0.0` — all of
84
- // which are typos rather than a deliberate "allow everything". Only a written
85
- // `0` means that, which is the next branch's business.
86
- const prefixDigits = prefixText.trim();
87
- if (!/^\d+$/.test(prefixDigits)) return false;
88
- const prefix = Number(prefixDigits);
89
- const family = isIP(address);
90
- if (
91
- family !== isIP(network) ||
92
- !Number.isInteger(prefix) ||
93
- prefix < 0 ||
94
- prefix > (family === 4 ? 32 : 128)
95
- )
96
- return false;
97
- if (family === 4) {
98
- const bits = 32 - prefix;
99
- return bits === 32 ? true : ipv4Number(address) >>> bits === ipv4Number(network) >>> bits;
100
- }
101
- const a = ipv6Bytes(address);
102
- const n = ipv6Bytes(network);
103
- for (let index = 0; index < prefix; index++) {
104
- const byte = Math.floor(index / 8);
105
- const bit = 7 - (index % 8);
106
- if (((a[byte]! >> bit) & 1) !== ((n[byte]! >> bit) & 1)) return false;
107
- }
108
- return true;
47
+ return cidrContainsAddress(address, cidr, {
48
+ requirePrefix: true,
49
+ unwrapMapped: false,
50
+ allowDottedQuad: true,
51
+ });
109
52
  }
110
53
 
111
54
  function unsafeAddress(address: string): boolean {
112
55
  const family = isIP(address);
113
56
  if (family === 4) {
114
- const n = ipv4Number(address);
57
+ const n = ipv4ToNumberStrict(address);
115
58
  return [
116
59
  [0, 8],
117
60
  [0x0a000000, 8],
@@ -175,9 +118,15 @@ export function validateResolvedAddresses(
175
118
  return addresses[0]!;
176
119
  }
177
120
 
121
+ export async function resolveAllAddresses(hostname: string): Promise<string[]>;
178
122
  export async function resolveAllAddresses(
179
123
  hostname: string,
180
124
  signal: AbortSignal,
125
+ lookupFn?: DnsLookup,
126
+ ): Promise<string[]>;
127
+ export async function resolveAllAddresses(
128
+ hostname: string,
129
+ signal: AbortSignal = new AbortController().signal,
181
130
  lookupFn: DnsLookup = lookup,
182
131
  ): Promise<string[]> {
183
132
  if (signal.aborted) throw abortErrorForSignal(signal);
@@ -255,7 +255,7 @@ export async function readTelemetryEvents(): Promise<TelemetryEventRecord[]> {
255
255
  return events;
256
256
  }
257
257
 
258
- export interface EventQuery {
258
+ interface EventQuery {
259
259
  readonly tenantId?: string;
260
260
  readonly since?: Date;
261
261
  /**
@@ -372,7 +372,7 @@ export async function queryTelemetryEvents(query: EventQuery): Promise<Telemetry
372
372
  return events;
373
373
  }
374
374
 
375
- export interface EventTotals {
375
+ interface EventTotals {
376
376
  requests: number;
377
377
  succeeded: number;
378
378
  failed: number;