cartethyia 2.8.0 → 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 (348) hide show
  1. package/.env.example +3 -3
  2. package/dist/dashboard/assets/{BannedPage-Cc0vKpMG.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bqj2aa1T.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-CXsaOAO2.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BeLpJ4ma.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-DnV0uZ3m.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-CBxAjo63.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-B5yEE0k6.js → HealthEventsModal-Clq8E9cW.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BWFLECmz.js → InjectSystemPromptPanel-6Teoq9Se.js} +1 -1
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-CgoPc5B9.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-D9bFQern.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-B_p4tL8k.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-D_0PiZtg.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/{ProvidersPage-CLI2bJTK.js → ProvidersPage-C3fYuu6i.js} +1 -1
  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-DA-a1iSN.js → QuotaPage-D1RV_2BX.js} +1 -1
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-CctTga2C.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-febLU4r1.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-M6fvR5Wz.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-nyeuRqz9.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BFOlvrJC.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-CqTLw5e7.js → assert-BMcj1xZE.js} +1 -1
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-C83EqLB0.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-D1xn7pIe.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-DU3Lq5Lm.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-Dw1U_drK.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-CEL5Sg_J.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-BjFR5UHy.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-HvBW0AjI.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-CECVELyy.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-DYYofQ7W.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-BSu3gFlt.js → entry-DScKYvZj.js} +3 -3
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-DLW4i8zK.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-1-fCqe64.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-DGvU2oLK.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-iNnO1X2K.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-0q8UGypb.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-C0w2bQjJ.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-M5aJ_xCa.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-BKeQGT3K.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-CivAk3da.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-CpzvI5ep.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/{routing-BFQX2VBc.js → routing-8jZr-3WE.js} +1 -1
  91. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  92. package/dist/dashboard/assets/{select-CXbck0jG.js → select-oO1FxMVb.js} +1 -1
  93. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  94. package/dist/dashboard/assets/{settings-C_XvMHh9.js → settings-CLr3kdIx.js} +1 -1
  95. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  96. package/dist/dashboard/assets/{stack-DggRiCxE.js → stack-Y3pDteZr.js} +1 -1
  97. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  98. package/dist/dashboard/assets/{state-ClFzfbYp.js → state-CArX_Iak.js} +1 -1
  99. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  100. package/dist/dashboard/assets/{switch-CTIFxKqS.js → switch-DwlwmDO-.js} +1 -1
  101. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  102. package/dist/dashboard/assets/{useMutation-BClG3yWj.js → useMutation-DJ9GYEgf.js} +1 -1
  103. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  104. package/dist/dashboard/index.html +2 -2
  105. package/dist/dashboard/index.html.gz +0 -0
  106. package/dist/dashboard/precompressed-assets.json +51 -51
  107. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  108. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  109. package/package.json +1 -1
  110. package/src/boot/app.ts +1 -1
  111. package/src/boot/dependencies.ts +4 -6
  112. package/src/config/providers.ts +6 -5
  113. package/src/config/spec.ts +8 -8
  114. package/src/console/backup/nine-router.ts +71 -10
  115. package/src/console/backup/service.ts +8 -1
  116. package/src/console/console-router.ts +1 -1
  117. package/src/console/domain-registration.ts +7 -2
  118. package/src/console/inferhub/client.ts +1 -1
  119. package/src/console/observability/live.ts +2 -2
  120. package/src/console/providers/catalog/contracts.ts +9 -3
  121. package/src/console/providers/catalog/model-operations.ts +2 -3
  122. package/src/console/providers/catalog/provider-operations.ts +1 -1
  123. package/src/console/providers/catalog/routes.ts +2 -2
  124. package/src/console/providers/catalog/store.ts +1914 -1907
  125. package/src/console/providers/detail/routes.ts +1 -0
  126. package/src/console/providers/detail/store.ts +4 -0
  127. package/src/console/providers/oauth/callback-listener.ts +30 -11
  128. package/src/console/providers/oauth/device-egress.ts +123 -0
  129. package/src/console/providers/oauth/routes.ts +123 -16
  130. package/src/console/quota/account-quota-tenant.ts +5 -5
  131. package/src/console/quota/account-test-egress.ts +2 -2
  132. package/src/console/quota/daily-growth-job.ts +1 -1
  133. package/src/console/routing/pools/contracts.ts +1 -1
  134. package/src/console/routing/pools/operations.ts +1 -1
  135. package/src/console/routing/pools/probe-verdict.ts +2 -2
  136. package/src/console/routing/pools/relay-deploy.ts +1 -1
  137. package/src/console/routing/pools/store.ts +4 -4
  138. package/src/network/client.ts +67 -0
  139. package/src/network/egress/bidi-session.ts +322 -0
  140. package/src/network/egress/dial.ts +201 -0
  141. package/src/network/egress/factory.ts +191 -0
  142. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  143. package/src/network/egress/tls-profile.ts +123 -0
  144. package/src/network/egress/ws.ts +175 -0
  145. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  146. package/src/network/pool/agent.ts +2 -2
  147. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +7 -7
  148. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  149. package/src/network/pool/resolver.ts +10 -849
  150. package/src/network/pool/selector.ts +2 -2
  151. package/src/network/ssrf.ts +1 -1
  152. package/src/network/{retry.ts → util/retry.ts} +1 -1
  153. package/src/observability/telemetry-buffer.ts +1 -1
  154. package/src/persistence/schema.ts +9 -0
  155. package/src/persistence/ttl-cache.ts +1 -1
  156. package/src/protocol/codecs.ts +58 -1
  157. package/src/protocol/request/messages.ts +31 -18
  158. package/src/protocol/response/messages.ts +34 -7
  159. package/src/protocol/translation/quirks.ts +15 -0
  160. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  161. package/src/providers/adapters/compatible-adapter.ts +5 -0
  162. package/src/providers/authentication/oauth-client.ts +40 -6
  163. package/src/providers/authentication/oauth-device-flow.ts +7 -3
  164. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  165. package/src/providers/authentication/oauth-refresh-service.ts +766 -739
  166. package/src/providers/default-registry.ts +76 -0
  167. package/src/providers/discovery/discovery-types.ts +7 -0
  168. package/src/providers/discovery/probe-phases.ts +6 -2
  169. package/src/providers/discovery/probing-service.ts +7 -0
  170. package/src/providers/integrations/antigravity/antigravity-protocol.ts +11 -0
  171. package/src/providers/integrations/buddy/account.ts +969 -0
  172. package/src/providers/integrations/buddy/capabilities.ts +10 -10
  173. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  174. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +5 -5
  175. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  176. package/src/providers/integrations/buddy/codebuddy/shared.ts +4 -4
  177. package/src/providers/integrations/buddy/{buddy-identity.ts → headers.ts} +138 -140
  178. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  179. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  180. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  181. package/src/providers/integrations/cline/cline.ts +3 -4
  182. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  183. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  184. package/src/providers/integrations/clixad/provider.ts +145 -0
  185. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  186. package/src/providers/integrations/cursor/cursor-auth.ts +1 -1
  187. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  188. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  189. package/src/providers/integrations/cursor/cursor-shared.ts +1 -1
  190. package/src/providers/integrations/cursor/cursor.ts +485 -70
  191. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  192. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  193. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  194. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  195. package/src/providers/integrations/deepai/provider.ts +42 -0
  196. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  197. package/src/providers/integrations/factory/factory-oauth.ts +16 -7
  198. package/src/providers/integrations/freebuff/freebuff.ts +2 -2
  199. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  200. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  201. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  202. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  203. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  204. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  205. package/src/providers/integrations/freepi/freepi.ts +80 -54
  206. package/src/providers/integrations/github/github-discovery.ts +30 -13
  207. package/src/providers/integrations/github/github-oauth.ts +217 -44
  208. package/src/providers/integrations/github/github-quota.ts +22 -5
  209. package/src/providers/integrations/github/github.ts +40 -32
  210. package/src/providers/integrations/grok/grok-oauth.ts +4 -4
  211. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  212. package/src/providers/integrations/hermes/hermes.ts +34 -5
  213. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  214. package/src/providers/integrations/kilo/kilo.ts +1 -1
  215. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  216. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  217. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  218. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  219. package/src/providers/integrations/openrouter/provider.ts +1 -1
  220. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  221. package/src/providers/integrations/perch/perch.ts +3 -3
  222. package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
  223. package/src/providers/integrations/qoder/qoder-adapter.ts +19 -113
  224. package/src/providers/integrations/qoder/qoder-auth.ts +8 -1
  225. package/src/providers/integrations/qoder/qoder-catalog.ts +1 -1
  226. package/src/providers/integrations/qoder/qoder-chat.ts +25 -8
  227. package/src/providers/integrations/qoder/qoder-checkin.ts +1 -1
  228. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  229. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  230. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  231. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  232. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  233. package/src/providers/integrations/qoder/qoder.ts +3 -25
  234. package/src/providers/integrations/sensenova/provider.ts +144 -0
  235. package/src/providers/integrations/trae/trae-quota.ts +15 -1
  236. package/src/providers/integrations/vercel/provider.ts +117 -0
  237. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  238. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  239. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  240. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  241. package/src/providers/integrations/zro/zro.ts +2 -1
  242. package/src/providers/operations/account-refresh-status.ts +14 -0
  243. package/src/providers/operations/client-versions.ts +95 -30
  244. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  245. package/src/providers/operations/provider-caches.ts +1 -1
  246. package/src/providers/operations/provider-credential-service.ts +22 -25
  247. package/src/providers/operations/session-resolution.ts +17 -1
  248. package/src/providers/provider-contracts.ts +2 -2
  249. package/src/providers/provider-metadata.ts +30 -0
  250. package/src/providers/quota/quota-contracts.ts +2 -0
  251. package/src/providers/quota/refresh.ts +25 -3
  252. package/src/security/ip-boundary.ts +1 -1
  253. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  254. package/src/transport/dispatch/attempt-loop.ts +3 -3
  255. package/src/transport/dispatch/handler-deps.ts +1 -1
  256. package/src/transport/dispatch/leases.ts +1 -1
  257. package/src/transport/dispatch/retry-policy.ts +1 -1
  258. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  259. package/src/transport/dispatch/streaming/stream.ts +1 -1
  260. package/src/transport/dispatch/upstream.ts +1 -1
  261. package/src/transport/shutdown-notice.ts +1 -1
  262. package/src/workers/buddy-growth-tasks.ts +8 -8
  263. package/src/workers/daily-checkin.ts +4 -4
  264. package/src/workers/quota-refresh-sweep.ts +1 -1
  265. package/src/workers/zcode-gift-claim.ts +1 -1
  266. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  267. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  268. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  269. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  270. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  271. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  272. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  273. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  274. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  275. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  276. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  277. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +0 -8
  278. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  279. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  280. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  281. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js +0 -9
  282. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  283. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  284. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  285. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  286. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  287. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  288. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  289. package/dist/dashboard/assets/app-Dx2QgsNj.css +0 -2
  290. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  291. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  292. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  293. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  294. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  295. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  296. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  297. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  298. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  299. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  300. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  301. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  302. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  303. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  304. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  305. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  306. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  307. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  308. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  309. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  310. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  311. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  312. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  313. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  314. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  315. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  316. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  317. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  318. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  319. package/src/console/providers/catalog/discover-constants.ts +0 -6
  320. package/src/network/pool-health.ts +0 -198
  321. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  322. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  323. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  324. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  325. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  326. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  327. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  328. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  329. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  330. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  331. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  332. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -188
  333. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  334. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -179
  335. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  336. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  337. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -870
  338. package/src/providers/integrations/qoder/qoder-ide.ts +0 -481
  339. /package/dist/dashboard/assets/{app-DIXLjojg.js → app-C0gTXEoN.js} +0 -0
  340. /package/dist/dashboard/assets/{app-DIXLjojg.js.gz → app-C0gTXEoN.js.gz} +0 -0
  341. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  342. /package/src/network/pool/{byte-accounting.ts → accounting.ts} +0 -0
  343. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  344. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  345. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  346. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  347. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
  348. /package/src/network/{rate-limit-verdict.ts → verdict.ts} +0 -0
@@ -1,20 +1,40 @@
1
1
  /**
2
- * Qoder-specific SSE decoding.
2
+ * Qoder-specific SSE decoding + enveloped stream → canonical events.
3
3
  *
4
4
  * The generic `decodeSseEvents` splits on newlines and treats a bare line
5
- * (no `data:` prefix) with no open event as malformed. Qoder's IDE surface
6
- * embeds literal newline bytes inside JSON string values (e.g. `raw_usage`
7
- * keys like `"data_policy_preferre\n\nnd_count"`), which fragments one logical
8
- * `data:` event into many physical lines — including blank lines that are not
9
- * event boundaries. The generic decoder can throw `Malformed SSE line` on the
5
+ * (no `data:` prefix) with no open event as malformed. Qoder's surfaces embed
6
+ * literal newline bytes inside JSON string values (e.g. `raw_usage` keys like
7
+ * `"data_policy_preferre\n\nnd_count"`), which fragments one logical `data:`
8
+ * event into many physical lines — including blank lines that are not event
9
+ * boundaries. The generic decoder can throw `Malformed SSE line` on the
10
10
  * trailing fragments of those giant events.
11
11
  *
12
12
  * This decoder is tolerant by construction: it accumulates one event's payload
13
13
  * across `data:` lines, bare continuations, and blank lines until the payload
14
14
  * is a complete JSON value or the `[DONE]` sentinel. It never throws on a
15
15
  * bare fragment — payload completeness is the only way an event ends.
16
+ *
17
+ * The enveloped-stream mapping (`qoderBodyToCanonicalEvents`) is the sole
18
+ * chat-stream parser for the live legacy COSY surface
19
+ * (`/algo/.../agent_chat_generation`). It understands Qoder's envelope
20
+ * (`{headers, body, statusCodeValue, statusCode}`), direct OpenAI chunks, and
21
+ * the terminal `raw_usage` event, and maps them to canonical events. The dead
22
+ * surfaces (gRPC `model.chat.ChatService`, IDE `/model/v1`) are not part of
23
+ * this module anymore.
16
24
  */
17
25
 
26
+ import { GatewayError } from "../../../protocol/gateway-error";
27
+ import type { CanonicalEvent, CanonicalStopReason } from "../../../protocol/canonical-model";
28
+ import { usageFromProvider, readReasoningText } from "../../provider-usage";
29
+ import {
30
+ classifyUpstreamError,
31
+ extractUpstreamMessage,
32
+ isUpstreamQueued,
33
+ queueRetryAfterMs,
34
+ upstreamErrorIdentifier,
35
+ } from "../../../transport/failure-policy";
36
+ import { QODER_PROVIDER_ID } from "./qoder-realm";
37
+
18
38
  export interface QoderSseEvent {
19
39
  readonly event: string | null;
20
40
  readonly data: string;
@@ -78,82 +98,441 @@ export async function* decodeQoderSseEvents(
78
98
  return null;
79
99
  }
80
100
  if (trimmed === "[DONE]" || isCompleteJsonValue(trimmed)) {
81
- const data = current;
101
+ const out = current;
82
102
  current = null;
83
103
  eventBytes = 0;
84
- return data;
104
+ return out;
85
105
  }
86
106
  return null;
87
107
  };
88
108
 
89
- const appendPayload = (text: string): void => {
90
- if (current === null) return;
91
- current += text;
92
- eventBytes += text.length;
93
- if (eventBytes > maxEventBytes) throw new Error("SSE event exceeds size bound");
94
- };
95
-
96
- /** Emit a complete open payload, if any. Returns `true` when the stream ends. */
97
- function* emitComplete(): Generator<QoderSseEvent, boolean, void> {
98
- const data = takeComplete();
99
- if (data === null) return false;
100
- if (data.trim() === "[DONE]") return true;
101
- yield { event: null, data };
102
- return false;
109
+ while (true) {
110
+ const { done, value } = await reader.read();
111
+ if (signal?.aborted) return;
112
+ if (done) break;
113
+ lineBuffer += decoder.decode(value, { stream: true });
114
+ // Drain complete lines from the buffer into the accumulation stream.
115
+ let newlineIndex: number;
116
+ while ((newlineIndex = lineBuffer.indexOf("\n")) >= 0) {
117
+ let line = lineBuffer.slice(0, newlineIndex).replace(/\r$/, "");
118
+ lineBuffer = lineBuffer.slice(newlineIndex + 1);
119
+ if (line.startsWith("data:")) {
120
+ const payload = line.slice(5).replace(/^ /, "");
121
+ eventBytes += payload.length;
122
+ if (eventBytes > maxEventBytes) {
123
+ current = null;
124
+ eventBytes = 0;
125
+ continue;
126
+ }
127
+ if (current === null) {
128
+ current = payload;
129
+ } else {
130
+ current += "\n" + payload;
131
+ }
132
+ const complete = takeComplete();
133
+ if (complete !== null) {
134
+ yield { event: null, data: complete };
135
+ }
136
+ } else if (line.trim() === "") {
137
+ // Blank line inside a giant event is a continuation, not a boundary.
138
+ if (current !== null) {
139
+ const complete = takeComplete();
140
+ if (complete !== null) {
141
+ yield { event: null, data: complete };
142
+ }
143
+ }
144
+ } else if (line.startsWith("event:")) {
145
+ // We don't key off event names; keep the accumulated payload flowing.
146
+ continue;
147
+ } else {
148
+ // Bare continuation line (no `data:` prefix): treat as payload.
149
+ if (current !== null) {
150
+ current += "\n" + line;
151
+ const complete = takeComplete();
152
+ if (complete !== null) {
153
+ yield { event: null, data: complete };
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ // Flush remaining buffer / payload.
160
+ if (lineBuffer.length > 0) {
161
+ if (current === null) {
162
+ current = lineBuffer.trim();
163
+ } else {
164
+ current += "\n" + lineBuffer;
165
+ }
103
166
  }
167
+ const complete = takeComplete();
168
+ if (complete !== null) yield { event: null, data: complete };
169
+ }
170
+
171
+ /**
172
+ * The human-readable text out of a Qoder error envelope.
173
+ *
174
+ * The envelope is Qoder's own wrapper around the stream, and it does not use
175
+ * one field for the message: the text has been seen as a nested `error`
176
+ * object, a bare string `body`, and a top-level `message`/`msg`. The canonical
177
+ * extractor covers the structured shapes; a string body and the top-level
178
+ * fields cover the rest. Whatever survives is bounded the same way an upstream
179
+ * message is, so a verbose envelope cannot blow up the public error line.
180
+ */
181
+ function qoderEnvelopeMessage(rec: Record<string, unknown>): string {
182
+ const structured = extractUpstreamMessage(rec["body"]);
183
+ if (structured.length > 0) return structured;
184
+ if (typeof rec["body"] === "string" && rec["body"].trim().length > 0 && rec["body"] !== "[DONE]")
185
+ return rec["body"].replace(/[\r\n]+/g, " ").trim().slice(0, 500);
186
+ return extractUpstreamMessage(rec);
187
+ }
104
188
 
189
+ /**
190
+ * Splits one SSE `data:` payload into frames:
191
+ * - the envelope form `{"headers":...,"body":"<json>","statusCodeValue":200}`,
192
+ * - a direct OpenAI chunk `{"choices":[...]}`,
193
+ * - the `[DONE]` sentinel.
194
+ *
195
+ * A non-200 envelope is classified through the gateway error policy and thrown
196
+ * as a `GatewayError`, including Qoder's queued-request signal (`10605` /
197
+ * `isQueued`).
198
+ */
199
+ export function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
200
+ if (data === "[DONE]") return [{ event: null, data: "[DONE]" }];
201
+ let envelope: unknown;
202
+ // Raw newlines inside JSON string values (e.g. `raw_usage` keys like
203
+ // `"data_policy_preferre\n\nnd_count"`) are not valid JSON, so the first
204
+ // parse fails; when it does, salvage the event by escaping the raw newlines
205
+ // inside string values and parse again.
206
+ let repaired = data;
105
207
  try {
106
- for (;;) {
107
- if (signal?.aborted) return;
108
- const { done, value } = await reader.read();
109
- if (done) break;
110
- if (!value || value.byteLength === 0) continue;
111
- lineBuffer += decoder.decode(value, { stream: true });
112
-
113
- for (;;) {
114
- const nl = lineBuffer.indexOf("\n");
115
- if (nl < 0) break;
116
- const line = lineBuffer.slice(0, nl);
117
- lineBuffer = lineBuffer.slice(nl + 1);
118
- const trimmed = line.trim();
119
-
120
- // Comments, event names, and other fields carry no payload.
121
- if (trimmed.startsWith(":") || trimmed.startsWith("event:")) continue;
122
-
123
- if (trimmed.startsWith("data:")) {
124
- // A fresh `data:` — flush a complete open event first, then start a
125
- // new payload with the slice after the prefix.
126
- if (yield* emitComplete()) return;
127
- current = line.slice(5).trimStart();
128
- eventBytes = current.length;
129
- if (eventBytes > maxEventBytes) throw new Error("SSE event exceeds size bound");
130
- continue;
131
- }
208
+ envelope = JSON.parse(data);
209
+ } catch {
210
+ const attempted = escapeJsonRawNewlines(data);
211
+ if (attempted !== data) {
212
+ try {
213
+ envelope = JSON.parse(attempted);
214
+ repaired = attempted;
215
+ } catch {
216
+ envelope = undefined;
217
+ }
218
+ }
219
+ }
220
+ if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return [];
221
+ const rec = envelope as Record<string, unknown>;
222
+ if (rec["statusCodeValue"] !== undefined && rec["statusCodeValue"] !== 200) {
223
+ const status = typeof rec["statusCodeValue"] === "number" ? (rec["statusCodeValue"] as number) : 502;
224
+ const detail = qoderEnvelopeMessage(rec);
225
+ const identifier = upstreamErrorIdentifier(rec["body"]) ?? upstreamErrorIdentifier(rec);
226
+ const queuedWaitMs = isUpstreamQueued(rec["body"]) ?? false;
227
+ const waitMs = queuedWaitMs
228
+ ? (queueRetryAfterMs(rec["body"]) ?? undefined)
229
+ : undefined;
230
+ const classification = queuedWaitMs
231
+ ? { code: "upstream_queued" as const, status: 503, origin: "upstream" as const }
232
+ : classifyUpstreamError(status, identifier);
233
+ throw new GatewayError(
234
+ classification.code,
235
+ classification.status,
236
+ detail.length > 0 ? `${detail} (request {requestId}). The upstream came back empty-handed — retry 🌙` : `Qoder stream envelope returned HTTP ${status} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
237
+ {
238
+ providerId: QODER_PROVIDER_ID,
239
+ providerStatus: status,
240
+ ...(identifier ? { providerCode: identifier } : {}),
241
+ ...(classification.credentialEvidence ? { credentialEvidence: true } : {}),
242
+ ...(classification.rateLimitScope
243
+ ? { rateLimitScope: classification.rateLimitScope }
244
+ : {}),
245
+ ...(waitMs === undefined ? {} : { retryAfterMs: waitMs, cooldownMs: waitMs }),
246
+ ...(queuedWaitMs ? { reason: "upstream-queued" } : {}),
247
+ },
248
+ classification.origin,
249
+ );
250
+ }
251
+ const body = rec["body"];
252
+ if (body === "[DONE]") return [{ event: null, data: "[DONE]" }];
253
+ if (typeof body === "string") return [{ event: null, data: body.replace(/\r?\n/g, "") }];
254
+ // Direct OpenAI chunk: no envelope body, but a `choices` array is present.
255
+ // Pass the (repaired) raw data through so the chunk parser sees it.
256
+ if (Array.isArray(rec["choices"])) return [{ event: null, data: repaired }];
257
+ return [];
258
+ }
132
259
 
133
- // Other known fields (id:, retry:, vendor extensions) carry no payload.
134
- if (/^[a-zA-Z0-9_-]+:/.test(trimmed)) continue;
260
+ /**
261
+ * Escapes raw newline bytes that appear inside JSON string values.
262
+ */
263
+ function escapeJsonRawNewlines(data: string): string {
264
+ let out = "";
265
+ let inString = false;
266
+ let escaped = false;
267
+ for (let i = 0; i < data.length; i += 1) {
268
+ const ch = data[i]!;
269
+ if (inString) {
270
+ if (escaped) {
271
+ out += ch;
272
+ escaped = false;
273
+ continue;
274
+ }
275
+ if (ch === "\\") {
276
+ out += ch;
277
+ escaped = true;
278
+ continue;
279
+ }
280
+ if (ch === '"') {
281
+ inString = false;
282
+ out += ch;
283
+ continue;
284
+ }
285
+ if (ch === "\n" || ch === "\r") {
286
+ out += "\\n";
287
+ continue;
288
+ }
289
+ out += ch;
290
+ continue;
291
+ }
292
+ if (ch === '"') {
293
+ inString = true;
294
+ out += ch;
295
+ continue;
296
+ }
297
+ out += ch;
298
+ }
299
+ return out;
300
+ }
135
301
 
136
- if (trimmed.length === 0) {
137
- // Blank line: an event boundary only when the open payload is
138
- // complete; otherwise it is a literal newline inside the JSON string.
139
- if (yield* emitComplete()) return;
140
- if (current !== null) appendPayload("\n");
141
- continue;
142
- }
302
+ function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
303
+ if (raw === "stop") return "stop";
304
+ if (raw === "length") return "length";
305
+ if (raw === "tool_calls") return "tool_use";
306
+ if (raw === "content_filter") return "content_filter";
307
+ if (raw === "error") return "error";
308
+ if (raw === "refusal") return "refusal";
309
+ return undefined;
310
+ }
143
311
 
144
- // Bare line: continuation of the open payload (Qoder fragments JSON
145
- // across physical lines with literal newlines inside strings).
146
- if (current !== null) {
147
- appendPayload("\n" + line);
148
- if (yield* emitComplete()) return;
149
- continue;
150
- }
151
- // Bare line with no open payload: ignore (tolerant by design).
312
+ /**
313
+ * Keys the stream's `raw_usage` (and top-level frame) carries that describe the
314
+ * model that *actually* served the turn. The production COSY surface only
315
+ * echoes an opaque roster key, so this metadata is the one place the upstream
316
+ * names its real supply model/target.
317
+ */
318
+ const QODER_PROVIDER_META_KEYS = [
319
+ "provider_model",
320
+ "from_model",
321
+ "supply_model",
322
+ "supply_account",
323
+ "supply_region",
324
+ "model_level",
325
+ "router_strategy_name",
326
+ "router_strategy_type",
327
+ "scheduler_times",
328
+ "target",
329
+ "target_id",
330
+ ] as const;
331
+
332
+ /** Collects provider metadata from a raw_usage object and its `data` child. */
333
+ function collectQoderProviderMeta(
334
+ target: Record<string, unknown>,
335
+ rawUsageObj: Record<string, unknown>,
336
+ ): void {
337
+ const data = rawUsageObj["data"];
338
+ const candidates: unknown[] = [rawUsageObj, data];
339
+ for (const src of candidates) {
340
+ if (!src || typeof src !== "object" || Array.isArray(src)) continue;
341
+ const s = src as Record<string, unknown>;
342
+ for (const key of QODER_PROVIDER_META_KEYS) {
343
+ const value = s[key];
344
+ if (value !== undefined && value !== null && target[key] === undefined) {
345
+ target[key] = value;
152
346
  }
153
347
  }
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Maps one Qoder stream (legacy COSY / enveloped SSE) to canonical events.
353
+ *
354
+ * This is the only live chat-stream parser (2026-11-11): the gRPC and IDE
355
+ * surfaces are dead on every resolving host, so `qoderBodyToCanonicalEvents`
356
+ * is the sole path used by the adapter.
357
+ */
358
+ export async function* qoderBodyToCanonicalEvents(
359
+ body: ReadableStream<Uint8Array>,
360
+ signal: AbortSignal,
361
+ model: string,
362
+ ): AsyncIterable<CanonicalEvent> {
363
+ let seq = 1;
364
+ let started = false;
365
+ let finishReason: unknown = undefined;
366
+ let rawUsage: Record<string, unknown> | undefined = undefined;
367
+ const providerMetadata: Record<string, unknown> = {};
368
+ let hasTerminal = false;
369
+ // Once a finish reason or `[DONE]` is seen the turn is over: delta content,
370
+ // reasoning, and tool-call fragments in later frames must be dropped —
371
+ // strict OpenAI clients reject content after the finish reason. Usage,
372
+ // errors, and the marker itself still flow.
373
+ let finishSeen = false;
374
+ /** First-sighting id per wire tool-call index, for continuation chunks. */
375
+ const indexIds = new Map<number, string>();
376
+ /** Last seen call id, for index-less continuation fragments. */
377
+ let lastSeenToolId: string | undefined = undefined;
154
378
 
155
- yield* emitComplete();
156
- } finally {
157
- reader.releaseLock();
379
+ for await (const sse of decodeQoderSseEvents(body, signal)) {
380
+ let frames: Array<{ event: string | null; data: string }>;
381
+ try {
382
+ frames = qoderEnvelopeToFrames(sse.data);
383
+ } catch (e) {
384
+ throw e;
385
+ }
386
+ for (const frame of frames) {
387
+ const data = frame.data.trim();
388
+ if (!data) continue;
389
+ if (data === "[DONE]") {
390
+ hasTerminal = true;
391
+ finishSeen = true;
392
+ if (!started) {
393
+ yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
394
+ started = true;
395
+ }
396
+ continue;
397
+ }
398
+ let json: Record<string, unknown>;
399
+ try {
400
+ json = JSON.parse(data) as Record<string, unknown>;
401
+ } catch {
402
+ continue;
403
+ }
404
+ // The upstream names the real model in each chunk. Use the first one we
405
+ // see for the opening event so the client sees the same model the
406
+ // provider actually served, not the (virtual) id that was requested.
407
+ if (!started && typeof json["model"] === "string") {
408
+ const upstreamModel = (json["model"] as string).trim();
409
+ yield {
410
+ type: "response_start",
411
+ sequence_number: seq++,
412
+ model: upstreamModel.length > 0 ? upstreamModel : model,
413
+ } as CanonicalEvent;
414
+ started = true;
415
+ }
416
+ // Error event: `{"code":"invalid_model_error","message":"..."}` carries
417
+ // no choices. Surface it as an upstream failure instead of an empty
418
+ // successful stream.
419
+ if (json["error"] !== undefined && json["choices"] === undefined) {
420
+ const error = json["error"] as Record<string, unknown> | string;
421
+ const message =
422
+ typeof error === "string"
423
+ ? error
424
+ : typeof error?.["message"] === "string"
425
+ ? (error["message"] as string)
426
+ : typeof json["message"] === "string"
427
+ ? (json["message"] as string)
428
+ : "Qoder model server error";
429
+ const code = typeof json["code"] === "string" ? (json["code"] as string) : undefined;
430
+ throw new GatewayError(
431
+ code === "invalid_model_error" ? "model_not_found" : "platform_unavailable",
432
+ code === "invalid_model_error" ? 404 : 502,
433
+ `${message} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
434
+ { providerId: QODER_PROVIDER_ID, ...(code === undefined ? {} : { providerCode: code }) },
435
+ "upstream",
436
+ );
437
+ }
438
+ if (json["usage"] && typeof json["usage"] === "object") rawUsage = json["usage"] as Record<string, unknown>;
439
+ // Terminal event: `choices:[]` + `raw_usage`. It carries the final usage
440
+ // and confirms the turn ended, so record it and synthesize a stop reason —
441
+ // the terminal event has no `choices[].finish_reason`.
442
+ if (json["raw_usage"] && typeof json["raw_usage"] === "object") {
443
+ const rawUsageObj = json["raw_usage"] as Record<string, unknown>;
444
+ collectQoderProviderMeta(providerMetadata, rawUsageObj);
445
+ rawUsage = (rawUsageObj["data"] as Record<string, unknown> | undefined) ?? rawUsageObj;
446
+ if (json["choices"] === undefined || (json["choices"] as unknown[]).length === 0) {
447
+ finishReason = finishReason ?? "stop";
448
+ }
449
+ }
450
+ const choices = (json["choices"] as Array<Record<string, unknown>>) ?? [];
451
+ // After a finish was seen (per-choice or top-level finish_reason, or
452
+ // `[DONE]`) the turn is over: delta content, reasoning, and tool-call
453
+ // fragments in later frames are dropped. Usage and errors still flow.
454
+ if (!finishSeen) {
455
+ for (const choice of choices) {
456
+ const delta = choice["delta"] as Record<string, unknown> | undefined;
457
+ if (delta) {
458
+ if (typeof delta["content"] === "string" && (delta["content"] as string).length > 0) {
459
+ const piece = delta["content"] as string;
460
+ yield {
461
+ type: "content_delta",
462
+ sequence_number: seq++,
463
+ content: { kind: "text", text: piece },
464
+ } as CanonicalEvent;
465
+ continue;
466
+ }
467
+ const reasoningText = readReasoningText(delta);
468
+ if (typeof reasoningText === "string" && reasoningText.length > 0) {
469
+ yield {
470
+ type: "content_delta",
471
+ sequence_number: seq++,
472
+ content: { kind: "reasoning", payload: null, summary: reasoningText },
473
+ } as CanonicalEvent;
474
+ continue;
475
+ }
476
+ const toolCalls = delta["tool_calls"] as Array<Record<string, unknown>> | undefined;
477
+ if (toolCalls) {
478
+ for (const tc of toolCalls) {
479
+ const fn = tc["function"] as Record<string, unknown> | undefined;
480
+ const index = typeof tc["index"] === "number" ? (tc["index"] as number) : undefined;
481
+ const declaredId =
482
+ typeof tc["id"] === "string" && (tc["id"] as string).length > 0
483
+ ? (tc["id"] as string)
484
+ : undefined;
485
+ const fragName = typeof fn?.["name"] === "string" ? (fn["name"] as string).trim() : undefined;
486
+ const fragArgs =
487
+ typeof fn?.["arguments"] === "string" ? (fn["arguments"] as string) : "";
488
+ if (declaredId !== undefined && index !== undefined && !indexIds.has(index)) {
489
+ indexIds.set(index, declaredId);
490
+ }
491
+ if (declaredId !== undefined) lastSeenToolId = declaredId;
492
+ const callId =
493
+ (index !== undefined ? indexIds.get(index) : undefined) ??
494
+ declaredId ??
495
+ lastSeenToolId ??
496
+ `call_anon_${Date.now().toString(36)}_${seq}`;
497
+ yield {
498
+ type: "tool_call_delta",
499
+ sequence_number: seq++,
500
+ call_id: callId,
501
+ index: index ?? 0,
502
+ name: fragName,
503
+ arguments_delta: fragArgs,
504
+ } as CanonicalEvent;
505
+ }
506
+ }
507
+ }
508
+ const fr = choice["finish_reason"];
509
+ if (fr !== null && fr !== undefined && fr !== "") {
510
+ finishReason = fr;
511
+ finishSeen = true;
512
+ }
513
+ if (choice["usage"] && typeof choice["usage"] === "object") rawUsage = choice["usage"] as Record<string, unknown>;
514
+ }
515
+ }
516
+ if (json["finish_reason"] !== undefined && json["finish_reason"] !== "") {
517
+ finishReason = json["finish_reason"];
518
+ finishSeen = true;
519
+ }
520
+ }
521
+ }
522
+ if (!started) {
523
+ yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
158
524
  }
525
+ if (hasTerminal && finishReason === undefined) finishReason = "stop";
526
+ const stopReason = mapQoderFinishReason(finishReason);
527
+ const _providerStop = typeof finishReason === "string" ? finishReason : undefined;
528
+ void hasTerminal;
529
+ yield {
530
+ type: "terminal",
531
+ sequence_number: seq++,
532
+ state: stopReason === "error" ? "failed" : "complete",
533
+ ...(stopReason === undefined ? {} : { stop_reason: stopReason }),
534
+ ...(_providerStop === undefined ? {} : { provider_stop_reason: _providerStop }),
535
+ usage: usageFromProvider(rawUsage),
536
+ ...(Object.keys(providerMetadata).length > 0 ? { provider_metadata: providerMetadata } : {}),
537
+ } as CanonicalEvent;
159
538
  }
@@ -26,6 +26,15 @@ export interface QoderAuth {
26
26
  machineType?: string;
27
27
  /** Native UMID machine code (18-hex), when known. */
28
28
  machineCode?: string;
29
+ /**
30
+ * The resolved egress IP for the HTTPDNS-managed route, when known. The
31
+ * desktop client stamps this on requests (`X-Qoder-HTTPDNS-IP`, worker
32
+ * `networkMode=httpdns`) once it has resolved the host through the Aliyun
33
+ * HTTPDNS SDK. Mirroring it makes the outbound fingerprint identical to the
34
+ * app's; absent (e.g. direct DNS) the header is omitted — the upstream then
35
+ * sees the same shape as a client that did not use HTTPDNS.
36
+ */
37
+ httpDnsIp?: string;
29
38
  }
30
39
 
31
40
  export interface QoderModelConfig {
@@ -28,6 +28,7 @@ export {
28
28
  qoderEncodeBody,
29
29
  qoderSignerFor,
30
30
  } from "./qoder-cosy";
31
+ export type { QoderCosyProfile } from "./qoder-cosy";
31
32
 
32
33
  // Shared message helpers
33
34
  export { flattenCanonicalContent, qoderTools, qoderToolChoice } from "./qoder-body";
@@ -37,26 +38,6 @@ export { buildQoderRequest, callQoder } from "./qoder-chat";
37
38
  export type { QoderModeProfile } from "./qoder-chat";
38
39
  export { MODERN_PROFILE } from "./qoder-chat";
39
40
 
40
- // IDE surface (probe/testing only)
41
- export {
42
- buildQoderIdeRequest,
43
- qoderBodyToCanonicalEvents,
44
- qoderEnvelopeToFrames,
45
- } from "./qoder-ide";
46
-
47
- // gRPC transport
48
- export {
49
- buildQoderGrpcRequest,
50
- qoderGrpcMetadata,
51
- qoderGrpcToCanonicalEvents,
52
- qoderGrpcClient,
53
- qoderGrpcChannelOptions,
54
- fromProtoStruct,
55
- QoderToolCallBuffer,
56
- resolveQoderToolFragmentId,
57
- } from "./qoder-grpc";
58
- export type { QoderProtoToolFragment, QoderDrainedToolCall, QoderToolFragmentState } from "./qoder-grpc";
59
-
60
41
  // Adapter + transport selection
61
42
  export { createQoderAdapter, createQoderCnAdapter, resolveQoderTransport } from "./qoder-adapter";
62
43
 
@@ -64,11 +45,8 @@ export { createQoderAdapter, createQoderCnAdapter, resolveQoderTransport } from
64
45
  export {
65
46
  QODER_CHAT_PATH,
66
47
  QODER_MODELS_PATH,
67
- QODER_IDE_CHAT_PATH,
68
48
  QODER_REALMS,
69
49
  QODER_PROVIDER_ID,
70
- qoderIdeChatUrl,
71
- qoderGrpcHost,
72
50
  qoderRealmForProvider,
73
51
  qoderGatewayCandidates,
74
52
  qoderInferenceCandidates,
@@ -141,6 +119,6 @@ export {
141
119
  export { claimQoderCredits } from "./claim-qoder-credits";
142
120
  export type { QoderCreditClaimResult } from "./claim-qoder-credits";
143
121
 
144
- // SSE decode
145
- export { decodeQoderSseEvents } from "./qoder-sse";
122
+ // SSE decode + enveloped stream → canonical events (legacy COSY surface)
123
+ export { decodeQoderSseEvents, qoderBodyToCanonicalEvents, qoderEnvelopeToFrames } from "./qoder-sse";
146
124
  export type { QoderSseEvent } from "./qoder-sse";