cartethyia 2.7.3 → 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 (625) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.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-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.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-BKj_r0rp.js +1 -0
  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-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.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-5SnUSy3j.js +9 -0
  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-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.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-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DMrMPmth.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-Be5Uzamx.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-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.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-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-Bm8WKaGY.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-q5HwZ8nN.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-BTScrQpe.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-Orjuzemr.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-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-CU1Rgf3p.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-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
  87. package/dist/dashboard/assets/{list-row-CzFvogbj.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/minimize-2-BTmMTK4o.js +1 -0
  92. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  94. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  95. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  96. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  97. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  98. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  99. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  100. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  101. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  102. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  103. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  104. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
  106. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
  108. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  109. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  110. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  111. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  112. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
  114. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
  116. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  117. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
  118. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  119. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
  120. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  121. package/dist/dashboard/console-backgrounds.jpg +0 -0
  122. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  123. package/dist/dashboard/index.html +2 -2
  124. package/dist/dashboard/index.html.gz +0 -0
  125. package/dist/dashboard/precompressed-assets.json +59 -57
  126. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  127. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  128. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  129. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  130. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  131. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  132. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  133. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  134. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  135. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  136. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  137. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  138. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  139. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  140. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  141. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  142. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  143. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  144. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  145. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  146. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  147. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  148. package/package.json +4 -5
  149. package/scripts/cli/update.ts +15 -41
  150. package/src/boot/app.ts +67 -10
  151. package/src/boot/dependencies.ts +786 -732
  152. package/src/boot/lifecycle.ts +11 -12
  153. package/src/config/cooldowns.ts +34 -55
  154. package/src/config/network.ts +8 -4
  155. package/src/config/package-paths.ts +1 -1
  156. package/src/config/providers.ts +13 -14
  157. package/src/config/resources.ts +0 -5
  158. package/src/config/runtime.ts +2 -5
  159. package/src/config/spec.ts +26 -6
  160. package/src/console/api-keys/ban-constants.ts +38 -0
  161. package/src/console/api-keys/contracts.ts +92 -31
  162. package/src/console/api-keys/routes.ts +131 -6
  163. package/src/console/audit/routes.ts +3 -3
  164. package/src/console/auth/access.ts +4 -4
  165. package/src/console/auth/account-reset.ts +5 -5
  166. package/src/console/auth/audit-recorder.ts +1 -1
  167. package/src/console/auth/lockout.ts +7 -3
  168. package/src/console/auth/row-coercion.ts +57 -12
  169. package/src/console/auth/session-resolver.ts +1 -1
  170. package/src/console/auth/session-service.ts +8 -2
  171. package/src/console/auth/session.ts +40 -34
  172. package/src/console/auth/setup-service.ts +4 -3
  173. package/src/console/auth/user-store.ts +1 -1
  174. package/src/console/backup/9router-map.ts +0 -3
  175. package/src/console/backup/contracts.ts +11 -9
  176. package/src/console/backup/export-endpoint.ts +11 -12
  177. package/src/console/backup/nine-router.ts +1 -7
  178. package/src/console/backup/routes.ts +32 -36
  179. package/src/console/backup/service.ts +468 -468
  180. package/src/console/backup/store.ts +47 -29
  181. package/src/console/backup/validate.ts +11 -1
  182. package/src/console/cli-tools/contracts.ts +7 -7
  183. package/src/console/cli-tools/injectors/cline.ts +110 -110
  184. package/src/console/cli-tools/injectors/codex.ts +181 -181
  185. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  186. package/src/console/cli-tools/injectors/driver.ts +247 -247
  187. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  188. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  189. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  190. package/src/console/cli-tools/routes.ts +4 -4
  191. package/src/console/cli-tools/service.ts +1 -1
  192. package/src/console/cli-tools/store.ts +2 -2
  193. package/src/console/console-router-guards.ts +2 -2
  194. package/src/console/console-router.ts +36 -4
  195. package/src/console/dashboard-assets.ts +9 -9
  196. package/src/console/data-management/routes.ts +3 -3
  197. package/src/console/domain-registration.ts +18 -4
  198. package/src/console/inferhub/client.ts +0 -7
  199. package/src/console/inferhub/key-store.ts +1 -1
  200. package/src/console/inferhub/normalize.ts +2 -2
  201. package/src/console/inferhub/routes.ts +1 -1
  202. package/src/console/inferhub/types.ts +1 -1
  203. package/src/console/observability/contracts.ts +5 -1
  204. package/src/console/observability/live.ts +34 -21
  205. package/src/console/observability/logs.ts +4 -4
  206. package/src/console/observability/performance.ts +3 -3
  207. package/src/console/observability/routes.ts +3 -3
  208. package/src/console/observability/sse.ts +62 -8
  209. package/src/console/observability/store.ts +36 -20
  210. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  211. package/src/console/providers/catalog/contracts.ts +82 -22
  212. package/src/console/providers/catalog/model-operations.ts +1 -1
  213. package/src/console/providers/catalog/provider-operations.ts +110 -17
  214. package/src/console/providers/catalog/public-model-store.ts +0 -0
  215. package/src/console/providers/catalog/routes.ts +30 -6
  216. package/src/console/providers/catalog/store-helpers.ts +349 -326
  217. package/src/console/providers/catalog/store.ts +1662 -1292
  218. package/src/console/providers/detail/routes.ts +4 -4
  219. package/src/console/providers/oauth/callback-listener.ts +100 -18
  220. package/src/console/providers/oauth/routes.ts +95 -16
  221. package/src/console/quota/account-quota-global.ts +41 -3
  222. package/src/console/quota/account-quota-shared.ts +10 -3
  223. package/src/console/quota/account-quota-tenant.ts +285 -67
  224. package/src/console/quota/account-test-egress.ts +4 -4
  225. package/src/console/quota/daily-growth-job.ts +308 -0
  226. package/src/console/quota/native-identity.ts +2 -2
  227. package/src/console/quota/quota-view.ts +1 -1
  228. package/src/console/remote-support/routes.ts +1 -1
  229. package/src/console/remote-support/service.ts +2 -2
  230. package/src/console/routing/model/contracts.ts +1 -1
  231. package/src/console/routing/model/routes.ts +3 -3
  232. package/src/console/routing/pools/contracts.ts +13 -10
  233. package/src/console/routing/pools/import-parse.ts +3 -3
  234. package/src/console/routing/pools/operations.ts +14 -7
  235. package/src/console/routing/pools/probe-verdict.ts +8 -3
  236. package/src/console/routing/pools/relay-deploy.ts +12 -12
  237. package/src/console/routing/pools/routes.ts +3 -3
  238. package/src/console/routing/pools/store.ts +21 -1
  239. package/src/console/settings/contracts.ts +29 -2
  240. package/src/console/settings/routes.ts +3 -3
  241. package/src/console/settings/store.ts +20 -0
  242. package/src/console/share/share-router.ts +112 -44
  243. package/src/console/share/share-stats.ts +109 -39
  244. package/src/console/share/share-usage.ts +1 -1
  245. package/src/console/shared/errors.ts +2 -2
  246. package/src/console/shared/object.ts +18 -0
  247. package/src/console/shared/read-through-cache.ts +42 -6
  248. package/src/console/shared/transaction.ts +14 -0
  249. package/src/console/system-prompt/contracts.ts +89 -12
  250. package/src/console/system-prompt/routes.ts +98 -45
  251. package/src/console/updates/apply-update.ts +244 -0
  252. package/src/console/updates/routes.ts +256 -98
  253. package/src/main.ts +11 -5
  254. package/src/network/http2-bidi.ts +1 -1
  255. package/src/network/ip-utils.ts +298 -0
  256. package/src/network/outbound-fetch.ts +76 -5
  257. package/src/network/pool/agent.ts +7 -6
  258. package/src/network/pool/byte-accounting.ts +1 -1
  259. package/src/network/pool/resolver.ts +55 -51
  260. package/src/network/pool/selector.ts +150 -136
  261. package/src/network/pool-cooldown-store.ts +71 -16
  262. package/src/network/pool-health-machine.ts +22 -2
  263. package/src/network/rate-limit-verdict.ts +68 -0
  264. package/src/network/retry.ts +1 -1
  265. package/src/network/ssrf.ts +24 -75
  266. package/src/observability/event-log.ts +2 -2
  267. package/src/observability/event-query.ts +286 -214
  268. package/src/observability/log-sanitize.ts +1 -1
  269. package/src/observability/metrics.ts +4 -4
  270. package/src/observability/payload-capture.ts +6 -5
  271. package/src/observability/payload-store.ts +1 -1
  272. package/src/observability/runtime-metrics.ts +1 -1
  273. package/src/observability/telemetry-buffer.ts +12 -3
  274. package/src/observability/telemetry-reconciliation.ts +3 -3
  275. package/src/observability/telemetry-status.ts +1 -1
  276. package/src/observability/token-speed.ts +1 -1
  277. package/src/persistence/connection-url.ts +1 -1
  278. package/src/persistence/db-handle.ts +1 -1
  279. package/src/persistence/db-pg.ts +2 -2
  280. package/src/persistence/db-sqlite.ts +10 -5
  281. package/src/persistence/dialect.ts +5 -1
  282. package/src/persistence/readiness.ts +19 -7
  283. package/src/persistence/redis.ts +1 -1
  284. package/src/persistence/schema.ts +177 -45
  285. package/src/persistence/shared-cache.ts +2 -2
  286. package/src/persistence/sort-order.ts +1 -1
  287. package/src/persistence/stores/api-key-store.ts +109 -4
  288. package/src/persistence/stores/share-store.ts +20 -8
  289. package/src/persistence/stores/telemetry-store.ts +1 -1
  290. package/src/persistence/system-prompt-store.ts +63 -18
  291. package/src/persistence/tenant-preferences.ts +1 -1
  292. package/src/persistence/ttl-cache.ts +3 -3
  293. package/src/persistence/usage-flush.ts +7 -2
  294. package/src/protocol/canonical-model.ts +1 -1
  295. package/src/protocol/codecs.ts +12 -16
  296. package/src/protocol/dsml.ts +3 -49
  297. package/src/protocol/gateway-error.ts +6 -3
  298. package/src/protocol/image-clamp.ts +2 -2
  299. package/src/protocol/image-generation.ts +2 -2
  300. package/src/protocol/reasoning-text.ts +1 -1
  301. package/src/protocol/reasoning.ts +7 -52
  302. package/src/protocol/registry.ts +50 -15
  303. package/src/protocol/request/chat.ts +46 -19
  304. package/src/protocol/request/codex.ts +1 -1
  305. package/src/protocol/request/messages.ts +46 -14
  306. package/src/protocol/request/responses.ts +19 -10
  307. package/src/protocol/response/chat.ts +27 -12
  308. package/src/protocol/response/codex.ts +2 -2
  309. package/src/protocol/response/gemini.ts +1 -1
  310. package/src/protocol/response/messages.ts +6 -6
  311. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  312. package/src/protocol/response/responses.ts +3 -3
  313. package/src/protocol/sse-decoder.ts +64 -4
  314. package/src/protocol/stop-reason.ts +2 -2
  315. package/src/protocol/think-split.ts +347 -0
  316. package/src/protocol/translation/attachment-integrity.ts +3 -3
  317. package/src/protocol/translation/cache-controls.ts +8 -20
  318. package/src/protocol/translation/capabilities.ts +8 -8
  319. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  320. package/src/protocol/translation/dialect/inband.ts +39 -0
  321. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  322. package/src/protocol/translation/dialect/qwen.ts +114 -0
  323. package/src/protocol/translation/dialect/resolver.ts +83 -0
  324. package/src/protocol/translation/dialect/rules.ts +93 -0
  325. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  326. package/src/protocol/translation/dialect/types.ts +58 -0
  327. package/src/protocol/translation/quirks.ts +18 -4
  328. package/src/protocol/translation/thinking.ts +12 -252
  329. package/src/protocol/translation/tool-id.ts +3 -3
  330. package/src/protocol/translation/tool-repair.ts +1 -1
  331. package/src/protocol/upstream/post-json.ts +6 -1
  332. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  333. package/src/providers/adapters/base-adapter.ts +1 -1
  334. package/src/providers/adapters/compatible-adapter.ts +44 -5
  335. package/src/providers/authentication/jwt-validator.ts +0 -5
  336. package/src/providers/authentication/oauth-client.ts +254 -253
  337. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  338. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  339. package/src/providers/bundled-discovery-providers.ts +6 -30
  340. package/src/providers/configured-openai-providers.ts +82 -97
  341. package/src/providers/configured-provider.ts +1 -1
  342. package/src/providers/default-registry.ts +23 -35
  343. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  344. package/src/providers/discovery/discovery-types.ts +0 -1
  345. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  346. package/src/providers/discovery/probe-phases.ts +50 -15
  347. package/src/providers/discovery/probing-service.ts +18 -20
  348. package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
  349. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  350. package/src/providers/integrations/anthropic.ts +4 -11
  351. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  352. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  353. package/src/providers/integrations/antigravity/antigravity.ts +15 -26
  354. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
  355. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  356. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  357. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  358. package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
  359. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  360. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  361. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  362. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
  363. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  364. package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
  365. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  366. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  367. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  368. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  369. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  370. package/src/providers/integrations/claude/claude.ts +3 -10
  371. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  372. package/src/providers/integrations/cline/cline.ts +2 -2
  373. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  374. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  375. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  376. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  377. package/src/providers/integrations/codex/codex.ts +14 -15
  378. package/src/providers/integrations/commandcode/commandcode.ts +76 -116
  379. package/src/providers/integrations/commandcode/quota.ts +132 -48
  380. package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
  381. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  382. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  383. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  384. package/src/providers/integrations/cursor/cursor.ts +53 -20
  385. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  386. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  387. package/src/providers/integrations/factory/factory.ts +13 -13
  388. package/src/providers/integrations/gemini.ts +11 -11
  389. package/src/providers/integrations/github/github-discovery.ts +55 -10
  390. package/src/providers/integrations/github/github-oauth.ts +6 -5
  391. package/src/providers/integrations/github/github.ts +47 -20
  392. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  393. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  394. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  395. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  396. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  397. package/src/providers/integrations/grok/grok.ts +88 -42
  398. package/src/providers/integrations/inferhub.ts +4 -11
  399. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  400. package/src/providers/integrations/kimi/kimi.ts +4 -11
  401. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  402. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  403. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  404. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  405. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  406. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  407. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  408. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  409. package/src/providers/integrations/kiro/kiro.ts +91 -78
  410. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  411. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  412. package/src/providers/integrations/muse/muse.ts +6 -9
  413. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  414. package/src/providers/integrations/opencode/provider.ts +17 -24
  415. package/src/providers/integrations/perplexity.ts +2 -2
  416. package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
  417. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  418. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  419. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  420. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  421. package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
  422. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  423. package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
  424. package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
  425. package/src/providers/integrations/qoder/qoder.ts +521 -63
  426. package/src/providers/integrations/shared/connect.ts +0 -54
  427. package/src/providers/integrations/shared/hash.ts +15 -0
  428. package/src/providers/integrations/shared/session-cache.ts +136 -0
  429. package/src/providers/integrations/tokenharbor.ts +2 -2
  430. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  431. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  432. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  433. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  434. package/src/providers/integrations/trae/trae.ts +59 -19
  435. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  436. package/src/providers/integrations/zcode/oauth.ts +1 -1
  437. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  438. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  439. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  440. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  441. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  442. package/src/providers/operations/account-error-classification.ts +131 -116
  443. package/src/providers/operations/account-health-service.ts +31 -39
  444. package/src/providers/operations/account-reset-service.ts +58 -1
  445. package/src/providers/operations/cli-platform.ts +31 -5
  446. package/src/providers/operations/client-version-resolver.ts +4 -6
  447. package/src/providers/operations/client-versions.ts +10 -7
  448. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  449. package/src/providers/operations/provider-catalog-service.ts +22 -6
  450. package/src/providers/operations/provider-credential-service.ts +35 -6
  451. package/src/providers/operations/upstream-deadline.ts +3 -3
  452. package/src/providers/provider-contracts.ts +65 -10
  453. package/src/providers/provider-credential-resolver.ts +1 -1
  454. package/src/providers/provider-metadata.ts +17 -2
  455. package/src/providers/provider-model-definition.ts +22 -1
  456. package/src/providers/provider-probe.ts +4 -2
  457. package/src/providers/provider-registry.ts +4 -4
  458. package/src/providers/provider-usage.ts +81 -16
  459. package/src/providers/quota/cache.ts +29 -0
  460. package/src/providers/quota/quota-contracts.ts +28 -10
  461. package/src/providers/quota/refresh.ts +29 -6
  462. package/src/providers/quota/snapshot.ts +94 -0
  463. package/src/providers/search/search-provider.ts +6 -8
  464. package/src/providers/search/search-providers.ts +2 -1
  465. package/src/security/abuse.ts +8 -8
  466. package/src/security/admission/reasons.ts +17 -17
  467. package/src/security/admission/service.ts +2 -2
  468. package/src/security/admission/ttl.ts +61 -61
  469. package/src/security/api-key-auth.ts +20 -8
  470. package/src/security/client-router-fingerprint.ts +4 -4
  471. package/src/security/credential-codec.ts +3 -3
  472. package/src/security/csrf.ts +2 -2
  473. package/src/security/ip-boundary.ts +16 -189
  474. package/src/security/model-access-rule.ts +1 -1
  475. package/src/security/outbound-headers.ts +18 -0
  476. package/src/shared/coerce.ts +19 -0
  477. package/src/shared/invoke-guard.ts +47 -0
  478. package/src/transport/contracts.ts +13 -4
  479. package/src/transport/dispatch/attempt-finalize.ts +5 -4
  480. package/src/transport/dispatch/attempt-loop.ts +10 -7
  481. package/src/transport/dispatch/capture-policy.ts +34 -37
  482. package/src/transport/dispatch/empty-stream.ts +28 -25
  483. package/src/transport/dispatch/generation.ts +33 -53
  484. package/src/transport/dispatch/leases.ts +8 -8
  485. package/src/transport/dispatch/native-shared.ts +127 -0
  486. package/src/transport/dispatch/proxy-request.ts +10 -8
  487. package/src/transport/dispatch/responses-compact.ts +35 -56
  488. package/src/transport/dispatch/retry-policy.ts +4 -67
  489. package/src/transport/dispatch/stream-bridge.ts +1 -1
  490. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  491. package/src/transport/dispatch/streaming/finalize.ts +11 -10
  492. package/src/transport/dispatch/streaming/stream.ts +7 -3
  493. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  494. package/src/transport/dispatch/systemone.ts +27 -55
  495. package/src/transport/dispatch/token-count.ts +49 -46
  496. package/src/transport/dispatch/websearch.ts +24 -50
  497. package/src/transport/drain-endpoint.ts +3 -3
  498. package/src/transport/failure-policy.ts +19 -6
  499. package/src/transport/guards/auth-guard.ts +6 -11
  500. package/src/transport/guards/body-policy.ts +7 -7
  501. package/src/transport/guards/error-lifecycle.ts +18 -3
  502. package/src/transport/guards/guard-errors.ts +7 -3
  503. package/src/transport/guards/headroom-guard.ts +2 -2
  504. package/src/transport/guards/pipeline.ts +7 -7
  505. package/src/transport/guards/readiness-guard.ts +16 -2
  506. package/src/transport/guards/request-context.ts +15 -20
  507. package/src/transport/request/preparer.ts +306 -27
  508. package/src/transport/request/rtk/compress-request.ts +3 -3
  509. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  510. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  511. package/src/transport/request/state.ts +5 -20
  512. package/src/transport/request/system-prompt-injection.ts +74 -11
  513. package/src/transport/request/tenant-preferences.ts +22 -0
  514. package/src/transport/routing/candidate-index.ts +2 -2
  515. package/src/transport/routing/round-robin.ts +1 -1
  516. package/src/transport/routing/route-catalog.ts +113 -19
  517. package/src/transport/routing/route-model.ts +131 -38
  518. package/src/transport/routing/router.ts +154 -39
  519. package/src/transport/shutdown-notice.ts +21 -9
  520. package/src/transport/surface/adapters.ts +3 -3
  521. package/src/transport/surface/chat/encode.ts +7 -7
  522. package/src/transport/surface/chat/parse.ts +7 -7
  523. package/src/transport/surface/completion.ts +3 -3
  524. package/src/transport/surface/content-parts.ts +1 -1
  525. package/src/transport/surface/dialects.ts +2 -2
  526. package/src/transport/surface/messages/encode.ts +1 -1
  527. package/src/transport/surface/messages/parse.ts +3 -3
  528. package/src/transport/surface/responses/adapter.ts +1 -1
  529. package/src/transport/surface/responses/errors.ts +0 -9
  530. package/src/transport/vision-handoff-cache.ts +58 -0
  531. package/src/transport/vision-handoff.ts +376 -0
  532. package/src/workers/account-identity-sweep.ts +2 -2
  533. package/src/workers/buddy-growth-tasks.ts +392 -346
  534. package/src/workers/daily-checkin.ts +102 -49
  535. package/src/workers/maintenance-lease.ts +2 -35
  536. package/src/workers/oauth-refresh-sweep.ts +8 -1
  537. package/src/workers/quota-refresh-sweep.ts +120 -82
  538. package/src/workers/scheduler.ts +67 -4
  539. package/src/workers/sweep-runner.ts +24 -5
  540. package/src/workers/zcode-gift-claim.ts +108 -0
  541. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  542. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  543. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  544. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  545. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  546. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
  547. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  548. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  549. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  550. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  551. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  552. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  553. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  554. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  555. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  556. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  557. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  558. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  559. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  560. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  561. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  562. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  563. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  564. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  565. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  566. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  567. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  568. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  569. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  570. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  571. package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
  572. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  573. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  574. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  575. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  576. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  577. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  578. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  579. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  580. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  581. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  582. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  583. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  584. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  585. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  586. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  587. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  588. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  589. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  590. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  591. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  592. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  593. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  594. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  595. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  596. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  597. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  598. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  599. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  600. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  601. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  602. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  603. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  604. package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
  605. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  606. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  607. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  608. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  609. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  610. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  611. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  612. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  613. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  614. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  615. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  616. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  617. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  618. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  619. package/src/network/response-headers.ts +0 -39
  620. package/src/protocol/response/dsml.ts +0 -38
  621. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  622. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
  623. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  624. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  625. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -3,185 +3,25 @@
3
3
  import { isIP } from "node:net";
4
4
  import type { TrustedProxyBoundary } from "../config/network";
5
5
  import { log } from "../observability/logger";
6
+ import {
7
+ cidrContainsAddress,
8
+ ipv4ToNumber,
9
+ ipv6ToBigInt,
10
+ isLoopbackAddress,
11
+ unwrapMappedIpv4,
12
+ } from "../network/ip-utils";
6
13
 
7
- function ipv4ToNumber(address: string): number | undefined {
8
- const parts = address.split(".");
9
- if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part))) return undefined;
10
- const octets = parts.map(Number);
11
- if (octets.some((part) => part < 0 || part > 255)) return undefined;
12
- const [a, b, c, d] = octets;
13
- if (a === undefined || b === undefined || c === undefined || d === undefined) return undefined;
14
- return ((a * 256 + b) * 256 + c) * 256 + d;
15
- }
16
-
17
- function ipv6ToBigInt(address: string): bigint | undefined {
18
- const value = address.toLowerCase().split("%", 1)[0] ?? "";
19
- if (value.includes(".")) return undefined;
20
- const halves = value.split("::");
21
- if (halves.length > 2) return undefined;
22
- const left = halves[0] ? halves[0].split(":") : [];
23
- const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
24
- if (halves.length === 1 && left.length !== 8) return undefined;
25
- const missing = 8 - left.length - right.length;
26
- if (missing < 0 || (halves.length === 2 && missing < 1)) return undefined;
27
- const groups = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
28
- if (groups.length !== 8 || groups.some((part) => !/^[0-9a-f]{1,4}$/.test(part)))
29
- return undefined;
30
- let result = 0n;
31
- for (const group of groups) result = (result << 16n) | BigInt(Number.parseInt(group, 16));
32
- return result;
33
- }
34
-
35
- /**
36
- * Unwraps an IPv4-mapped IPv6 address to plain IPv4.
37
- *
38
- * Bun reports an IPv4 loopback peer as `::ffff:127.0.0.1` on Windows, and
39
- * `isIP` classifies it as IPv6 — so `ipv6ToBigInt` (which bails on a dotted
40
- * quad) never matched `127.0.0.1/32`. The trust check therefore failed for the
41
- * exact peer `TRUSTED_PROXY_CIDRS=127.0.0.1/32` is meant to cover, forwarded
42
- * headers were ignored, and every cloudflared request stored the loopback peer
43
- * as the client address. Accepts both the dotted and the hex (`::ffff:7f00:1`)
44
- * spelling.
45
- */
46
- function unwrapMappedIpv4(address: string): string {
47
- const value = address.toLowerCase().split("%", 1)[0] ?? "";
48
- // Recognize the mapped range STRUCTURALLY, not by its compressed spelling. The
49
- // old `startsWith("::ffff:")` test missed any form that writes the leading zero
50
- // groups out — `0:0:0:0:0:ffff:7f00:1` is the same host as `::ffff:127.0.0.1`,
51
- // but it failed the prefix test, fell through to `ipv6ToBigInt`, and was treated
52
- // as an unrelated IPv6 address. The trust check then refused a peer the allowlist
53
- // covered, and `canonicalClientIpKey` minted a second key for one host — which is
54
- // stored on issued child keys for consistent usage reporting and audit.
55
- //
56
- // A mapped address is `::ffff:<32 bits>`, i.e. the first 80 bits are zero and the
57
- // next 16 are `0xffff`. Matching that is the same test `isIP` uses internally.
58
- const tail = mappedIpv4Tail(value);
59
- if (tail === undefined) return address;
60
- // `mappedIpv4Tail` always returns two hex groups, so the dotted and hex
61
- // spellings have already converged by here.
62
- const groups = tail.split(":");
63
- if (groups.length !== 2) return address;
64
- const high = Number.parseInt(groups[0] ?? "", 16);
65
- const low = Number.parseInt(groups[1] ?? "", 16);
66
- if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 0xffff || low < 0 || low > 0xffff)
67
- return address;
68
- return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
69
- }
70
-
71
- /**
72
- * The 32-bit tail of an IPv4-mapped IPv6 address, or `undefined` when `value` is
73
- * not one. Accepts every spelling of the same address: `::ffff:1.2.3.4`,
74
- * `::ffff:102:304`, `0:0:0:0:0:ffff:102:304`, and `::ffff:0:102:304`.
75
- *
76
- * Works by normalizing to exactly eight 16-bit groups first. That is the only way
77
- * to compare spellings: a dotted tail is written as ONE colon-separated token but
78
- * occupies TWO group slots, so any check that counts `split(":")` entries gets the
79
- * position of the `ffff` marker wrong.
80
- */
81
- function mappedIpv4Tail(value: string): string | undefined {
82
- const groups = normalizeIpv6Groups(value);
83
- if (groups === undefined) return undefined;
84
- // The first five groups (80 bits) must be zero, and the sixth must be `ffff`.
85
- for (let index = 0; index < 5; index += 1) {
86
- if (groups[index] !== "0") return undefined;
87
- }
88
- if (groups[5] !== "ffff") return undefined;
89
- const high = groups[6];
90
- const low = groups[7];
91
- if (high === undefined || low === undefined) return undefined;
92
- return `${high}:${low}`;
93
- }
94
-
95
- /**
96
- * An IPv6 literal as exactly eight lowercase 16-bit hex groups, or `undefined`
97
- * when it is not one. Expands a `::` run and converts a trailing dotted quad into
98
- * the two groups it represents.
99
- */
100
- function normalizeIpv6Groups(value: string): string[] | undefined {
101
- const text = value.trim();
102
- if (text.length === 0) return undefined;
103
- const doubleIndex = text.indexOf("::");
104
- // More than one `::` is invalid, as is a `::` inside a group.
105
- if (doubleIndex !== text.lastIndexOf("::")) return undefined;
106
-
107
- const headText = doubleIndex >= 0 ? text.slice(0, doubleIndex) : text;
108
- const tailText = doubleIndex >= 0 ? text.slice(doubleIndex + 2) : "";
109
- const head = headText.length > 0 ? headText.split(":") : [];
110
- const tail = tailText.length > 0 ? tailText.split(":") : [];
111
-
112
- // A dotted quad is a single token that fills two groups.
113
- const expandDotted = (tokens: string[]): string[] | undefined => {
114
- const last = tokens[tokens.length - 1];
115
- if (last === undefined || !last.includes(".")) return tokens;
116
- const quad = last.split(".");
117
- if (quad.length !== 4) return undefined;
118
- const octets = quad.map((part) => Number(part));
119
- if (octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return undefined;
120
- const [a, b, c, d] = octets;
121
- if (a === undefined || b === undefined || c === undefined || d === undefined) return undefined;
122
- const high = ((a << 8) | b).toString(16);
123
- const low = ((c << 8) | d).toString(16);
124
- return [...tokens.slice(0, -1), high, low];
125
- };
126
-
127
- const expandedHead = expandDotted(head);
128
- const expandedTail = expandDotted(tail);
129
- if (expandedHead === undefined || expandedTail === undefined) return undefined;
130
-
131
- let full: string[];
132
- if (doubleIndex >= 0) {
133
- const missing = 8 - expandedHead.length - expandedTail.length;
134
- // `::` must stand for at least one group, so exactly-8 without it is invalid.
135
- if (missing < 1) return undefined;
136
- full = [...expandedHead, ...Array.from({ length: missing }, () => "0"), ...expandedTail];
137
- } else {
138
- full = expandedHead;
139
- }
140
- if (full.length !== 8) return undefined;
141
- const normalized: string[] = [];
142
- for (const group of full) {
143
- if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;
144
- normalized.push(Number.parseInt(group, 16).toString(16));
145
- }
146
- return normalized;
147
- }
14
+ export { isLoopbackAddress };
148
15
 
149
16
  function matchesCidr(address: string, cidr: string): boolean {
150
- const [network, rawPrefix] = cidr.split("/");
151
- if (!network) return false;
152
- // A bare address (`"127.0.0.1"`) has no prefix and means "this exact host". A
153
- // present-but-blank prefix (`"10.0.0.0/"`) must NOT be treated as /0: `Number("")`
154
- // is 0, and /0 matches every address — which made `isTrustedProxyPeer` true for
155
- // ANY peer, so the gateway believed a caller's own `X-Forwarded-For` and every
156
- // per-address decision downstream (abuse throttling, model bans, the audit
157
- // trail) read a forged value.
158
- //
159
- // Requiring a run of digits also refuses the exotic spellings `Number` accepts
160
- // for zero (`-0`, `+0`, `0x0`, `0b0`, `0o0`, `0.0`), all of which are typos
161
- // rather than a deliberate "trust everyone".
162
- const prefixText = rawPrefix === undefined ? undefined : rawPrefix.trim();
163
- if (prefixText !== undefined && !/^\d+$/.test(prefixText)) return false;
164
- // A mapped peer and a plain-IPv4 allowlist entry describe the same host.
165
- const subject = unwrapMappedIpv4(address);
166
- const base = unwrapMappedIpv4(network);
167
- const family = isIP(subject);
168
- if (family === 4) {
169
- const value = ipv4ToNumber(subject);
170
- const networkValue = ipv4ToNumber(base);
171
- if (value === undefined || networkValue === undefined) return false;
172
- const prefix = prefixText === undefined ? 32 : Number(prefixText);
173
- if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) return false;
174
- const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
175
- return ((value >>> 0) & mask) === ((networkValue >>> 0) & mask);
176
- }
177
- if (family !== 6) return false;
178
- const value = ipv6ToBigInt(subject);
179
- const networkValue = ipv6ToBigInt(base);
180
- if (value === undefined || networkValue === undefined) return false;
181
- const prefix = prefixText === undefined ? 128 : Number(prefixText);
182
- if (!Number.isInteger(prefix) || prefix < 0 || prefix > 128) return false;
183
- const shift = BigInt(128 - prefix);
184
- return (value >> shift) === (networkValue >> shift);
17
+ // `unwrapMapped: true`: a mapped peer and a plain-IPv4 allowlist entry
18
+ // describe the same host (Bun reports the IPv4 loopback peer as
19
+ // `::ffff:127.0.0.1` on Windows). `requirePrefix: false`: a bare address
20
+ // (`"127.0.0.1"`) means "this exact host".
21
+ return cidrContainsAddress(address, cidr, {
22
+ requirePrefix: false,
23
+ unwrapMapped: true,
24
+ });
185
25
  }
186
26
 
187
27
  function isTrustedPeer(peer: string, boundary: TrustedProxyBoundary): boolean {
@@ -206,20 +46,7 @@ const CLIENT_IDENTITY_HEADERS = [
206
46
 
207
47
  /**
208
48
  * True when `address` is a loopback address.
209
- *
210
- * Structural, not a string-prefix test: `127.0.0.1`, `::1`, and the mapped
211
- * `::ffff:127.0.0.1` a Windows/Bun peer arrives as are all the same host, and
212
- * a prefix test would miss the mapped form — the exact shape that hid this
213
- * class of bug before.
214
49
  */
215
- export function isLoopbackAddress(address: string): boolean {
216
- const normalized = unwrapMappedIpv4(address.trim().split("%", 1)[0] ?? "");
217
- if (normalized === "::1") return true;
218
- if (isIP(normalized) !== 4) return false;
219
- const value = ipv4ToNumber(normalized);
220
- // 127.0.0.0/8 — the whole loopback block, not just 127.0.0.1.
221
- return value !== undefined && ((value >>> 0) & 0xff000000) === (127 << 24);
222
- }
223
50
 
224
51
  /**
225
52
  * Whether a peer may vouch for forwarded headers *for client-address
@@ -24,7 +24,7 @@ import type { ApiKeyModelAccessMode } from "../persistence/schema";
24
24
  export type ModelRejectionReason = "model-denied" | "model-not-allowed";
25
25
 
26
26
  /** The subset of an authorization snapshot this rule reads. */
27
- export interface ModelAccessPolicy {
27
+ interface ModelAccessPolicy {
28
28
  readonly model_access_mode?: ApiKeyModelAccessMode | null | undefined;
29
29
  readonly model_list?: readonly string[] | ReadonlySet<string> | null | undefined;
30
30
  }
@@ -21,6 +21,24 @@ export const X_FRAME_OPTIONS = "DENY";
21
21
  */
22
22
  export const BADGE_IMAGE_ORIGIN = "https://img.shields.io";
23
23
 
24
+ /**
25
+ * Invariant: upstream response headers are NEVER forwarded wholesale to
26
+ * callers. There is no denylist-and-forward step on the egress path — the
27
+ * success projection (`proxySuccessHeaders` in
28
+ * `transport/dispatch/upstream.ts`) rebuilds the caller-facing header set from
29
+ * scratch (`cache-control: no-store`, `x-request-id`, and
30
+ * `GATEWAY_SECURITY_HEADERS` below), so credential-bearing upstream fields
31
+ * (`set-cookie`, `www-authenticate`, `proxy-authorization`) and hop-by-hop
32
+ * headers can never leak through by omission from a strip list.
33
+ *
34
+ * Any future feature that wants to pass upstream response fields to a caller
35
+ * must copy only the specific content-bearing headers it needs — do not
36
+ * reintroduce a "forward everything except" filter. A module implementing
37
+ * exactly that (`src/network/response-headers.ts` + `stripResponseHeaders`)
38
+ * existed once and was deleted as dead code (see git history for it), because
39
+ * every egress path had already moved to the rebuild-from-scratch projection.
40
+ */
41
+
24
42
  /**
25
43
  * Security headers on every `/v1` response, whichever path produced it.
26
44
  *
@@ -13,6 +13,25 @@ export function asRecord(value: unknown): Record<string, unknown> | undefined {
13
13
  : undefined;
14
14
  }
15
15
 
16
+ /**
17
+ * Narrow a stored `auth_state`/JSONB value to a plain object. Postgres'
18
+ * drizzle `jsonb` arrives as a POJO; SQLite's text-backed column arrives as
19
+ * a JSON STRING. Parse the string form so both backends hand downstream
20
+ * readers the same shape — otherwise auth-state-dependent providers (Qoder's
21
+ * userId/machineId, Trae's region, …) silently lose their identity on the
22
+ * SQLite backend.
23
+ */
24
+ export function asJsonbRecord(value: unknown): Record<string, unknown> | undefined {
25
+ if (typeof value === "string") {
26
+ try {
27
+ return asRecord(JSON.parse(value));
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ return asRecord(value);
33
+ }
34
+
16
35
  /** Narrow to a finite number, else `undefined`. `NaN`/`Infinity` are not numbers here. */
17
36
  export function asNumber(value: unknown): number | undefined {
18
37
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * InvokeGuard — a process-local foreground-activity gauge (G-03).
3
+ *
4
+ * Every foreground request path signals its activity here: the console control
5
+ * plane bumps the counter for the duration of each request, and the proxy data
6
+ * plane exposes its in-flight count through `setProxyBusySource`. The scheduled
7
+ * worker registry (and the sweep runner between waves) consults
8
+ * `foregroundBusy()` before starting background work, so a page open / an
9
+ * invoke in progress is never forced to share the single event loop with a
10
+ * heavy sweep.
11
+ *
12
+ * The defer policy is at the call sites (scheduler: bounded pause before a
13
+ * tick; sweep runner: bounded pause between waves). This module only owns the
14
+ * gauge — it never decides when to pause.
15
+ */
16
+
17
+ /** Console request counter. Bumped per in-flight console request. */
18
+ let consoleActivity = 0;
19
+
20
+ /** Optional live source of "the proxy plane is busy" (the in-flight store). */
21
+ let proxyBusySource: (() => boolean) | undefined;
22
+
23
+ /** Registers the proxy plane's busy source (wired once from the app root). */
24
+ export function setProxyBusySource(source: () => boolean): void {
25
+ proxyBusySource = source;
26
+ }
27
+
28
+ /** Marks a foreground console request; returns the matching end callback. */
29
+ export function beginConsoleActivity(): () => void {
30
+ consoleActivity += 1;
31
+ let ended = false;
32
+ return () => {
33
+ if (ended) return;
34
+ ended = true;
35
+ consoleActivity = Math.max(0, consoleActivity - 1);
36
+ };
37
+ }
38
+
39
+ /** Live foreground-request count (console counter + proxy source if wired). */
40
+ export function foregroundActivityCount(): number {
41
+ return consoleActivity + (proxyBusySource?.() === true ? 1 : 0);
42
+ }
43
+
44
+ /** True when any foreground work is in flight right now. */
45
+ export function foregroundBusy(): boolean {
46
+ return consoleActivity > 0 || (proxyBusySource?.() ?? false);
47
+ }
@@ -54,12 +54,12 @@ export interface CanonicalAdapter {
54
54
  */
55
55
 
56
56
  /** One native service: the path it is served at and the kind its models carry. */
57
- export interface NativeServiceRoute {
57
+ interface NativeServiceRoute {
58
58
  readonly serviceKind: Exclude<ServiceKind, "llm">;
59
59
  readonly path: string;
60
60
  }
61
61
 
62
- export const NATIVE_SERVICES: readonly NativeServiceRoute[] = [
62
+ const NATIVE_SERVICES: readonly NativeServiceRoute[] = [
63
63
  { serviceKind: "systemone", path: "/v1/systemone" },
64
64
  { serviceKind: "websearch", path: "/v1/search" },
65
65
  { serviceKind: "imageGeneration", path: "/v1/images/generations" },
@@ -75,7 +75,7 @@ export const NATIVE_SERVICE_PATHS: readonly string[] = NATIVE_SERVICES.map(
75
75
  );
76
76
 
77
77
  /** Whether `path` is served by a native service route. */
78
- export function isNativeServicePath(path: string | undefined): boolean {
78
+ function isNativeServicePath(path: string | undefined): boolean {
79
79
  return path !== undefined && NATIVE_SERVICE_PATHS.includes(path);
80
80
  }
81
81
 
@@ -97,6 +97,15 @@ export function isNativeServicePath(path: string | undefined): boolean {
97
97
  */
98
98
  export const TOKEN_COUNT_PATH = "/v1/messages/count_tokens";
99
99
 
100
+ /**
101
+ * The native Codex compaction route (`POST /v1/responses/compact`).
102
+ *
103
+ * Not a native service (it has no `serviceKind` of its own — Codex rows are
104
+ * `llm`-adjacent), so it is declared separately from `NATIVE_SERVICE_PATHS`,
105
+ * but its body is equally opaque and must skip the canonical chat stage.
106
+ */
107
+ export const RESPONSES_COMPACT_PATH = "/v1/responses/compact";
108
+
100
109
  /**
101
110
  * Whether `path` reads a JSON body that is *not* a canonical chat request.
102
111
  *
@@ -105,7 +114,7 @@ export const TOKEN_COUNT_PATH = "/v1/messages/count_tokens";
105
114
  * will either reject a body the upstream accepts or plan it twice.
106
115
  */
107
116
  export function isOpaqueBodyRoute(path: string | undefined): boolean {
108
- return isNativeServicePath(path) || path === TOKEN_COUNT_PATH;
117
+ return isNativeServicePath(path) || path === TOKEN_COUNT_PATH || path === RESPONSES_COMPACT_PATH;
109
118
  }
110
119
 
111
120
  /** The route path serving `serviceKind`, when one is declared. */
@@ -394,7 +394,7 @@ let captureFailureReported = false;
394
394
  * deployment condition that does not change per request, so repeating it for
395
395
  * every request would bury the rest of the log without adding information.
396
396
  */
397
- export function reportCaptureFailure(error: unknown): void {
397
+ function reportCaptureFailure(error: unknown): void {
398
398
  if (captureFailureReported) return;
399
399
  captureFailureReported = true;
400
400
  const directory = payloadDirectory();
@@ -425,7 +425,7 @@ export function terminalFailure(
425
425
  return new GatewayError(
426
426
  "transport_unavailable",
427
427
  502,
428
- "The upstream closed the response without reporting a result. Retry; if it keeps happening, the account or pool is unstable.",
428
+ "The upstream closed the response without reporting a result (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻",
429
429
  {},
430
430
  "upstream",
431
431
  );
@@ -437,7 +437,7 @@ export function terminalFailure(
437
437
  const details: Record<string, unknown> = {};
438
438
  if (terminal.provider_stop_reason) details["provider_code"] = terminal.provider_stop_reason;
439
439
  if (terminal.stop_details) Object.assign(details, terminal.stop_details);
440
- const message = typeof details["message"] === "string" ? details["message"] : "upstream request failed";
440
+ const message = typeof details["message"] === "string" ? details["message"] : "upstream request failed (request {requestId}). The upstream came back empty-handed — retry 🌙";
441
441
  // Preserve the upstream's error code when the provider embedded it in
442
442
  // `stop_details` (Cursor writes `quota_exceeded` and `authentication_failed`
443
443
  // there). Wrapping every terminal failure as `transport_unavailable` discarded
@@ -460,7 +460,7 @@ export function terminalFailure(
460
460
  // reports it as truncation instead.
461
461
  return options.truncated === true
462
462
  ? undefined
463
- : new GatewayError("transport_closed", 499, "request was cancelled");
463
+ : new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
464
464
  }
465
465
  return undefined;
466
466
  }
@@ -823,6 +823,7 @@ export async function completeAttempt(
823
823
  (completion.error instanceof ProxyConnectRefusalError ? "network" : undefined);
824
824
  await recordPoolDispatchOutcome(completion.db, completion.networkPoolId, {
825
825
  succeeded: completion.status === "completed",
826
+ ...(completion.providerId === undefined ? {} : { providerId: completion.providerId }),
826
827
  ...(completion.error === undefined ? {} : { error: completion.error }),
827
828
  // A CONNECT refusal is the proxy's own answer, given before any
828
829
  // upstream was dialed, so it is a network fault even though the
@@ -41,7 +41,8 @@ import {
41
41
  type ProviderExchangeCapture,
42
42
  } from "./attempt-finalize";
43
43
  import { repriceUsage } from "../../providers/provider-usage";
44
- import { isFreeTierIpBlock, isOAuthCredentialInvalidated, shouldCooldownPool } from "./retry-policy";
44
+ import { classifyEgressFailure } from "../../network/rate-limit-verdict";
45
+ import { isOAuthCredentialInvalidated } from "./retry-policy";
45
46
  import { drainAbortReason } from "../shutdown-notice";
46
47
 
47
48
  /**
@@ -316,7 +317,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
316
317
  };
317
318
  try {
318
319
  if (state.abortController.signal.aborted)
319
- throw new GatewayError("transport_closed", 499, "request was cancelled");
320
+ throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
320
321
  const { credential, adapter } = await input.prepare(candidate);
321
322
  const host = input.resolveHost?.(candidate);
322
323
  // Single attempt-lease manager owns acquisition order (admission →
@@ -353,9 +354,10 @@ export async function runAttemptLoop<TResult, TAdapter>(
353
354
  });
354
355
  } catch (error) {
355
356
  // A drain aborts the controller, so a mid-flight attempt unwinds here as
356
- // an abort. Surface the typed shutdown error (503 shutting_down /
357
- // restart_for_update) instead of an opaque `transport_closed`, so the
358
- // client's JSON error — not just the SSE path — names the real cause.
357
+ // an abort. Surface the typed shutdown error (503 shutting_down, reason
358
+ // restart_for_update for an update drain) instead of an opaque
359
+ // `transport_closed`, so the client's JSON error — not just the SSE path —
360
+ // names the real cause.
359
361
  const drain = drainAbortReason(state.abortController.signal.reason);
360
362
  if (drain !== undefined) error = drain;
361
363
  lastError = error;
@@ -374,11 +376,12 @@ export async function runAttemptLoop<TResult, TAdapter>(
374
376
  // single candidate carrying every pool). Judging it there is what made a
375
377
  // refused egress end the request instead of failing over to a healthy
376
378
  // one.
377
- const rateLimitedPool = !cancelled && shouldCooldownPool(error, candidate.provider_id);
379
+ const egressVerdict = !cancelled ? classifyEgressFailure(error, candidate.provider_id) : { kind: "none" } as const;
378
380
  // A free-tier refusal is only ever this provider's: the guard is gated on
379
381
  // `ipScopedRateLimit` in the provider metadata, so a 403 from any other
380
382
  // upstream still means what it always did and never retires an egress.
381
- const blockedPool = !cancelled && isFreeTierIpBlock(error, candidate.provider_id);
383
+ const rateLimitedPool = egressVerdict.kind === "cooldown";
384
+ const blockedPool = egressVerdict.kind === "blocked";
382
385
  const poolScopedFailure =
383
386
  (rateLimitedPool || blockedPool) &&
384
387
  networkPoolId !== undefined &&
@@ -1,8 +1,35 @@
1
1
  // Tenant payload-capture policy resolved once and shared by dispatch attempts.
2
2
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
3
- import { payloadRetentionMs, resolvePayloadCapture } from "../../observability/payload-capture";
3
+ import { resolvePayloadCapture } from "../../observability/payload-capture";
4
4
  import type { TelemetryPayloadDepth, TelemetryPayloadMode } from "../../console/settings/contracts";
5
5
 
6
+ /** The resolved payload-capture preference, memoized on the request state. */
7
+ export type PayloadCapturePreference =
8
+ | {
9
+ readonly ok: true;
10
+ readonly mode: TelemetryPayloadMode;
11
+ readonly depth: TelemetryPayloadDepth;
12
+ readonly retentionMs: number;
13
+ }
14
+ | { readonly ok: false };
15
+
16
+ /** The slice of request state that memoizes the capture preference. */
17
+ export type CapturePreferenceState = { payloadCapturePreference?: PayloadCapturePreference };
18
+
19
+ /** Resolves the tenant's capture preference once, memoized on the request state. */
20
+ async function resolveCapturePreference(
21
+ db: CartethyiaDatabase,
22
+ tenantId: string | null,
23
+ state: CapturePreferenceState,
24
+ ): Promise<PayloadCapturePreference> {
25
+ let preference = state.payloadCapturePreference;
26
+ if (preference === undefined) {
27
+ preference = await resolvePayloadCapture(db, tenantId);
28
+ state.payloadCapturePreference = preference;
29
+ }
30
+ return preference;
31
+ }
32
+
6
33
  /** Per-stage body cap for the provider request/response tee. */
7
34
  export function providerCaptureMaxBytes(depth?: TelemetryPayloadDepth): number {
8
35
  if (depth === "moderate") return 2 * 1024 * 1024;
@@ -14,14 +41,9 @@ export function providerCaptureMaxBytes(depth?: TelemetryPayloadDepth): number {
14
41
  export async function shouldCaptureProviderBodyForRequest(
15
42
  db: CartethyiaDatabase,
16
43
  tenantId: string | null,
17
- state: { payloadCapturePreference?: unknown },
44
+ state: CapturePreferenceState,
18
45
  ): Promise<boolean> {
19
- if (state.payloadCapturePreference === undefined) {
20
- state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
21
- }
22
- const resolved = state.payloadCapturePreference as
23
- | { readonly ok: true; readonly mode: string }
24
- | { readonly ok: false };
46
+ const resolved = await resolveCapturePreference(db, tenantId, state);
25
47
  return resolved.ok === true && resolved.mode === "full";
26
48
  }
27
49
 
@@ -29,14 +51,9 @@ export async function shouldCaptureProviderBodyForRequest(
29
51
  export async function resolveCaptureModeForRequest(
30
52
  db: CartethyiaDatabase,
31
53
  tenantId: string | null,
32
- state: { payloadCapturePreference?: unknown },
54
+ state: CapturePreferenceState,
33
55
  ): Promise<TelemetryPayloadMode> {
34
- if (state.payloadCapturePreference === undefined) {
35
- state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
36
- }
37
- const resolved = state.payloadCapturePreference as
38
- | { readonly ok: true; readonly mode: TelemetryPayloadMode }
39
- | { readonly ok: false };
56
+ const resolved = await resolveCapturePreference(db, tenantId, state);
40
57
  return resolved.ok === true ? resolved.mode : "none";
41
58
  }
42
59
 
@@ -44,32 +61,12 @@ export async function resolveCaptureModeForRequest(
44
61
  export async function resolveCaptureDepthForRequest(
45
62
  db: CartethyiaDatabase,
46
63
  tenantId: string | null,
47
- state: { payloadCapturePreference?: unknown },
64
+ state: CapturePreferenceState,
48
65
  ): Promise<TelemetryPayloadDepth> {
49
- if (state.payloadCapturePreference === undefined) {
50
- state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
51
- }
52
- const resolved = state.payloadCapturePreference as
53
- | { readonly ok: true; readonly depth: TelemetryPayloadDepth }
54
- | { readonly ok: false };
66
+ const resolved = await resolveCapturePreference(db, tenantId, state);
55
67
  return resolved.ok === true ? resolved.depth : "minimum";
56
68
  }
57
69
 
58
- /** Resolve the tenant's payload retention once per request. */
59
- export async function resolveCaptureRetentionForRequest(
60
- db: CartethyiaDatabase,
61
- tenantId: string | null,
62
- state: { payloadCapturePreference?: unknown },
63
- ): Promise<number> {
64
- if (state.payloadCapturePreference === undefined) {
65
- state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
66
- }
67
- const resolved = state.payloadCapturePreference as
68
- | { readonly ok: true; readonly retentionMs: number }
69
- | { readonly ok: false };
70
- return resolved.ok === true ? resolved.retentionMs : payloadRetentionMs();
71
- }
72
-
73
70
  /** Combined body cap for the four payload panels at one configured depth. */
74
71
  export function captureDepthMaxBytes(depth: TelemetryPayloadDepth): number {
75
72
  if (depth === "moderate") return 8 * 1024 * 1024;