cartethyia 2.7.9 → 2.8.1

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 (496) hide show
  1. package/.env.example +6 -5
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
  31. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
  44. package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
  45. package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
  61. package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
  63. package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
  73. package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
  77. package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
  79. package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
  87. package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
  89. package/dist/dashboard/assets/providers/sensenova.png +0 -0
  90. package/dist/dashboard/assets/providers/zro.svg +3 -0
  91. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  92. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  93. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  94. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
  95. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  96. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
  97. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
  99. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
  101. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
  103. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
  105. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  108. package/dist/dashboard/index.html +2 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -53
  111. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  112. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  113. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  114. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  115. package/package.json +1 -1
  116. package/src/boot/app.ts +11 -14
  117. package/src/boot/dependencies.ts +5 -7
  118. package/src/boot/flush.ts +20 -0
  119. package/src/boot/lifecycle.ts +7 -5
  120. package/src/config/providers.ts +6 -5
  121. package/src/config/routing.ts +26 -0
  122. package/src/config/spec-metadata.ts +72 -0
  123. package/src/config/spec.ts +28 -8
  124. package/src/console/api-keys/contracts.ts +12 -1
  125. package/src/console/api-keys/routes.ts +40 -28
  126. package/src/console/auth/account-reset.ts +7 -9
  127. package/src/console/backup/contracts.ts +4 -0
  128. package/src/console/backup/export-endpoint.ts +7 -5
  129. package/src/console/backup/nine-router.ts +71 -10
  130. package/src/console/backup/service.ts +8 -1
  131. package/src/console/cli-tools/service.ts +6 -12
  132. package/src/console/console-router-guards.ts +23 -16
  133. package/src/console/console-router.ts +1 -1
  134. package/src/console/domain-registration.ts +7 -4
  135. package/src/console/inferhub/client.ts +1 -1
  136. package/src/console/inferhub/normalize.ts +24 -28
  137. package/src/console/inferhub/types.ts +3 -0
  138. package/src/console/ip-bans/routes.ts +7 -13
  139. package/src/console/observability/live.ts +2 -2
  140. package/src/console/observability/store.ts +0 -53
  141. package/src/console/providers/catalog/contracts.ts +9 -3
  142. package/src/console/providers/catalog/model-operations.ts +2 -3
  143. package/src/console/providers/catalog/provider-operations.ts +1 -1
  144. package/src/console/providers/catalog/routes.ts +3 -2
  145. package/src/console/providers/catalog/store.ts +1914 -1902
  146. package/src/console/providers/detail/routes.ts +1 -0
  147. package/src/console/providers/detail/store.ts +4 -0
  148. package/src/console/providers/oauth/callback-listener.ts +30 -11
  149. package/src/console/providers/oauth/device-egress.ts +123 -0
  150. package/src/console/providers/oauth/routes.ts +123 -16
  151. package/src/console/quota/account-quota-global.ts +14 -24
  152. package/src/console/quota/account-quota-shared.ts +49 -0
  153. package/src/console/quota/account-quota-tenant.ts +11 -23
  154. package/src/console/quota/account-test-egress.ts +2 -2
  155. package/src/console/quota/daily-growth-job.ts +1 -1
  156. package/src/console/quota/quota-view.ts +19 -3
  157. package/src/console/routing/pools/contracts.ts +1 -1
  158. package/src/console/routing/pools/operations.ts +32 -47
  159. package/src/console/routing/pools/probe-verdict.ts +2 -2
  160. package/src/console/routing/pools/relay-deploy.ts +1 -1
  161. package/src/console/routing/pools/store.ts +4 -4
  162. package/src/console/shared/errors.ts +33 -6
  163. package/src/main.ts +2 -3
  164. package/src/network/client.ts +67 -0
  165. package/src/network/egress/bidi-session.ts +322 -0
  166. package/src/network/egress/dial.ts +201 -0
  167. package/src/network/egress/factory.ts +191 -0
  168. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  169. package/src/network/egress/tls-profile.ts +123 -0
  170. package/src/network/egress/ws.ts +175 -0
  171. package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
  172. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  173. package/src/network/pool/agent.ts +2 -2
  174. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
  175. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  176. package/src/network/pool/resolver.ts +10 -849
  177. package/src/network/pool/selector.ts +184 -41
  178. package/src/network/ssrf.ts +7 -7
  179. package/src/network/{retry.ts → util/retry.ts} +1 -1
  180. package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
  181. package/src/observability/event-log.ts +7 -361
  182. package/src/observability/performance-metrics.ts +9 -0
  183. package/src/observability/runtime-metrics.ts +14 -9
  184. package/src/observability/telemetry-buffer.ts +16 -26
  185. package/src/observability/telemetry-reconciliation.ts +9 -37
  186. package/src/observability/telemetry-row.ts +125 -0
  187. package/src/persistence/db-mode.ts +4 -1
  188. package/src/persistence/dialect.ts +18 -0
  189. package/src/persistence/migrate.ts +6 -3
  190. package/src/persistence/schema.ts +17 -0
  191. package/src/persistence/ttl-cache.ts +21 -3
  192. package/src/protocol/codecs.ts +59 -2
  193. package/src/protocol/dsml.ts +34 -86
  194. package/src/protocol/markup-stream-parser.ts +195 -0
  195. package/src/protocol/reasoning-text.ts +19 -0
  196. package/src/protocol/request/chat.ts +10 -10
  197. package/src/protocol/request/gemini.ts +8 -2
  198. package/src/protocol/request/messages.ts +34 -22
  199. package/src/protocol/response/chat.ts +12 -20
  200. package/src/protocol/response/messages.ts +34 -7
  201. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  202. package/src/protocol/response/responses.ts +18 -2
  203. package/src/protocol/stop-reason.ts +23 -1
  204. package/src/protocol/think-split.ts +112 -0
  205. package/src/protocol/translation/dialect/qwen.ts +26 -58
  206. package/src/protocol/translation/quirks.ts +15 -0
  207. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  208. package/src/providers/adapters/compatible-adapter.ts +76 -12
  209. package/src/providers/authentication/oauth-client.ts +40 -6
  210. package/src/providers/authentication/oauth-device-flow.ts +105 -11
  211. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  212. package/src/providers/authentication/oauth-refresh-service.ts +766 -758
  213. package/src/providers/configured-provider.ts +148 -0
  214. package/src/providers/default-registry.ts +137 -36
  215. package/src/providers/discovery/discovery-types.ts +7 -0
  216. package/src/providers/discovery/probe-phases.ts +20 -6
  217. package/src/providers/discovery/probing-service.ts +22 -2
  218. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  219. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  220. package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
  221. package/src/providers/integrations/buddy/account.ts +969 -0
  222. package/src/providers/integrations/buddy/capabilities.ts +12 -12
  223. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  224. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
  225. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  226. package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
  227. package/src/providers/integrations/buddy/headers.ts +311 -0
  228. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  229. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  230. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  231. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  232. package/src/providers/integrations/cline/cline.ts +5 -6
  233. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  234. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  235. package/src/providers/integrations/clixad/provider.ts +145 -0
  236. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  237. package/src/providers/integrations/commandcode/quota.ts +22 -28
  238. package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
  239. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  240. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  241. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  242. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  243. package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
  244. package/src/providers/integrations/cursor/cursor.ts +574 -110
  245. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  246. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  247. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  248. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  249. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  250. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  251. package/src/providers/integrations/deepai/provider.ts +42 -0
  252. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  253. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  254. package/src/providers/integrations/factory/factory-oauth.ts +33 -51
  255. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  256. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  257. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  258. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  259. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  260. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  261. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  262. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  263. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  264. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  265. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  266. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  267. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  268. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  269. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  270. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  271. package/src/providers/integrations/freepi/freepi.ts +80 -54
  272. package/src/providers/integrations/github/github-discovery.ts +30 -13
  273. package/src/providers/integrations/github/github-oauth.ts +217 -44
  274. package/src/providers/integrations/github/github-quota.ts +22 -5
  275. package/src/providers/integrations/github/github.ts +40 -32
  276. package/src/providers/integrations/grok/grok-oauth.ts +52 -49
  277. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  278. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  279. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  280. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
  281. package/src/providers/integrations/hermes/index.ts +2 -0
  282. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  283. package/src/providers/integrations/kilo/kilo.ts +1 -1
  284. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  285. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  286. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  287. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  288. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  289. package/src/providers/integrations/kiro/kiro.ts +15 -19
  290. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  291. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  292. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  293. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  294. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  295. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  296. package/src/providers/integrations/openrouter/provider.ts +1 -1
  297. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  298. package/src/providers/integrations/perch/perch-protocol.ts +0 -6
  299. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  300. package/src/providers/integrations/perch/perch.ts +6 -16
  301. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  302. package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
  303. package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
  304. package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
  305. package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
  306. package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
  307. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  308. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  309. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  310. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  311. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  312. package/src/providers/integrations/qoder/qoder.ts +3 -26
  313. package/src/providers/integrations/sensenova/provider.ts +144 -0
  314. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  315. package/src/providers/integrations/trae/trae-quota.ts +21 -29
  316. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  317. package/src/providers/integrations/trae/trae.ts +7 -24
  318. package/src/providers/integrations/vercel/provider.ts +117 -0
  319. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  320. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  321. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  322. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  323. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  324. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  325. package/src/providers/integrations/zro/zro.ts +205 -0
  326. package/src/providers/operations/account-error-classification.ts +199 -307
  327. package/src/providers/operations/account-health-service.ts +82 -1
  328. package/src/providers/operations/account-refresh-status.ts +36 -37
  329. package/src/providers/operations/account-tier.ts +15 -16
  330. package/src/providers/operations/client-version-resolver.ts +1 -1
  331. package/src/providers/operations/client-versions.ts +100 -81
  332. package/src/providers/operations/cooldown-rules.ts +11 -16
  333. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  334. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  335. package/src/providers/operations/provider-catalog-service.ts +1 -1
  336. package/src/providers/operations/provider-credential-service.ts +118 -51
  337. package/src/providers/operations/provider-rules/github.ts +48 -0
  338. package/src/providers/operations/provider-rules/grok.ts +33 -0
  339. package/src/providers/operations/provider-rules/inception.ts +49 -0
  340. package/src/providers/operations/provider-rules/index.ts +32 -0
  341. package/src/providers/operations/provider-rules/perch.ts +37 -0
  342. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  343. package/src/providers/operations/provider-rules/trae.ts +81 -0
  344. package/src/providers/operations/session-resolution.ts +17 -1
  345. package/src/providers/provider-contracts.ts +6 -4
  346. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  347. package/src/providers/provider-metadata.ts +70 -10
  348. package/src/providers/provider-probe.ts +15 -1
  349. package/src/providers/provider-registry.ts +42 -33
  350. package/src/providers/quota/quota-contracts.ts +2 -0
  351. package/src/providers/quota/quota-limit-windows.ts +16 -5
  352. package/src/providers/quota/quota-support.ts +1 -1
  353. package/src/providers/quota/quota-window-parser.ts +90 -18
  354. package/src/providers/quota/refresh.ts +100 -21
  355. package/src/security/api-key-auth-cache.ts +1 -1
  356. package/src/security/api-key-auth.ts +6 -2
  357. package/src/security/ip-boundary.ts +1 -1
  358. package/src/transport/dispatch/attempt-finalize.ts +15 -14
  359. package/src/transport/dispatch/attempt-loop.ts +73 -19
  360. package/src/transport/dispatch/generation.ts +2 -8
  361. package/src/transport/dispatch/handler-deps.ts +1 -1
  362. package/src/transport/dispatch/leases.ts +1 -1
  363. package/src/transport/dispatch/native-shared.ts +25 -0
  364. package/src/transport/dispatch/proxy-request.ts +8 -46
  365. package/src/transport/dispatch/responses-compact.ts +2 -8
  366. package/src/transport/dispatch/retry-policy.ts +1 -1
  367. package/src/transport/dispatch/streaming/finalize.ts +10 -7
  368. package/src/transport/dispatch/streaming/stream.ts +9 -37
  369. package/src/transport/dispatch/systemone.ts +2 -8
  370. package/src/transport/dispatch/token-count.ts +2 -6
  371. package/src/transport/dispatch/upstream.ts +63 -0
  372. package/src/transport/drain-endpoint.ts +31 -4
  373. package/src/transport/failure-policy.ts +16 -30
  374. package/src/transport/guards/body-policy.ts +7 -1
  375. package/src/transport/guards/error-lifecycle.ts +13 -1
  376. package/src/transport/guards/request-context.ts +22 -14
  377. package/src/transport/request/preparer.ts +81 -71
  378. package/src/transport/routing/bounded-lru.ts +14 -0
  379. package/src/transport/routing/drain-reserve.ts +129 -0
  380. package/src/transport/routing/plan-gate.ts +7 -1
  381. package/src/transport/routing/round-robin.ts +60 -0
  382. package/src/transport/routing/route-catalog.ts +123 -111
  383. package/src/transport/routing/route-model.ts +51 -9
  384. package/src/transport/routing/router.ts +393 -32
  385. package/src/transport/shutdown-notice.ts +1 -1
  386. package/src/transport/surface/adapters.ts +11 -2
  387. package/src/transport/surface/chat/adapter.ts +2 -2
  388. package/src/transport/surface/chat/encode.ts +47 -4
  389. package/src/transport/surface/chat/parse.ts +2 -12
  390. package/src/transport/surface/completion.ts +44 -33
  391. package/src/transport/surface/content-parts.ts +21 -0
  392. package/src/transport/surface/messages/adapter.ts +7 -1
  393. package/src/transport/surface/messages/encode.ts +11 -3
  394. package/src/transport/surface/messages/parse.ts +2 -1
  395. package/src/transport/surface/messages/stream.ts +33 -17
  396. package/src/transport/surface/responses/encode.ts +22 -5
  397. package/src/transport/surface/responses/parse.ts +3 -11
  398. package/src/workers/buddy-growth-tasks.ts +8 -8
  399. package/src/workers/daily-checkin.ts +4 -4
  400. package/src/workers/quota-refresh-sweep.ts +1 -1
  401. package/src/workers/zcode-gift-claim.ts +1 -1
  402. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  403. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  404. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  405. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  406. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  407. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  408. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  409. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  410. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  411. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  412. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  413. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  414. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  415. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  416. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  417. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  418. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  419. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
  420. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  421. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  422. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  423. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  424. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  425. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  426. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  427. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  428. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  429. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  430. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  431. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  432. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  433. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  434. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  435. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  436. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  437. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  438. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  439. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  440. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  441. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  442. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  443. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  444. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  445. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  446. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  447. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  448. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  449. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  450. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  451. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  452. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  453. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  454. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  455. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  456. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  457. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  458. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  459. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  460. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  461. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  462. package/src/network/pool-health.ts +0 -210
  463. package/src/providers/bundled-discovery-providers.ts +0 -98
  464. package/src/providers/configured-openai-providers.ts +0 -120
  465. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  466. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  467. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  468. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  469. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  470. package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
  471. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  472. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  473. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  474. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  475. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  476. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  477. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
  478. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  479. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
  480. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  481. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  482. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
  483. package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
  484. package/src/providers/operations/model-discovery-cache.ts +0 -34
  485. package/src/providers/operations/probe-fetch.ts +0 -15
  486. package/src/providers/operations/provider-version-cache.ts +0 -30
  487. package/src/providers/provider-capabilities.ts +0 -33
  488. package/src/providers/provider-credential-envelope.ts +0 -43
  489. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
  490. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
  491. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  492. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  493. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  494. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  495. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  496. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
@@ -11,7 +11,7 @@ import { chatAdapter } from "../surface/chat/adapter";
11
11
  import { responsesAdapter } from "../surface/responses/adapter";
12
12
  import { messagesAdapter } from "../surface/messages/adapter";
13
13
  import { completionAdapter } from "../surface/completion";
14
- import { forwardedRequestHeaders, proxySuccessHeaders, buildUpstreamDispatchContext, reportedModelFrom } from "./upstream";
14
+ import { forwardedRequestHeaders, proxySuccessHeaders, buildUpstreamDispatchContext, buildDispatchNetworkBindings, reportedModelFrom } from "./upstream";
15
15
  import { resolvePromptCacheKey } from "../../providers/operations/session-resolution";
16
16
  import {
17
17
  applyTenantPreferences,
@@ -21,18 +21,12 @@ import {
21
21
  import { metrics } from "../../observability/metrics";
22
22
  import { repriceUsage } from "../../providers/provider-usage";
23
23
  import {
24
- captureProviderExchange,
25
24
  completeAttempt,
26
25
  completionContext,
27
26
  estimatedUsage,
28
27
  parseCapturedBody,
29
28
  terminalFailure,
30
29
  } from "./attempt-finalize";
31
- import {
32
- providerCaptureMaxBytes,
33
- resolveCaptureDepthForRequest,
34
- shouldCaptureProviderBodyForRequest,
35
- } from "./capture-policy";
36
30
  import { runAttemptLoop } from "./attempt-loop";
37
31
  import { projectForRoute, routeCapabilitiesFor } from "../../protocol/translation/capabilities";
38
32
  import { dispatchStreamingAttempt } from "./streaming/stream";
@@ -264,45 +258,13 @@ export async function handleProviderProxyRequest(
264
258
  ...(tenantSystemPromptText === undefined ? {} : { tenantSystemPrompt: tenantSystemPromptText }),
265
259
  ...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
266
260
  ...(providerCapture ? { providerCapture } : {}),
267
- ...(deps.networkBindingFactory
268
- ? {
269
- outboundFetch: captureProviderExchange(
270
- deps.networkBindingFactory.fetch(
271
- networkPoolId,
272
- prepared.authorization.snapshot.tenant_id,
273
- ),
274
- providerCapture,
275
- await shouldCaptureProviderBodyForRequest(
276
- deps.db,
277
- prepared.authorization.snapshot.tenant_id,
278
- state,
279
- ),
280
- providerCaptureMaxBytes(
281
- await resolveCaptureDepthForRequest(
282
- deps.db,
283
- prepared.authorization.snapshot.tenant_id,
284
- state,
285
- ),
286
- ),
287
- ),
288
- }
289
- : {}),
290
- ...(deps.networkBindingFactory
291
- ? {
292
- outboundAttachmentFetch: deps.networkBindingFactory.fetch(
293
- networkPoolId,
294
- prepared.authorization.snapshot.tenant_id,
295
- ),
296
- }
297
- : {}),
298
- ...(deps.networkBindingFactory
299
- ? {
300
- outboundHttp2: deps.networkBindingFactory.http2Bidi(
301
- networkPoolId,
302
- prepared.authorization.snapshot.tenant_id,
303
- ),
304
- }
305
- : {}),
261
+ ...(await buildDispatchNetworkBindings({
262
+ deps,
263
+ networkPoolId,
264
+ tenantId: prepared.authorization.snapshot.tenant_id,
265
+ providerCapture,
266
+ state,
267
+ })),
306
268
  ...(leaseOwnership === undefined ? {} : { leaseOwnership }),
307
269
  ...(continuationScope === undefined ? {} : { continuationScope }),
308
270
  });
@@ -4,6 +4,7 @@ import type { CodexCompactAdapter } from "../../providers/integrations/codex/cod
4
4
  import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
5
5
  import { runAttemptLoop } from "./attempt-loop";
6
6
  import {
7
+ forwardUpstreamResponse,
7
8
  networkFetchContext,
8
9
  requireRecordBody,
9
10
  resolveAttemptCredential,
@@ -143,14 +144,7 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
143
144
  responseBody: null,
144
145
  });
145
146
  metrics.proxy_requests_total.inc(1, { status: "completed" });
146
- return new Response(response.body, {
147
- status: response.status,
148
- headers: {
149
- "content-type": response.headers.get("content-type") ?? "application/json",
150
- "cache-control": "no-store",
151
- "x-request-id": state.requestId,
152
- },
153
- });
147
+ return forwardUpstreamResponse(response, state.requestId);
154
148
  },
155
149
  });
156
150
  };
@@ -10,7 +10,7 @@
10
10
  * cooldown eligibility (here).
11
11
  *
12
12
  * The egress-scoped half of the rate-limit decision lives in
13
- * `../../network/rate-limit-verdict`; this module keeps only the credential-
13
+ * `../../network/verdict`; this module keeps only the credential-
14
14
  * scoped predicates.
15
15
  */
16
16
  import { GatewayError } from "../../protocol/gateway-error";
@@ -23,8 +23,8 @@ import type { AdmissionLease } from "../../../security/admission/contracts";
23
23
  import type { Reservation } from "../../routing/route-model";
24
24
  import { repriceUsage } from "../../../providers/provider-usage";
25
25
  import { metrics } from "../../../observability/metrics";
26
- import { flagPoolBlocked, flagPoolCooldown } from "../../../network/pool-health";
27
- import { classifyEgressFailure } from "../../../network/rate-limit-verdict";
26
+ import { flagPoolOnEgressFailure } from "../../../network/pool/health";
27
+ import { classifyEgressFailure } from "../../../network/verdict";
28
28
  import { releaseAttemptLeases } from "../leases";
29
29
  import type { AttemptLeaseOwnership } from "../leases";
30
30
  import { finalizeRequestTelemetry } from "../../guards/error-lifecycle";
@@ -581,11 +581,14 @@ export class StreamFinalizer {
581
581
  this.streamSettled = true;
582
582
  if (candidate.networkPoolId && !cancelled && deps.poolSelector && err instanceof GatewayError) {
583
583
  const egressVerdict = classifyEgressFailure(err, candidate.providerId);
584
- if (egressVerdict.kind === "cooldown") {
585
- flagPoolCooldown(deps.poolSelector, deps.db, candidate.networkPoolId, candidate.providerId, err);
586
- } else if (egressVerdict.kind === "blocked") {
587
- flagPoolBlocked(deps.poolSelector, deps.db, candidate.networkPoolId, candidate.providerId, err);
588
- }
584
+ flagPoolOnEgressFailure(
585
+ deps.poolSelector,
586
+ deps.db,
587
+ candidate.networkPoolId,
588
+ candidate.providerId,
589
+ err,
590
+ egressVerdict,
591
+ );
589
592
  }
590
593
  if (!cancelled) metrics.proxy_requests_total.inc(1, { status: "failed" });
591
594
  else metrics.proxy_requests_total.inc(1, { status: "cancelled" });
@@ -36,24 +36,18 @@
36
36
  import { GatewayError } from "../../../protocol/gateway-error";
37
37
  import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonical-model";
38
38
  import type { ProviderAdapter, ResolvedCredential } from "../../../providers/provider-contracts";
39
- import type { ValidatedNetworkBindingFactory } from "../../../network/pool/resolver";
39
+ import type { ValidatedNetworkBindingFactory } from "../../../network/egress/factory";
40
40
  import type { CartethyiaDatabase } from "../../../persistence/db-handle";
41
41
  import type { Reservation, RouteCandidate, RouteSnapshotService } from "../../routing/route-model";
42
42
  import type { AdmissionLease } from "../../../security/admission/contracts";
43
43
  import type { NetworkPoolSelector } from "../../../network/pool/selector";
44
44
  import type { TelemetryBatchBuffer } from "../../../observability/telemetry-buffer";
45
45
  import type { ProviderExchangeCapture } from "../attempt-finalize";
46
- import { captureProviderExchange } from "../attempt-finalize";
47
- import {
48
- providerCaptureMaxBytes,
49
- resolveCaptureDepthForRequest,
50
- resolveCaptureModeForRequest,
51
- shouldCaptureProviderBodyForRequest,
52
- } from "../capture-policy";
46
+ import { resolveCaptureModeForRequest } from "../capture-policy";
53
47
  import { resolveTenantImageClampMaxBytes, readTenantSystemPromptsForDispatch } from "../../request/tenant-preferences";
54
48
  import { resolveTenantSystemPrompt } from "../../request/system-prompt-injection";
55
49
  import { isWrappedInjectionProvider } from "../../../console/system-prompt/contracts";
56
- import { proxySuccessHeaders, buildUpstreamDispatchContext } from "../upstream";
50
+ import { proxySuccessHeaders, buildUpstreamDispatchContext, buildDispatchNetworkBindings } from "../upstream";
57
51
  import type { AttemptLeaseOwnership } from "../leases";
58
52
  import type { ProviderContinuationScope } from "../../../providers/provider-contracts";
59
53
  import { resolveStreamFirstChunkTimeoutMs, resolveStreamStallTimeoutMs, resolveUpstreamTimeoutMs } from "../../../config/timeouts";
@@ -170,18 +164,13 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
170
164
  continuationScope,
171
165
  } = input;
172
166
 
173
- const outboundFetch = deps.networkBindingFactory?.fetch(
167
+ const bindings = await buildDispatchNetworkBindings({
168
+ deps,
174
169
  networkPoolId,
175
- prepared.authorization.snapshot.tenant_id,
176
- );
177
- const captureBody = outboundFetch
178
- ? await shouldCaptureProviderBodyForRequest(deps.db, prepared.authorization.snapshot.tenant_id, state)
179
- : false;
180
- const captureDepth = await resolveCaptureDepthForRequest(
181
- deps.db,
182
- prepared.authorization.snapshot.tenant_id,
170
+ tenantId: prepared.authorization.snapshot.tenant_id,
171
+ providerCapture,
183
172
  state,
184
- );
173
+ });
185
174
  // Resolved once, before the pipeline exists: the pipeline's diagnostics are
186
175
  // gated on it from construction, so `none`/`metadata` tenants never pay the
187
176
  // transcript serialization or the client-response copy for any event.
@@ -190,14 +179,6 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
190
179
  prepared.authorization.snapshot.tenant_id,
191
180
  state,
192
181
  );
193
- const wrappedOutboundFetch = outboundFetch
194
- ? captureProviderExchange(
195
- outboundFetch,
196
- providerCapture,
197
- captureBody,
198
- providerCaptureMaxBytes(captureDepth),
199
- )
200
- : undefined;
201
182
  // Same memoized ceiling the non-streaming path resolves, read before the
202
183
  // dispatch context is built so a stream gets the same limit as a normal
203
184
  // request rather than silently falling back to the deployment default.
@@ -237,16 +218,7 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
237
218
  return text === undefined ? {} : { tenantSystemPrompt: text };
238
219
  })()),
239
220
  ...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
240
- ...(wrappedOutboundFetch ? { outboundFetch: wrappedOutboundFetch } : {}),
241
- ...(outboundFetch ? { outboundAttachmentFetch: outboundFetch } : {}),
242
- ...(deps.networkBindingFactory
243
- ? {
244
- outboundHttp2: deps.networkBindingFactory.http2Bidi(
245
- networkPoolId,
246
- prepared.authorization.snapshot.tenant_id,
247
- ),
248
- }
249
- : {}),
221
+ ...bindings,
250
222
  ...(leaseOwnership === undefined ? {} : { leaseOwnership }),
251
223
  ...(continuationScope === undefined ? {} : { continuationScope }),
252
224
  ...(providerCapture ? { providerCapture } : {}),
@@ -4,6 +4,7 @@ import { metrics } from "../../observability/metrics";
4
4
  import { estimatedUsage, nativeCompletionBase, completeAttempt } from "./attempt-finalize";
5
5
  import { runAttemptLoop } from "./attempt-loop";
6
6
  import {
7
+ forwardUpstreamResponse,
7
8
  networkFetchContext,
8
9
  requireRecordBody,
9
10
  resolveAttemptCredential,
@@ -164,14 +165,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
164
165
  responseBody: null,
165
166
  });
166
167
  metrics.proxy_requests_total.inc(1, { status: "completed" });
167
- return new Response(response.body, {
168
- status: response.status,
169
- headers: {
170
- "content-type": response.headers.get("content-type") ?? "application/json",
171
- "cache-control": "no-store",
172
- "x-request-id": state.requestId,
173
- },
174
- });
168
+ return forwardUpstreamResponse(response, state.requestId);
175
169
  },
176
170
  });
177
171
  };
@@ -43,7 +43,7 @@ import type { PreparedNativeRequest } from "../request/preparer";
43
43
  import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
44
44
  import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
45
45
  import { runAttemptLoop } from "./attempt-loop";
46
- import { hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
46
+ import { forwardUpstreamResponse, hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
47
47
 
48
48
  import type { GatewayHandlerDeps } from "./handler-deps";
49
49
 
@@ -189,11 +189,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
189
189
 
190
190
  // Verbatim: the client reads the provider's own number, and the
191
191
  // provider's own error when it refuses to produce one.
192
- const contentType = upstream.headers.get("content-type") ?? "application/json";
193
- return new Response(upstream.body, {
194
- status: upstream.status,
195
- headers: { "content-type": contentType, "cache-control": "no-store", "x-request-id": state.requestId },
196
- });
192
+ return forwardUpstreamResponse(upstream, state.requestId);
197
193
  },
198
194
  });
199
195
  };
@@ -2,6 +2,15 @@ import type { CanonicalEvent } from "../../protocol/canonical-model";
2
2
  import type { AttemptLeaseOwnership, ValidatedOutboundFetch, ValidatedOutboundHttp2Bidi } from "../../providers/provider-contracts";
3
3
  import type { ProviderCaptureExchange, ProviderContinuationScope } from "../../providers/provider-contracts";
4
4
  import { GATEWAY_SECURITY_HEADERS } from "../../security/outbound-headers";
5
+ import type { ValidatedNetworkBindingFactory } from "../../network/egress/factory";
6
+ import type { CartethyiaDatabase } from "../../persistence/db-handle";
7
+ import { captureProviderExchange, type ProviderExchangeCapture } from "./attempt-finalize";
8
+ import {
9
+ providerCaptureMaxBytes,
10
+ resolveCaptureDepthForRequest,
11
+ shouldCaptureProviderBodyForRequest,
12
+ type CapturePreferenceState,
13
+ } from "./capture-policy";
5
14
 
6
15
  const FORWARDED_REQUEST_HEADERS = new Set([
7
16
  "user-agent",
@@ -34,6 +43,60 @@ export function proxySuccessHeaders(state: { requestId: string }): Record<string
34
43
  };
35
44
  }
36
45
 
46
+ /** The three validated egress bindings shared by every dispatch attempt. */
47
+ export interface DispatchNetworkBindings {
48
+ readonly outboundFetch?: ValidatedOutboundFetch;
49
+ readonly outboundAttachmentFetch?: ValidatedOutboundFetch;
50
+ readonly outboundHttp2?: ValidatedOutboundHttp2Bidi;
51
+ }
52
+
53
+ /**
54
+ * Builds the outbound network bindings for one attempt's dispatch context,
55
+ * shared by the non-streaming proxy path and the streaming branch.
56
+ *
57
+ * Both previously assembled the same three objects by hand (wrapped fetch,
58
+ * attachment fetch, HTTP/2 bidi), which drifted in the capture wrap. The
59
+ * capture-body gating is preserved exactly: the DB-backed capture-preference
60
+ * read is skipped when no network binding exists (`outboundFetch ? ... :
61
+ * false`), so a request that can never tee a provider exchange does not pay
62
+ * for the preference read. The reads are memoized on the request state, so
63
+ * resolving them here costs no extra DB reads.
64
+ */
65
+ export async function buildDispatchNetworkBindings(input: {
66
+ readonly deps: {
67
+ readonly networkBindingFactory?: ValidatedNetworkBindingFactory | undefined;
68
+ readonly db: CartethyiaDatabase;
69
+ };
70
+ readonly networkPoolId: string | undefined;
71
+ readonly tenantId: string | null;
72
+ readonly providerCapture: ProviderExchangeCapture;
73
+ readonly state: CapturePreferenceState;
74
+ }): Promise<DispatchNetworkBindings> {
75
+ const factory = input.deps.networkBindingFactory;
76
+ if (factory === undefined) return {};
77
+ const outboundFetch = factory.fetch(input.networkPoolId, input.tenantId ?? undefined);
78
+ const captureBody = await shouldCaptureProviderBodyForRequest(
79
+ input.deps.db,
80
+ input.tenantId,
81
+ input.state,
82
+ );
83
+ const captureDepth = await resolveCaptureDepthForRequest(
84
+ input.deps.db,
85
+ input.tenantId,
86
+ input.state,
87
+ );
88
+ return {
89
+ outboundFetch: captureProviderExchange(
90
+ outboundFetch,
91
+ input.providerCapture,
92
+ captureBody,
93
+ providerCaptureMaxBytes(captureDepth),
94
+ ),
95
+ outboundAttachmentFetch: factory.fetch(input.networkPoolId, input.tenantId ?? undefined),
96
+ outboundHttp2: factory.http2Bidi(input.networkPoolId, input.tenantId ?? undefined),
97
+ };
98
+ }
99
+
37
100
  export function buildUpstreamDispatchContext(input: {
38
101
  readonly credential: unknown;
39
102
  readonly deadline: number;
@@ -50,14 +50,41 @@ export function tokenMatches(provided: string | null, expected: string): boolean
50
50
  return a.length === b.length && timingSafeEqual(a, b);
51
51
  }
52
52
 
53
+ interface LoopbackAdminGate {
54
+ /** Peer address of the request, resolved by the composition root. */
55
+ readonly peer: string | null;
56
+ /** Shared secret; the route is registered only when this is set. */
57
+ readonly token: string;
58
+ /** `policy_rejected` (403) message, preserved per surface. */
59
+ readonly policyRejectedMessage: string;
60
+ /** `authentication_failed` (401) message, preserved per surface. */
61
+ readonly authenticationFailedMessage: string;
62
+ }
63
+
64
+ /**
65
+ * The shared security gate for host-local admin operations: the request must
66
+ * come from loopback, then it must carry the drain token compared in constant
67
+ * time. The peer check is independent of the secret, so a leaked token alone
68
+ * cannot reach a remote surface. The peer refusal comes first, then the token,
69
+ * with the codes and messages preserved verbatim per caller.
70
+ */
71
+ export function requireLoopbackAdmin(request: Request, gate: LoopbackAdminGate): void {
72
+ if (!isLoopbackPeer(gate.peer))
73
+ throw new GatewayError("policy_rejected", 403, gate.policyRejectedMessage);
74
+ if (!tokenMatches(request.headers.get("x-drain-token"), gate.token))
75
+ throw new GatewayError("authentication_failed", 401, gate.authenticationFailedMessage);
76
+ }
77
+
53
78
  export function createDrainHandler(
54
79
  deps: DrainEndpointDeps,
55
80
  ): (context: { request: Request }) => Promise<Response> {
56
81
  return async ({ request }) => {
57
- if (!isLoopbackPeer(deps.resolvePeerAddress(request)))
58
- throw new GatewayError("policy_rejected", 403, "drain is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂");
59
- if (!tokenMatches(request.headers.get("x-drain-token"), deps.token))
60
- throw new GatewayError("authentication_failed", 401, "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑");
82
+ requireLoopbackAdmin(request, {
83
+ peer: deps.resolvePeerAddress(request),
84
+ token: deps.token,
85
+ policyRejectedMessage: "drain is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂",
86
+ authenticationFailedMessage: "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑",
87
+ });
61
88
  // Acknowledge first, then drain: the response must be on the wire before the
62
89
  // shutdown path starts closing sockets, or the caller sees a dropped
63
90
  // connection instead of a 202.
@@ -2,7 +2,6 @@ import { GatewayError, type GatewayErrorCode, type GatewayErrorOrigin } from "..
2
2
  import { isRecord } from "../protocol/codecs";
3
3
  import { resolveFallbackRetryBaseMs, resolveFallbackRetryCapMs } from "../config/timeouts";
4
4
  import { parseUpstreamBackoff } from "../protocol/upstream-parsing";
5
- import { resolveAccountQuotaCooldownMs } from "../config/cooldowns";
6
5
 
7
6
  /**
8
7
  * How much of the upstream's error body is kept and surfaced.
@@ -260,31 +259,6 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
260
259
  // DNS failure. Failing the client request instead of failing over is the wrong
261
260
  // outcome for a whole class the table already names as platform-wide.
262
261
  const statusCode = error.status;
263
- // Qoder answers an exhausted quota by closing the stream with an upstream 499
264
- // (nginx client-close style). That is a refusal from the upstream — the
265
- // gateway's own aborts are `transport_closed`, produced locally, and never
266
- // carry a qoder providerId — so a 499 scoped to qoder is quota, not a
267
- // malformed request. Without this it fell through to `invalid_request`
268
- // (non-retryable, no cooldown) and the account was re-dialled on every
269
- // request inside the exhausted window until a late 429 finally classified it.
270
- if (
271
- error.origin === "upstream" &&
272
- statusCode === 499 &&
273
- (providerId === "qoder" || providerId === "qodercn")
274
- ) {
275
- const cooldownMs = resolveAccountQuotaCooldownMs();
276
- return {
277
- retryable: true,
278
- mutatesAccount: true,
279
- category: "quota_exceeded",
280
- scope: "account",
281
- origin: error.origin,
282
- ...(providerCode ? { providerCode } : {}),
283
- ...(providerId ? { providerId } : {}),
284
- cooldownMs,
285
- statusCode,
286
- };
287
- }
288
262
  const retryableByStatus =
289
263
  statusCode === 401 ||
290
264
  statusCode === 403 ||
@@ -684,10 +658,8 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
684
658
  // quota (a cooldown) instead of `auth_invalidated`.
685
659
  "110",
686
660
  // Qoder also reports quota exhaustion as `{"code":116,"error":"quota
687
- // exceeded"}` (seen on the IDE model-server surface with a 499/stream
688
- // close). The 499 special-case below already treats qoder 499 as quota;
689
- // naming the code here makes any status that carries it classify as quota
690
- // instead of `invalid_request`/`authentication_failed`.
661
+ // exceeded"}`. Naming the code here makes any status that carries it classify
662
+ // as quota instead of `invalid_request`/`authentication_failed`.
691
663
  "116",
692
664
  // InferHub publishes its own vocabulary in `error.code`. The two 403s below
693
665
  // are deliberate absences: `account_blocked` means the account was blocked
@@ -846,6 +818,20 @@ export function classifyUpstreamError(
846
818
  return { code: "proxy_auth_required", status: 407, origin: "network" };
847
819
  if (normalized === "11140")
848
820
  return { code: "policy_rejected", status: 403, origin: "upstream" };
821
+ // ZCode plan-lane envelope codes (verified live 2026-10-30 by the donor and
822
+ // mirrored by the zcode-start provider). `3009` is model concurrency
823
+ // (transient, retried in place), `3008` user concurrency (the desktop client
824
+ // is busy on that account — switchable), `3012` is the identity/prompt-shape
825
+ // gate, `1113` the paid-lane "insufficient balance or no resource package"
826
+ // (a request that was not recognized as start-plan traffic).
827
+ if (normalized === "3009")
828
+ return { code: "rate_limited", status: 429, origin: "upstream" };
829
+ if (normalized === "3008")
830
+ return { code: "accounts_rate_limited", status: 429, origin: "upstream" };
831
+ if (normalized === "3012")
832
+ return { code: "policy_rejected", status: 405, origin: "upstream" };
833
+ if (normalized === "1113")
834
+ return { code: "quota_exceeded", status: 429, origin: "upstream", rateLimitScope: "provider" };
849
835
  if (normalized !== undefined && CONTEXT_LENGTH_CODES.has(normalized))
850
836
  return { code: "context_length_exceeded", status: 413, origin: "upstream" };
851
837
  if (normalized !== undefined && QUOTA_ERROR_IDENTIFIERS.has(normalized))
@@ -58,8 +58,14 @@ export function isProxyDispatchRoute(path: string | undefined): path is string {
58
58
  return path !== undefined && isJsonProxyRoute(path);
59
59
  }
60
60
 
61
+ /**
62
+ * The same canonical JSON-route membership as `isProxyDispatchRoute`, kept as
63
+ * a non-narrowing alias for callers that already hold a string path. One
64
+ * predicate, two spellings, so a route-table change cannot diverge between
65
+ * the body-read policy and the canonical-parse gate.
66
+ */
61
67
  export function isJsonProxyRoutePath(path: string): boolean {
62
- return isJsonProxyRoute(path);
68
+ return isProxyDispatchRoute(path);
63
69
  }
64
70
 
65
71
  interface IngressPolicyOptions {
@@ -77,8 +77,20 @@ export function createErrorNormalizationMiddleware(deps: {
77
77
  .error(({ request, error, set }) => {
78
78
  const state = deps.stateStore.get(request as Request);
79
79
  const gateway = error instanceof GatewayError ? error : undefined;
80
+ // Elysia's built-in errors (NotFound, ValidationError, ParseError,
81
+ // InternalServerError) always carry a numeric `status` **and** a code.
82
+ // A raw runtime error can carry a `.code` on its own — Bun's fetch
83
+ // surfaces `TypeError` with `code: "ECONNRESET"` when an upstream socket
84
+ // dies mid-response — so a `code` alone is not evidence of an Elysia
85
+ // built-in. Requiring the numeric status keeps that raw error from
86
+ // leaking into the public envelope as a fake code/400; the dispatch loop
87
+ // already normalizes socket drops to `transport_unavailable`/502 before
88
+ // they reach here.
80
89
  const builtin =
81
- !gateway && typeof error === "object" && error !== null
90
+ !gateway &&
91
+ typeof error === "object" &&
92
+ error !== null &&
93
+ typeof (error as ElysiaBuiltinError).status === "number"
82
94
  ? (error as ElysiaBuiltinError)
83
95
  : undefined;
84
96
  const builtinStatus = typeof builtin?.status === "number" ? builtin.status : undefined;
@@ -12,7 +12,7 @@ import { resolveClientIdentity } from "../../security/ip-boundary";
12
12
  import { GATEWAY_SECURITY_HEADERS } from "../../security/outbound-headers";
13
13
  import { pushStructuredConsoleLog } from "../../observability/log-ring";
14
14
  import { log } from "../../observability/logger";
15
- import { isJsonProxyRoutePath, isProxyDispatchRoute } from "./body-policy";
15
+ import { isProxyDispatchRoute } from "./body-policy";
16
16
  import { isOpaqueBodyRoute } from "../contracts";
17
17
  import { peerAddressUnavailableError } from "./guard-errors";
18
18
 
@@ -24,6 +24,24 @@ interface RequestApp {
24
24
  ): RequestApp;
25
25
  }
26
26
 
27
+ /**
28
+ * Whether this request falls outside the canonical JSON-proxy pipeline the
29
+ * canonical-parse and route-preparation stages serve.
30
+ *
31
+ * Shared gate, applied in the exact order both middlewares historically
32
+ * checked: non-`/v1/` paths, GET/HEAD (which carry no body to parse), then
33
+ * the JSON route table with opaque-body routes excluded — `/v1/completions`
34
+ * needs no separate arm because it is already in the JSON routes table, and
35
+ * native routes (compact, System One, token count) read a body but are never
36
+ * parsed into a canonical request, their bodies being opaque by contract.
37
+ */
38
+ function skipNonCanonicalProxyRequest(request: Request): boolean {
39
+ const path = fastPathname(request.url);
40
+ if (!path.startsWith("/v1/")) return true;
41
+ if (request.method === "GET" || request.method === "HEAD") return true;
42
+ return !isProxyDispatchRoute(path) || isOpaqueBodyRoute(path);
43
+ }
44
+
27
45
  interface RequestContextDeps {
28
46
  readonly stateStore: ProxyRequestStateStore;
29
47
  readonly clock?: () => number;
@@ -141,15 +159,9 @@ export function createCanonicalRequestMiddleware(deps: {
141
159
  }): Elysia {
142
160
  return new Elysia()
143
161
  .beforeHandle(async ({ request }) => {
144
- const path = fastPathname(request.url);
145
- if (!path.startsWith("/v1/")) return;
146
- if (request.method === "GET" || request.method === "HEAD") return;
147
- // `/v1/completions` needs no separate arm: it is already in
148
- // the JSON routes table, so the body-policy predicate covers it.
149
- // Native routes (compact, System One) read a body but are never parsed
150
- // into a canonical request — their bodies are opaque by contract.
151
- if (!isJsonProxyRoutePath(path) || isOpaqueBodyRoute(path)) return;
152
162
  // Model discovery and other GET/multimodal routes must not go through canonical parsing.
163
+ if (skipNonCanonicalProxyRequest(request)) return;
164
+ const path = fastPathname(request.url);
153
165
  const state = deps.stateStore.require(request);
154
166
  // Single-read invariant: the ingress policy middleware already decoded the
155
167
  // body once into `state.ingressBody`. Never re-read `request.body` here —
@@ -216,11 +228,7 @@ export function createProxyRoutePreparationMiddleware(deps: {
216
228
  }): Elysia {
217
229
  return new Elysia()
218
230
  .beforeHandle(async ({ request }) => {
219
- const path = fastPathname(request.url);
220
- if (!path.startsWith("/v1/")) return;
221
- if (request.method === "GET" || request.method === "HEAD") return;
222
- const isJsonRoute = isJsonProxyRoutePath(path);
223
- if (!isJsonRoute || isOpaqueBodyRoute(path)) return;
231
+ if (skipNonCanonicalProxyRequest(request)) return;
224
232
  const state = deps.stateStore.require(request);
225
233
  if (!state.authorization || !state.canonicalRequest)
226
234
  throw new GatewayError("route_unavailable", 503, "The request was not authenticated and parsed, so this route cannot be served (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");