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
@@ -3,7 +3,7 @@ import { cleanError, unsupportedQuota } from "./quota-contracts";
3
3
  import type { ProviderRegistry } from "../provider-registry";
4
4
  import { parseProviderId } from "../provider-contracts";
5
5
  import { providerBaseUrl } from "../provider-metadata";
6
- import { createProbeFetch } from "../operations/probe-fetch";
6
+ import { createProbeFetch } from "../provider-probe";
7
7
  /**
8
8
  * Verifies an API key against the provider's OpenAI-compatible `/models`
9
9
  * endpoint. For providers that expose no billing/quota surface, a reachable
@@ -98,6 +98,75 @@ function findReset(root: unknown, mapping: QuotaWindowMapping): string | null {
98
98
  return mapping.resetFallback ?? null;
99
99
  }
100
100
 
101
+ /**
102
+ * The raw per-window candidates the shared emission core resolves.
103
+ *
104
+ * Both quota walkers (`parseQuotaWindows` and `parseLimitWindows`) feed their
105
+ * independently-read fields into one resolution so the percent fallback chain
106
+ * and the skip rule cannot drift between them.
107
+ */
108
+ export interface WindowFieldCandidates {
109
+ /** Provider-preferred percent (e.g. a `derivePercent` result). */
110
+ readonly explicitPercent: number | null;
111
+ /** Raw used-percent read from the payload. */
112
+ readonly usedPercent: number | null;
113
+ /** Raw remaining-percent read from the payload. */
114
+ readonly remainingPercent: number | null;
115
+ /** Absolute used value. */
116
+ readonly used: number | null;
117
+ /** Absolute limit value. */
118
+ readonly limit: number | null;
119
+ /** Reset timestamp; required to emit a reset-only window. */
120
+ readonly reset: string | null;
121
+ /**
122
+ * Whether percent may be derived from absolute used/limit when no percentage
123
+ * was reported. Only `parseQuotaWindows` does this — `parseLimitWindows`
124
+ * reports percentages and resets the upstream gave it, never ones it
125
+ * invented, so Cline entries stay reset-only instead of gaining a derived
126
+ * percent.
127
+ */
128
+ readonly deriveFromUsedLimit: boolean;
129
+ /**
130
+ * When true, a reset-only window (percent null, reset present) is still
131
+ * emitted. When false/absent, an entry with neither percent nor reset is
132
+ * dropped — never rendered as 0% — and a reset-only window is dropped too.
133
+ */
134
+ readonly emitWithoutPercent?: boolean;
135
+ }
136
+
137
+ /**
138
+ * Shared per-window field resolution: the percent fallback chain plus the
139
+ * "emit or skip" decision.
140
+ *
141
+ * Percent resolution order is exact: explicit percent, then used-percent, then
142
+ * remaining-percent inverted (100 − remaining), then derived from absolute
143
+ * used/limit (only when the limit is positive and `deriveFromUsedLimit` is
144
+ * set). The result is never coerced to 0: a missing percentage stays `null`,
145
+ * so a window can be omitted or rendered as a countdown depending on
146
+ * `emitWithoutPercent`.
147
+ */
148
+ export function resolveWindowEmission(candidates: WindowFieldCandidates): {
149
+ readonly emit: boolean;
150
+ readonly percent: number | null;
151
+ } {
152
+ let percent = candidates.explicitPercent;
153
+ if (percent === null && candidates.usedPercent !== null) percent = candidates.usedPercent;
154
+ if (percent === null && candidates.remainingPercent !== null) {
155
+ percent = Math.max(0, 100 - candidates.remainingPercent);
156
+ }
157
+ if (
158
+ percent === null &&
159
+ candidates.deriveFromUsedLimit &&
160
+ candidates.used !== null &&
161
+ candidates.limit !== null &&
162
+ candidates.limit > 0
163
+ ) {
164
+ percent = (candidates.used / candidates.limit) * 100;
165
+ }
166
+ const emit = percent !== null || (candidates.emitWithoutPercent === true && candidates.reset !== null);
167
+ return { emit, percent };
168
+ }
169
+
101
170
  /** Parses a generic quota payload using the provided window mappings. */
102
171
  export function parseQuotaWindows(
103
172
  body: unknown,
@@ -110,27 +179,30 @@ export function parseQuotaWindows(
110
179
  for (const mapping of windowMappings) {
111
180
  const used = findNumber(payload, mapping.usedPaths);
112
181
  const limit = findNumber(payload, mapping.limitPaths);
182
+ const reset = findReset(payload, mapping);
113
183
 
114
- let percent: number | null = mapping.derivePercent ? mapping.derivePercent(payload) : null;
115
- if (percent === null) {
116
- const usedPercent = findNumber(payload, mapping.usedPercentPaths);
117
- const remainingPercent = findNumber(payload, mapping.remainingPercentPaths);
118
- if (usedPercent !== null) {
119
- percent = usedPercent;
120
- } else if (remainingPercent !== null) {
121
- percent = Math.max(0, 100 - remainingPercent);
122
- } else if (used !== null && limit !== null && limit > 0) {
123
- percent = (used / limit) * 100;
124
- }
125
- }
126
- if (percent !== null && mapping.valueMultiplier !== undefined && mapping.valueMultiplier !== 0) {
127
- percent = Math.min(100, Math.max(0, percent * mapping.valueMultiplier));
128
- }
184
+ const { emit, percent } = resolveWindowEmission({
185
+ explicitPercent: mapping.derivePercent ? mapping.derivePercent(payload) : null,
186
+ usedPercent: findNumber(payload, mapping.usedPercentPaths),
187
+ remainingPercent: findNumber(payload, mapping.remainingPercentPaths),
188
+ used,
189
+ limit,
190
+ reset,
191
+ deriveFromUsedLimit: true,
192
+ ...(mapping.emitWithoutPercent === undefined ? {} : { emitWithoutPercent: mapping.emitWithoutPercent }),
193
+ });
194
+ if (!emit) continue;
129
195
 
130
- const reset = findReset(payload, mapping);
131
- if (percent === null && !(mapping.emitWithoutPercent === true && reset !== null)) continue;
196
+ let resolvedPercent = percent;
197
+ if (
198
+ resolvedPercent !== null &&
199
+ mapping.valueMultiplier !== undefined &&
200
+ mapping.valueMultiplier !== 0
201
+ ) {
202
+ resolvedPercent = Math.min(100, Math.max(0, resolvedPercent * mapping.valueMultiplier));
203
+ }
132
204
 
133
- windows.push(percentWindow(mapping.kind, mapping.label, percent, reset, used, limit, "independent"));
205
+ windows.push(percentWindow(mapping.kind, mapping.label, resolvedPercent, reset, used, limit, "independent"));
134
206
  }
135
207
 
136
208
  if (options.extraWindows) windows.push(...options.extraWindows(payload));
@@ -18,8 +18,10 @@ import type { ProviderId } from "../provider-contracts";
18
18
  import type { ProviderRegistry } from "../provider-registry";
19
19
  import { quotaLens, setCachedQuota } from "./cache";
20
20
  import { persistQuotaSnapshot } from "./snapshot";
21
+ import { applyAccountVerdict } from "../operations/account-health-service";
21
22
  import { asJsonbRecord } from "../../shared/coerce";
22
23
  import { KIRO_DEFAULT_LABELS } from "../integrations/kiro/kiro-oauth";
24
+ import { invalidateCredentialCache } from "../operations/provider-credential-service";
23
25
 
24
26
  /**
25
27
  * The stock Kiro fallback labels as a SQL literal list. Built once from the
@@ -33,7 +35,7 @@ const KIRO_DEFAULT_LABEL_IN = sql.raw(
33
35
  );
34
36
 
35
37
  /** Why an account quota refresh failed; `null` means the fetch succeeded. */
36
- export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
38
+ export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed" | "auth_invalidated";
37
39
 
38
40
  export interface QuotaRefreshOutcome {
39
41
  readonly quota: ProviderQuotaResult;
@@ -149,13 +151,69 @@ function isTransientTimeout(error: string | null | undefined): boolean {
149
151
  export async function recordAccountCheck(
150
152
  db: CartethyiaDatabase,
151
153
  accountId: string,
152
- outcome: { ok: boolean; error?: string | null; category?: string | null },
154
+ outcome: { ok: boolean; error?: string | null; category?: string | null; authInvalidated?: boolean },
155
+ snapshotInvalidator?: { invalidate(): unknown },
153
156
  ): Promise<void> {
154
- const activeOnly = and(
155
- eq(providerAccounts.id, accountId),
156
- eq(providerAccounts.status, "active"),
157
- );
157
+ if (outcome.authInvalidated === true) {
158
+ const disabled = await applyAccountVerdict(db, {
159
+ accountId,
160
+ status: "disabled",
161
+ reason: outcome.error ?? "Provider rejected account authentication",
162
+ category: "auth_invalidated",
163
+ // No deadline: a credential rejected as invalid cannot heal by waiting,
164
+ // and the OAuth refresh sweep must not keep spending refresh attempts.
165
+ cooldownUntil: null,
166
+ });
167
+ if (disabled) {
168
+ invalidateCredentialCache(accountId);
169
+ await snapshotInvalidator?.invalidate();
170
+ }
171
+ return;
172
+ }
158
173
  if (outcome.ok) {
174
+ // A successful quota refresh is the freshest evidence about the account's
175
+ // health: heal a cooldown that the refresh shows is no longer justified, so
176
+ // the account returns to rotation immediately instead of waiting out the
177
+ // remainder of a stale fallback window. Only healable scopes heal — an
178
+ // auth/policy cooldown must never be lifted by a quota report (the
179
+ // credential is still refused). `lastErrorCategory` carries the category
180
+ // that parked the account, so the heal is scoped by the same verdict the
181
+ // health machine wrote.
182
+ const healableCategories = new Set([
183
+ "quota_exhausted",
184
+ "rate_limit_transient",
185
+ "model_capacity",
186
+ "model_unreachable",
187
+ "unknown",
188
+ "check_failed",
189
+ ]);
190
+ const rows = await db
191
+ .select({ status: providerAccounts.status, lastErrorCategory: providerAccounts.lastErrorCategory })
192
+ .from(providerAccounts)
193
+ .where(eq(providerAccounts.id, accountId))
194
+ .limit(1);
195
+ const account = rows[0];
196
+ const healable =
197
+ account?.status === "cooldown" &&
198
+ (account.lastErrorCategory === null || healableCategories.has(account.lastErrorCategory));
199
+ if (healable) {
200
+ const healed = await db
201
+ .update(providerAccounts)
202
+ .set({
203
+ status: "active",
204
+ lastSuccessAt: new Date(),
205
+ lastError: null,
206
+ lastErrorCategory: null,
207
+ lastErrorAt: null,
208
+ consecutiveFailures: 0,
209
+ cooldownUntil: null,
210
+ lastRecoveredAt: new Date(),
211
+ })
212
+ .where(eq(providerAccounts.id, accountId))
213
+ .returning({ id: providerAccounts.id });
214
+ if (healed.length > 0) await snapshotInvalidator?.invalidate();
215
+ return;
216
+ }
159
217
  await db
160
218
  .update(providerAccounts)
161
219
  .set({
@@ -165,7 +223,7 @@ export async function recordAccountCheck(
165
223
  lastErrorAt: null,
166
224
  consecutiveFailures: 0,
167
225
  })
168
- .where(activeOnly);
226
+ .where(and(eq(providerAccounts.id, accountId), eq(providerAccounts.status, "active")));
169
227
  return;
170
228
  }
171
229
  const transient = isTransientTimeout(outcome.error);
@@ -186,7 +244,7 @@ export async function recordAccountCheck(
186
244
  lastErrorAt: new Date(),
187
245
  ...(transient ? {} : { consecutiveFailures: sql`${providerAccounts.consecutiveFailures} + 1` }),
188
246
  })
189
- .where(activeOnly)
247
+ .where(and(eq(providerAccounts.id, accountId), eq(providerAccounts.status, "active")))
190
248
  .returning({ consecutiveFailures: providerAccounts.consecutiveFailures });
191
249
  if (transient) return;
192
250
  const failures = failing[0]?.consecutiveFailures ?? 0;
@@ -196,13 +254,18 @@ export async function recordAccountCheck(
196
254
  // pass. The cooldown clears itself; a success before it elapses resets the
197
255
  // count above.
198
256
  if (failures >= QUOTA_FAILURE_PARK_THRESHOLD) {
199
- await db
200
- .update(providerAccounts)
201
- .set({
202
- status: "cooldown",
203
- cooldownUntil: new Date(Date.now() + QUOTA_FAILURE_COOLDOWN_MS),
204
- })
205
- .where(activeOnly);
257
+ // Converge on the health application seam (proposal §3.5): quota refresh
258
+ // parking is a producer into `applyAccountVerdict`, not a separate state
259
+ // machine.
260
+ const parked = await applyAccountVerdict(db, {
261
+ accountId,
262
+ status: "cooldown",
263
+ reason: outcome.error ?? "Account check failed",
264
+ category: outcome.category ?? "quota_fetch_failed",
265
+ cooldownUntil: new Date(Date.now() + QUOTA_FAILURE_COOLDOWN_MS),
266
+ fromStatus: "active",
267
+ });
268
+ if (parked) await snapshotInvalidator?.invalidate();
206
269
  }
207
270
  }
208
271
 
@@ -407,7 +470,11 @@ async function runQuotaRefresh(
407
470
  error: error instanceof Error ? error.message : "Failed to refresh quota",
408
471
  };
409
472
  }
410
- failure = quota.error === null ? null : "quota_fetch_failed";
473
+ failure = quota.error === null
474
+ ? null
475
+ : quota.authInvalidated === true
476
+ ? "auth_invalidated"
477
+ : "quota_fetch_failed";
411
478
  }
412
479
 
413
480
  // Cache even a failed fetch: the error view ("credential expired", "quota
@@ -427,7 +494,13 @@ async function runQuotaRefresh(
427
494
  try {
428
495
  await deps.db
429
496
  .update(providerAccounts)
430
- .set({ quotaPlan: quota.plan })
497
+ .set({
498
+ quotaPlan: quota.plan,
499
+ // Freshness stamp for the plan column: routing uses it to downgrade
500
+ // a plan older than the freshness horizon to `unknown` instead of
501
+ // trusting a weeks-old string.
502
+ quotaPlanFetchedAt: new Date(),
503
+ })
431
504
  .where(eq(providerAccounts.id, target.accountId));
432
505
  } catch (error) {
433
506
  log.warn(`[quota] failed to record plan for account=${target.accountId}`, error as Error);
@@ -469,10 +542,16 @@ async function runQuotaRefresh(
469
542
  }
470
543
  }
471
544
  try {
472
- await recordAccountCheck(deps.db, target.accountId, {
473
- ok: quota.error === null,
474
- ...(quota.error === null ? {} : { error: quota.error, category: failure ?? "quota_fetch_failed" }),
475
- });
545
+ await recordAccountCheck(
546
+ deps.db,
547
+ target.accountId,
548
+ {
549
+ ok: quota.error === null,
550
+ ...(quota.error === null ? {} : { error: quota.error, category: failure ?? "quota_fetch_failed" }),
551
+ ...(quota.authInvalidated === true ? { authInvalidated: true } : {}),
552
+ },
553
+ deps.snapshotInvalidator,
554
+ );
476
555
  } catch (error) {
477
556
  // A stamp failure must not lose the freshly fetched quota.
478
557
  log.warn(`[quota] failed to stamp check outcome for account=${target.accountId}`, error as Error);
@@ -1,7 +1,7 @@
1
1
  /** Bounded process-local authorization cache; shared-cache I/O stays in the auth service. */
2
2
  export const AUTH_CACHE_TTL_MS = 3_000;
3
3
  const AUTH_CACHE_MAX_ENTRIES = 10_000;
4
- const AUTH_NEGATIVE_CACHE_TTL_MS = 1_500;
4
+ export const AUTH_NEGATIVE_CACHE_TTL_MS = 1_500;
5
5
  const AUTH_FAILURE_COOLDOWN_MS = 1_000;
6
6
 
7
7
  interface CacheEntry<T> {
@@ -21,8 +21,12 @@ import type { ApiKeyModelAccessMode } from "../persistence/schema";
21
21
  * snapshot's consumed value is a reference point, so a brief stale window
22
22
  * cannot meaningfully over-consume a budget.
23
23
  *
24
- * Negative results (unknown/revoked tokens) are deliberately NOT cached, so
25
- * a newly minted key is usable immediately.
24
+ * Unknown or revoked tokens are negative-cached for
25
+ * `AUTH_NEGATIVE_CACHE_TTL_MS` (1.5s): a token rejected within that window
26
+ * stays refused even if its key is minted or un-revoked in between. A newly
27
+ * minted key is usable immediately only because `invalidateApiKeyCache`
28
+ * clears the negative entries when a mutation lands — not because negatives
29
+ * are absent.
26
30
  */
27
31
  const authorizationCache = createApiKeyAuthorizationCache<ResolvedApiKey>();
28
32
 
@@ -9,7 +9,7 @@ import {
9
9
  ipv6ToBigInt,
10
10
  isLoopbackAddress,
11
11
  unwrapMappedIpv4,
12
- } from "../network/ip-utils";
12
+ } from "../network/util/ip";
13
13
 
14
14
  export { isLoopbackAddress };
15
15
 
@@ -20,7 +20,7 @@ import { modelsDevCatalog } from "../../providers/discovery/models-dev-catalog";
20
20
  import {
21
21
  disablePoolForProxyHttpStatus,
22
22
  recordPoolDispatchOutcome,
23
- } from "../../network/pool-health-machine";
23
+ } from "../../network/pool/health";
24
24
  import { ProxyConnectRefusalError } from "../../network/pool/agent";
25
25
  import { captureDepthMaxBytes, providerCaptureMaxBytes } from "./capture-policy";
26
26
 
@@ -622,19 +622,20 @@ export function nativeCompletionBase(input: {
622
622
  | "tokensPerSec"
623
623
  > {
624
624
  return {
625
- providerId: input.providerId,
626
- modelId: input.modelId,
627
- tenantId: input.tenantId,
628
- ingressBody: input.ingressBody,
629
- terminal: true,
630
- ...(input.accountId === undefined ? {} : { accountId: input.accountId }),
631
- ...(input.accountLabel === undefined ? {} : { accountLabel: input.accountLabel }),
632
- ...(input.networkPoolId === undefined ? {} : { networkPoolId: input.networkPoolId }),
633
- ...(input.lease === undefined ? {} : { lease: input.lease }),
634
- db: input.db,
635
- ...(input.telemetryBuffer === undefined ? {} : { telemetryBuffer: input.telemetryBuffer }),
636
- ...(input.snapshotService === undefined ? {} : { snapshotService: input.snapshotService }),
637
- ...(input.providerCapture === undefined ? {} : { providerCapture: input.providerCapture }),
625
+ ...completionContext({
626
+ providerId: input.providerId,
627
+ modelId: input.modelId,
628
+ tenantId: input.tenantId,
629
+ ingressBody: input.ingressBody,
630
+ ...(input.providerCapture === undefined ? {} : { providerCapture: input.providerCapture }),
631
+ db: input.db,
632
+ ...(input.accountId === undefined ? {} : { accountId: input.accountId }),
633
+ ...(input.accountLabel === undefined ? {} : { accountLabel: input.accountLabel }),
634
+ ...(input.networkPoolId === undefined ? {} : { networkPoolId: input.networkPoolId }),
635
+ ...(input.lease === undefined ? {} : { lease: input.lease }),
636
+ ...(input.telemetryBuffer === undefined ? {} : { telemetryBuffer: input.telemetryBuffer }),
637
+ ...(input.snapshotService === undefined ? {} : { snapshotService: input.snapshotService }),
638
+ }),
638
639
  ...(input.usage === undefined ? {} : { usage: input.usage }),
639
640
  ...(input.commitUsage === undefined ? {} : { commitUsage: input.commitUsage }),
640
641
  };
@@ -20,7 +20,7 @@ import {
20
20
  import { resolveRouteMaxAttempts } from "../../config/timeouts";
21
21
  import type { OAuthTokenRefresher } from "../../providers/authentication/oauth-refresh-service";
22
22
  import type { OAuthRefreshService } from "../../providers/authentication/oauth-refresh-service";
23
- import type { ValidatedNetworkBindingFactory } from "../../network/pool/resolver";
23
+ import type { ValidatedNetworkBindingFactory } from "../../network/egress/factory";
24
24
  import type { ByokUpstreamHost } from "../../providers/operations/provider-catalog-service";
25
25
  import type { AdmissionLease } from "../../security/admission/contracts";
26
26
  import type { RouteCandidate, Reservation, RouteSnapshotService } from "../routing/route-model";
@@ -32,7 +32,7 @@ import { metrics } from "../../observability/metrics";
32
32
  import type { NetworkPoolSelector } from "../../network/pool/selector";
33
33
  import type { ProxyRequestState } from "../request/state";
34
34
  import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
35
- import { flagPoolBlocked, flagPoolCooldown } from "../../network/pool-health";
35
+ import { flagPoolOnEgressFailure } from "../../network/pool/health";
36
36
  import {
37
37
  completeAttempt,
38
38
  errorClientResponseBody,
@@ -41,7 +41,7 @@ import {
41
41
  type ProviderExchangeCapture,
42
42
  } from "./attempt-finalize";
43
43
  import { repriceUsage } from "../../providers/provider-usage";
44
- import { classifyEgressFailure } from "../../network/rate-limit-verdict";
44
+ import { classifyEgressFailure } from "../../network/verdict";
45
45
  import { isOAuthCredentialInvalidated } from "./retry-policy";
46
46
  import { drainAbortReason } from "../shutdown-notice";
47
47
 
@@ -61,6 +61,41 @@ function comboCandidateKey(candidate: RouteCandidate): string {
61
61
  ].join(":");
62
62
  }
63
63
 
64
+ /**
65
+ * Extra dials allowed for a mid-response upstream socket drop when the
66
+ * candidate list is exhausted (single-account providers). One extra dial
67
+ * recovers the transient reset; more would stack latency on a dying origin.
68
+ */
69
+ const MAX_SOCKET_DROP_RETRIES = 1;
70
+
71
+ /**
72
+ * Normalizes a raw (non-GatewayError) failure before it escapes the loop.
73
+ *
74
+ * A transport-level throw — e.g. Bun's `TypeError` with
75
+ * `code: "ECONNRESET"` when the upstream socket dies mid-response — carries a
76
+ * `.code` of its own. The error middleware reads that `.code` for Elysia
77
+ * built-in errors, so a raw socket drop leaked to the client as
78
+ * `{"code":"ECONNRESET","status":400}` with origin `cartethyia`: the wrong
79
+ * code, the wrong status, and the wrong blame. The classifier already knows
80
+ * what this is (`transport_unavailable`, 502, upstream) — materialize it as a
81
+ * real GatewayError so the client and the captured Client Response see the
82
+ * same stable envelope every other upstream transport failure gets.
83
+ */
84
+ function normalizeEscapeError(error: unknown, signal: AbortSignal): unknown {
85
+ if (error instanceof GatewayError) return error;
86
+ const terminal = classifyTerminalOutcome(error, signal);
87
+ if (terminal.errorCategory === "transport_unavailable") {
88
+ return new GatewayError(
89
+ "transport_unavailable",
90
+ 502,
91
+ "The upstream closed the connection mid-response (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻",
92
+ {},
93
+ "upstream",
94
+ );
95
+ }
96
+ return error;
97
+ }
98
+
64
99
  /**
65
100
  * True when the rejection is about the API key or the gateway's own policy,
66
101
  * not about the provider account that was dialled.
@@ -321,7 +356,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
321
356
  status: terminal.status === "cancelled" ? "cancelled" : "failed",
322
357
  });
323
358
  }
324
- throw lastError;
359
+ throw normalizeEscapeError(lastError, state.abortController.signal);
325
360
  }
326
361
  }
327
362
  let lease: AdmissionLease | undefined;
@@ -381,6 +416,13 @@ export async function runAttemptLoop<TResult, TAdapter>(
381
416
  // names the real cause.
382
417
  const drain = drainAbortReason(state.abortController.signal.reason);
383
418
  if (drain !== undefined) error = drain;
419
+ // Normalize a raw transport throw (e.g. Bun's `TypeError` with
420
+ // `code: "ECONNRESET"` when the upstream socket dies mid-response) into
421
+ // the stable `transport_unavailable`/502/upstream GatewayError **before**
422
+ // classification, so the telemetry row, the captured Client Response, and
423
+ // the escaped error all agree — and the raw `.code` never leaks to the
424
+ // client as a fake Elysia built-in code (see `normalizeEscapeError`).
425
+ error = normalizeEscapeError(error, state.abortController.signal);
384
426
  lastError = error;
385
427
  // A drain is a *server* close, not a client cancel: it must be recorded
386
428
  // as a failure with the shutdown code, and it is never retryable.
@@ -411,21 +453,33 @@ export async function runAttemptLoop<TResult, TAdapter>(
411
453
  // the pool excluded. Both flag helpers write the local entry
412
454
  // synchronously, so the ordering holds even though the Redis mirror is
413
455
  // fire-and-forget.
414
- if (networkPoolId && deps.poolSelector && error instanceof GatewayError) {
415
- if (rateLimitedPool) {
416
- flagPoolCooldown(deps.poolSelector, deps.db, networkPoolId, candidate.provider_id, error);
417
- } else if (blockedPool) {
418
- // The address itself is refused, not the request: retire the egress
419
- // so the next attempt leaves by a different one instead of retrying
420
- // a proxy the upstream has already flagged.
421
- flagPoolBlocked(deps.poolSelector, deps.db, networkPoolId, candidate.provider_id, error);
422
- }
423
- }
456
+ flagPoolOnEgressFailure(
457
+ deps.poolSelector,
458
+ deps.db,
459
+ networkPoolId,
460
+ candidate.provider_id,
461
+ error,
462
+ egressVerdict,
463
+ );
464
+ // A mid-response upstream socket drop (`transport_unavailable`) is the one
465
+ // retryable failure that receives an extra dial even when the candidate
466
+ // list is exhausted: an origin that closes the connection before the
467
+ // response is often transient (Trae's edge resets under concurrency),
468
+ // and the fresh dial recovers it — verified live 2026-11-09, 7/7
469
+ // recovered on the first retry. Without this, a single-account provider
470
+ // (the common case) tripped `index === candidates.length - 1` on the very
471
+ // first attempt and the client saw the raw socket error. The retry is
472
+ // bounded to one extra dial per request, never unbounded.
473
+ const memberAttempts = attemptsByMember.get(memberKey) ?? 0;
474
+ const socketDropRetryLeft =
475
+ terminal.errorCategory === "transport_unavailable" &&
476
+ !cancelled &&
477
+ memberAttempts < MAX_SOCKET_DROP_RETRIES;
424
478
  const terminalAttempt =
425
479
  cancelled ||
426
480
  isClientScopedRejection(error) ||
427
481
  !isRetryableFailure(error) ||
428
- (index === candidates.length - 1 && !poolScopedFailure);
482
+ ((index === candidates.length - 1 && !poolScopedFailure) && !socketDropRetryLeft);
429
483
  // Track only the most recent fired exchange; the array this replaced
430
484
  // accumulated every attempt's capture until the loop returned.
431
485
  if (providerCapture.request !== null) lastFiredCapture = providerCapture;
@@ -474,7 +528,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
474
528
  // A 403 can be a deterministic provider policy rejection, not an
475
529
  // invalid OAuth credential. Only the evidence-based classifier above
476
530
  // may trigger a refresh.
477
- const candidateKey = `${candidate.provider_id}:${candidate.provider_account_id ?? "global"}:${candidate.model_id}:${candidate.endpoint}`;
531
+ const candidateKey = comboCandidateKey(candidate);
478
532
  const authInvalidated = isOAuthCredentialInvalidated(error);
479
533
  if (
480
534
  !cancelled &&
@@ -499,7 +553,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
499
553
  }
500
554
  if (terminalAttempt) {
501
555
  metrics.proxy_requests_total.inc(1, { status: cancelled ? "cancelled" : "failed" });
502
- throw error;
556
+ throw normalizeEscapeError(error, state.abortController.signal);
503
557
  }
504
558
  // A failed combo member stays flagged for this request. Move it behind
505
559
  // every candidate that has not failed yet, so the next attempt cannot
@@ -542,7 +596,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
542
596
  // as the sleep's own error would leave `proxy_requests_total` missing
543
597
  // the request.
544
598
  metrics.proxy_requests_total.inc(1, { status: "cancelled" });
545
- throw lastError;
599
+ throw normalizeEscapeError(lastError, state.abortController.signal);
546
600
  }
547
601
  }
548
602
  }
@@ -577,5 +631,5 @@ export async function runAttemptLoop<TResult, TAdapter>(
577
631
  }));
578
632
  }
579
633
  metrics.proxy_requests_total.inc(1, { status: "failed" });
580
- throw lastError;
634
+ throw normalizeEscapeError(lastError, state.abortController.signal);
581
635
  }
@@ -27,6 +27,7 @@ import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt
27
27
  import { runAttemptLoop } from "./attempt-loop";
28
28
  import { captureProviderRateLimit } from "./ratelimit-capture";
29
29
  import {
30
+ forwardUpstreamResponse,
30
31
  hasScope,
31
32
  jsonResponse,
32
33
  networkFetchContext,
@@ -133,14 +134,7 @@ async function passThrough(
133
134
  responseBody: null,
134
135
  });
135
136
  metrics.proxy_requests_total.inc(1, { status: "completed" });
136
- return new Response(upstream.body, {
137
- status: upstream.status,
138
- headers: {
139
- "content-type": upstream.headers.get("content-type") ?? "text/event-stream",
140
- "cache-control": "no-store",
141
- "x-request-id": state.requestId,
142
- },
143
- });
137
+ return forwardUpstreamResponse(upstream, state.requestId, "text/event-stream");
144
138
  }
145
139
 
146
140
  /** Builds the handler for one generation surface. */
@@ -11,7 +11,7 @@ import type { CartethyiaDatabase } from "../../persistence/db-handle";
11
11
  import type { ProviderAdapter } from "../../providers/provider-contracts";
12
12
  import type { OAuthRefreshService, OAuthTokenRefresher } from "../../providers/authentication/oauth-refresh-service";
13
13
  import type { NetworkPoolSelector } from "../../network/pool/selector";
14
- import type { ValidatedNetworkBindingFactory } from "../../network/pool/resolver";
14
+ import type { ValidatedNetworkBindingFactory } from "../../network/egress/factory";
15
15
  import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
16
16
  import type { ProxyRequestPreparer } from "../request/preparer";
17
17
  import type { ProxyRequestStateStore } from "../request/state";
@@ -5,7 +5,7 @@ import type { ApiKeyAuthorizationSnapshot } from "../../security/api-key-auth";
5
5
  import type { ByokUpstreamHost } from "../../providers/operations/provider-catalog-service";
6
6
  import type { PoolRotation, PoolSelectionFailure } from "../../network/pool/selector";
7
7
  import type { NetworkPoolSelector } from "../../network/pool/selector";
8
- import type { ValidatedNetworkBindingFactory } from "../../network/pool/resolver";
8
+ import type { ValidatedNetworkBindingFactory } from "../../network/egress/factory";
9
9
  import type { RouteCandidate as RouteCandidate, Reservation, RoutePlan } from "../routing/route-model";
10
10
  import type { RoutingEngine } from "../routing/router";
11
11
 
@@ -71,6 +71,31 @@ export function jsonResponse(payload: unknown, status: number, requestId: string
71
71
  });
72
72
  }
73
73
 
74
+ /**
75
+ * Forwards an upstream response body verbatim with the shared native-response
76
+ * header set.
77
+ *
78
+ * The native handlers (System One, responses-compact, token-count,
79
+ * generation's passThrough) each spelled the same three headers by hand; the
80
+ * copies drifted at least once (generation's JSON builder lost `cache-control`).
81
+ * Status and body come from the upstream untouched; only the content-type
82
+ * default differs per route (SSE vs JSON) and is passed per call.
83
+ */
84
+ export function forwardUpstreamResponse(
85
+ response: Response,
86
+ requestId: string,
87
+ defaultContentType = "application/json",
88
+ ): Response {
89
+ return new Response(response.body, {
90
+ status: response.status,
91
+ headers: {
92
+ "content-type": response.headers.get("content-type") ?? defaultContentType,
93
+ "cache-control": "no-store",
94
+ "x-request-id": requestId,
95
+ },
96
+ });
97
+ }
98
+
74
99
  /**
75
100
  * Strips a thinking suffix (`model(high)`) from the model name and rebuilds
76
101
  * the body with the bare model when it changed. Returns the bare model and