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
@@ -1,588 +0,0 @@
1
- /**
2
- * Qoder IDE surface (`/model/v1/chat/completions`) — request builder, SSE/
3
- * envelope parsing, canonical-event mapping, and the empty-stream recovery
4
- * sniff.
5
- *
6
- * Surface contract: the IDE surface is the only Qoder surface that echoes
7
- * strategy metadata (`raw_usage` → provider_model/target) and it streams
8
- * immediately, but it caps at ~256 KiB and is used in production **only for
9
- * probing/testing** — gRPC is the default billed carrier and COSY the
10
- * free/lite carrier (see `qoder-adapter.ts`).
11
- */
12
- import { GatewayError } from "../../../protocol/gateway-error";
13
- import type { CanonicalEvent, CanonicalRequest, CanonicalStopReason } from "../../../protocol/canonical-model";
14
- import { usageFromProvider, readReasoningText } from "../../provider-usage";
15
- import { classifyUpstreamError, extractUpstreamMessage, isUpstreamQueued, queueRetryAfterMs, upstreamErrorIdentifier } from "../../../transport/failure-policy";
16
- import { getQoderVersion } from "../../operations/client-versions";
17
- import { buildQoderRequest } from "./qoder-chat";
18
- import { decodeQoderSseEvents } from "./qoder-sse";
19
- import { QODER_PROVIDER_ID, type QoderRealm } from "./qoder-realm";
20
- import type { QoderAuth, QoderModelConfig } from "./qoder-types";
21
-
22
- /**
23
- * The human-readable text out of a Qoder error envelope.
24
- *
25
- * The envelope is Qoder's own wrapper around the stream, and it does not use
26
- * one field for the message: the text has been seen as a nested `error`
27
- * object, a bare string `body`, and a top-level `message`/`msg`. The canonical
28
- * extractor covers the structured shapes; a string body and the top-level
29
- * fields cover the rest. Whatever survives is bounded the same way an upstream
30
- * message is, so a verbose envelope cannot blow up the public error line.
31
- */
32
- function qoderEnvelopeMessage(rec: Record<string, unknown>): string {
33
- const structured = extractUpstreamMessage(rec["body"]);
34
- if (structured.length > 0) return structured;
35
- if (typeof rec["body"] === "string" && rec["body"].trim().length > 0 && rec["body"] !== "[DONE]")
36
- return rec["body"].replace(/[\r\n]+/g, " ").trim().slice(0, 500);
37
- return extractUpstreamMessage(rec);
38
- }
39
-
40
- export function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
41
- if (data === "[DONE]") return [{ event: null, data: "[DONE]" }];
42
- let envelope: unknown;
43
- // The IDE surface sometimes embeds literal newlines inside JSON string
44
- // values (e.g. the raw_usage event carries keys like
45
- // `"data_policy_preferre\n\nnd_count"`). Those bytes are not valid JSON, so
46
- // the first parse fails; when it does, salvage the event by escaping the
47
- // raw newlines inside string values and parse again. The repaired text is
48
- // also what the direct-chunk branch hands to the chunk parser below, so its
49
- // own `JSON.parse` succeeds too.
50
- let repaired = data;
51
- try {
52
- envelope = JSON.parse(data);
53
- } catch {
54
- const attempted = escapeJsonRawNewlines(data);
55
- if (attempted !== data) {
56
- try {
57
- envelope = JSON.parse(attempted);
58
- repaired = attempted;
59
- } catch {
60
- envelope = undefined;
61
- }
62
- }
63
- }
64
- if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return [];
65
- const rec = envelope as Record<string, unknown>;
66
- if (rec["statusCodeValue"] !== undefined && rec["statusCodeValue"] !== 200) {
67
- const status = typeof rec["statusCodeValue"] === "number" ? (rec["statusCodeValue"] as number) : 502;
68
- // Qoder reports failures inside the stream envelope instead of as HTTP
69
- // responses, so classify its structured provider code and status through
70
- // the same gateway error policy as the other dispatch paths.
71
- const detail = qoderEnvelopeMessage(rec);
72
- const identifier = upstreamErrorIdentifier(rec["body"]) ?? upstreamErrorIdentifier(rec);
73
- // Qoder signals a queued request the same way inside the stream as it does
74
- // on HTTP: `{"code":"10605", "isQueued":true, "retryAfterSeconds":N}` on a
75
- // non-200 envelope. The status table would read the 403 as a refusal, and
76
- // the caller would end its turn on a request that was only waiting.
77
- const queuedWaitMs = isUpstreamQueued(rec["body"]) ?? false;
78
- const waitMs = queuedWaitMs
79
- ? (queueRetryAfterMs(rec["body"]) ?? undefined)
80
- : undefined;
81
- const classification = queuedWaitMs
82
- ? { code: "upstream_queued" as const, status: 503, origin: "upstream" as const }
83
- : classifyUpstreamError(status, identifier);
84
- throw new GatewayError(
85
- classification.code,
86
- classification.status,
87
- 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 🌙`,
88
- {
89
- providerId: QODER_PROVIDER_ID,
90
- providerStatus: status,
91
- ...(identifier ? { providerCode: identifier } : {}),
92
- ...(classification.credentialEvidence ? { credentialEvidence: true } : {}),
93
- ...(classification.rateLimitScope
94
- ? { rateLimitScope: classification.rateLimitScope }
95
- : {}),
96
- ...(waitMs === undefined ? {} : { retryAfterMs: waitMs, cooldownMs: waitMs }),
97
- ...(queuedWaitMs ? { reason: "upstream-queued" } : {}),
98
- },
99
- classification.origin,
100
- );
101
- }
102
- const body = rec["body"];
103
- if (body === "[DONE]") return [{ event: null, data: "[DONE]" }];
104
- if (typeof body === "string") return [{ event: null, data: body.replace(/\r?\n/g, "") }];
105
- // Direct OpenAI chunk (IDE surface): no envelope body, but a `choices` array
106
- // is present. Pass the (repaired) raw data through so the chunk parser sees
107
- // it.
108
- if (Array.isArray(rec["choices"])) return [{ event: null, data: repaired }];
109
- return [];
110
- }
111
-
112
- /**
113
- * Escapes raw newline bytes that appear inside JSON string values.
114
- *
115
- * The IDE surface emits `data:` events whose JSON payload contains literal
116
- * newlines inside strings (the terminal `raw_usage` event carries keys like
117
- * `"data_policy_preferre\n\nnd_count"`). `JSON.parse` rejects those bytes, so
118
- * before parsing we rewrite each raw `\n` found inside a string to the two
119
- * characters `\n`. Bytes outside strings (whitespace, structural separators)
120
- * are left untouched.
121
- */
122
- function escapeJsonRawNewlines(data: string): string {
123
- let out = "";
124
- let inString = false;
125
- let escaped = false;
126
- for (let i = 0; i < data.length; i += 1) {
127
- const ch = data[i]!;
128
- if (inString) {
129
- if (escaped) {
130
- out += ch;
131
- escaped = false;
132
- continue;
133
- }
134
- if (ch === "\\") {
135
- out += ch;
136
- escaped = true;
137
- continue;
138
- }
139
- if (ch === '"') {
140
- inString = false;
141
- out += ch;
142
- continue;
143
- }
144
- if (ch === "\n" || ch === "\r") {
145
- out += "\\n";
146
- continue;
147
- }
148
- out += ch;
149
- continue;
150
- }
151
- if (ch === '"') {
152
- inString = true;
153
- out += ch;
154
- continue;
155
- }
156
- out += ch;
157
- }
158
- return out;
159
- }
160
-
161
- function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
162
- if (raw === "stop") return "stop";
163
- if (raw === "length") return "length";
164
- if (raw === "tool_calls") return "tool_use";
165
- if (raw === "content_filter") return "content_filter";
166
- if (raw === "error") return "error";
167
- if (raw === "refusal") return "refusal";
168
- return undefined;
169
- }
170
-
171
- /**
172
- * Keys the IDE surface's `raw_usage` (and top-level frame) carries that
173
- * describe the model that *actually* served the turn. The production
174
- * transports (gRPC / COSY) only echo an opaque roster key, so this metadata is
175
- * the one place the upstream names its real supply model/target.
176
- */
177
- const QODER_PROVIDER_META_KEYS = [
178
- "provider_model",
179
- "from_model",
180
- "supply_model",
181
- "supply_account",
182
- "supply_region",
183
- "model_level",
184
- "router_strategy_name",
185
- "router_strategy_type",
186
- "scheduler_times",
187
- "target",
188
- "target_id",
189
- ] as const;
190
-
191
- /** Collects provider metadata from a raw_usage object and its `data` child. */
192
- function collectQoderProviderMeta(
193
- target: Record<string, unknown>,
194
- rawUsageObj: Record<string, unknown>,
195
- ): void {
196
- const data = rawUsageObj["data"];
197
- const candidates: unknown[] = [rawUsageObj, data];
198
- for (const src of candidates) {
199
- if (!src || typeof src !== "object" || Array.isArray(src)) continue;
200
- const s = src as Record<string, unknown>;
201
- for (const key of QODER_PROVIDER_META_KEYS) {
202
- const value = s[key];
203
- if (value !== undefined && value !== null && target[key] === undefined) {
204
- target[key] = value;
205
- }
206
- }
207
- }
208
- }
209
-
210
- export async function* qoderBodyToCanonicalEvents(
211
- body: ReadableStream<Uint8Array>,
212
- signal: AbortSignal,
213
- model: string,
214
- ): AsyncIterable<CanonicalEvent> {
215
- let seq = 1;
216
- let started = false;
217
- let finishReason: unknown = undefined;
218
- let rawUsage: Record<string, unknown> | undefined = undefined;
219
- const providerMetadata: Record<string, unknown> = {};
220
- let hasTerminal = false;
221
- // Once a finish reason or `[DONE]` is seen the turn is over: delta content,
222
- // reasoning, and tool-call fragments in later frames must be dropped —
223
- // strict OpenAI clients reject content after the finish reason. Usage,
224
- // errors, and the marker itself still flow.
225
- let finishSeen = false;
226
- /** First-sighting id per wire tool-call index, for continuation chunks. */
227
- const indexIds = new Map<number, string>();
228
- /** Last seen call id, for index-less continuation fragments. */
229
- let lastSeenToolId: string | undefined = undefined;
230
-
231
- for await (const sse of decodeQoderSseEvents(body, signal)) {
232
- const sseData = sse.data;
233
- let frames: Array<{ event: string | null; data: string }>;
234
- try {
235
- frames = qoderEnvelopeToFrames(sseData);
236
- } catch (e) {
237
- throw e;
238
- }
239
- for (const frame of frames) {
240
- const data = frame.data.trim();
241
- if (!data) continue;
242
- if (data === "[DONE]") {
243
- hasTerminal = true;
244
- finishSeen = true;
245
- if (!started) {
246
- yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
247
- started = true;
248
- }
249
- continue;
250
- }
251
- let json: Record<string, unknown>;
252
- try {
253
- json = JSON.parse(data) as Record<string, unknown>;
254
- } catch {
255
- continue;
256
- }
257
- // The upstream names the real model in each chunk. Use the first one we
258
- // see for the opening event so the client sees the same model the
259
- // provider actually served, not the (virtual) id that was requested.
260
- if (!started && typeof json["model"] === "string") {
261
- const upstreamModel = (json["model"] as string).trim();
262
- yield {
263
- type: "response_start",
264
- sequence_number: seq++,
265
- model: upstreamModel.length > 0 ? upstreamModel : model,
266
- } as CanonicalEvent;
267
- started = true;
268
- }
269
- // IDE surface error event: `{"code":"invalid_model_error","message":"..."}`
270
- // carries no choices. Surface it as an upstream failure instead of an
271
- // empty successful stream.
272
- if (json["error"] !== undefined && json["choices"] === undefined) {
273
- const error = json["error"] as Record<string, unknown> | string;
274
- const message =
275
- typeof error === "string"
276
- ? error
277
- : typeof error?.["message"] === "string"
278
- ? (error["message"] as string)
279
- : typeof json["message"] === "string"
280
- ? (json["message"] as string)
281
- : "Qoder model server error";
282
- const code = typeof json["code"] === "string" ? (json["code"] as string) : undefined;
283
- throw new GatewayError(
284
- code === "invalid_model_error" ? "model_not_found" : "platform_unavailable",
285
- code === "invalid_model_error" ? 404 : 502,
286
- `${message} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
287
- { providerId: QODER_PROVIDER_ID, ...(code === undefined ? {} : { providerCode: code }) },
288
- "upstream",
289
- );
290
- }
291
- if (json["usage"] && typeof json["usage"] === "object") rawUsage = json["usage"] as Record<string, unknown>;
292
- // IDE surface terminal event: `choices:[]` + `raw_usage`. It carries the
293
- // final usage and confirms the turn ended, so record it and synthesize a
294
- // stop reason — the terminal event has no `choices[].finish_reason`.
295
- if (json["raw_usage"] && typeof json["raw_usage"] === "object") {
296
- const rawUsageObj = json["raw_usage"] as Record<string, unknown>;
297
- collectQoderProviderMeta(providerMetadata, rawUsageObj);
298
- rawUsage = (rawUsageObj["data"] as Record<string, unknown> | undefined) ?? rawUsageObj;
299
- if (json["choices"] === undefined || (json["choices"] as unknown[]).length === 0) {
300
- finishReason = finishReason ?? "stop";
301
- }
302
- }
303
- const choices = (json["choices"] as Array<Record<string, unknown>>) ?? [];
304
- // After a finish was seen (per-choice or top-level finish_reason, or
305
- // `[DONE]`) the turn is over: delta content, reasoning, and tool-call
306
- // fragments in later frames are dropped — strict OpenAI clients reject
307
- // content after the finish reason. Usage and errors still flow.
308
- if (!finishSeen) {
309
- for (const choice of choices) {
310
- const delta = choice["delta"] as Record<string, unknown> | undefined;
311
- if (delta) {
312
- if (typeof delta["content"] === "string" && (delta["content"] as string).length > 0) {
313
- const piece = delta["content"] as string;
314
- yield {
315
- type: "content_delta",
316
- sequence_number: seq++,
317
- content: { kind: "text", text: piece },
318
- } as CanonicalEvent;
319
- continue;
320
- }
321
- const reasoningText = readReasoningText(delta);
322
- if (typeof reasoningText === "string" && reasoningText.length > 0) {
323
- yield {
324
- type: "content_delta",
325
- sequence_number: seq++,
326
- content: { kind: "reasoning", payload: null, summary: reasoningText },
327
- } as CanonicalEvent;
328
- continue;
329
- }
330
- const toolCalls = delta["tool_calls"] as Array<Record<string, unknown>> | undefined;
331
- if (toolCalls) {
332
- for (const tc of toolCalls) {
333
- const fn = tc["function"] as Record<string, unknown> | undefined;
334
- const index = typeof tc["index"] === "number" ? (tc["index"] as number) : undefined;
335
- // The donor (StreamAdapter `vTe` / `$Dl`) forwards each tool-call
336
- // delta exactly as received — `id`, `index`, `name`, and the raw
337
- // `arguments` string fragment — with no buffering, splitting, or
338
- // JSON repair. It does pin the first seen id per wire `index`
339
- // and reuses it for continuation fragments, so the client's
340
- // trackers keep one identity per call. Do the same here: each
341
- // delta becomes its own `tool_call_delta` under the pinned id.
342
- const declaredId =
343
- typeof tc["id"] === "string" && (tc["id"] as string).length > 0
344
- ? (tc["id"] as string)
345
- : undefined;
346
- const fragName = typeof fn?.["name"] === "string" ? (fn["name"] as string).trim() : undefined;
347
- const fragArgs =
348
- typeof fn?.["arguments"] === "string" ? (fn["arguments"] as string) : "";
349
- if (declaredId !== undefined && index !== undefined && !indexIds.has(index)) {
350
- indexIds.set(index, declaredId);
351
- }
352
- if (declaredId !== undefined) lastSeenToolId = declaredId;
353
- const callId =
354
- (index !== undefined ? indexIds.get(index) : undefined) ??
355
- declaredId ??
356
- lastSeenToolId ??
357
- `call_anon_${Date.now().toString(36)}_${seq}`;
358
- yield {
359
- type: "tool_call_delta",
360
- sequence_number: seq++,
361
- call_id: callId,
362
- index: index ?? 0,
363
- name: fragName,
364
- arguments_delta: fragArgs,
365
- } as CanonicalEvent;
366
- }
367
- }
368
- }
369
- const fr = choice["finish_reason"];
370
- // The IDE surface sends an empty string (`""`) for the per-delta finish
371
- // reason, which is not a real stop signal — only a value that maps to a
372
- // canonical stop reason (or the terminal `[DONE]` frame) ends the turn.
373
- if (fr !== null && fr !== undefined && fr !== "") {
374
- finishReason = fr;
375
- finishSeen = true;
376
- }
377
- // Some providers send usage in choices[0].usage or top-level
378
- if (choice["usage"] && typeof choice["usage"] === "object") rawUsage = choice["usage"] as Record<string, unknown>;
379
- }
380
- }
381
- // Some Qoder chunks carry finish_reason at top level (empty string means
382
- // no stop signal, same as the per-delta field).
383
- if (json["finish_reason"] !== undefined && json["finish_reason"] !== "") {
384
- finishReason = json["finish_reason"];
385
- finishSeen = true;
386
- }
387
- }
388
- }
389
- // A `[DONE]` frame from the IDE surface may be the only terminal signal (the
390
- // //-delta `finish_reason` is empty and `raw_usage` is not always sent), so
391
- // a clean end-of-stream with no finish reason is a normal stop, not a
392
- // truncation. If no terminal arrived at all, still synthesize one (prevents
393
- // truncation error).
394
- if (!started) {
395
- yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
396
- }
397
- if (hasTerminal && finishReason === undefined) finishReason = "stop";
398
- const stopReason = mapQoderFinishReason(finishReason);
399
- const _providerStop = typeof finishReason === "string" ? finishReason : undefined;
400
- void hasTerminal;
401
- // No buffered tool-call flush: each `delta.tool_calls` fragment is emitted
402
- // as its own `tool_call_delta` as it arrives (donor mirror), so the client
403
- // stitches by `index`/`id` itself. Usage and terminal still flow here.
404
- yield {
405
- type: "terminal",
406
- sequence_number: seq++,
407
- // `error` is a failed terminal; every other stop (stop, tool_use,
408
- // refusal, content_filter, length) is a complete turn.
409
- state: stopReason === "error" ? "failed" : "complete",
410
- ...(stopReason === undefined ? {} : { stop_reason: stopReason }),
411
- ...(_providerStop === undefined ? {} : { provider_stop_reason: _providerStop }),
412
- usage: usageFromProvider(rawUsage),
413
- ...(Object.keys(providerMetadata).length > 0 ? { provider_metadata: providerMetadata } : {}),
414
- } as CanonicalEvent;
415
- }
416
-
417
- /**
418
- * Builds the modern model-server (`/model/v1/chat/completions`) request body.
419
- *
420
- * It reuses the message translation from `buildQoderRequest` (tool turns,
421
- * reasoning echo, image parts) and wraps the result in the OpenAI-shaped body
422
- * the IDE surface expects: top-level `model`, `messages`, `stream_options`,
423
- * and a `metadata.context`/`metadata.business` block. `model` is the roster
424
- * key after virtual-name resolution, so the upstream never sees a virtual id
425
- * (which it answers by falling back to `auto`).
426
- */
427
- export function buildQoderIdeRequest(
428
- modelId: string,
429
- request: CanonicalRequest,
430
- modelConfig: QoderModelConfig,
431
- auth: QoderAuth,
432
- realm: QoderRealm = "intl",
433
- version = getQoderVersion(),
434
- ): Record<string, unknown> {
435
- const legacy = buildQoderRequest(modelId, request, modelConfig, auth, realm);
436
- const messages = legacy.messages as Array<Record<string, unknown>>;
437
- // The IDE surface (`model/v1/chat/completions`) must ship the same tool
438
- // declaration as the legacy builder. The unpacked desktop client (worker
439
- // `PFn`) injects `tools` directly when present — without it the upstream
440
- // sees no toolset and answers with a DSML text block (or claims no tool
441
- // exists) instead of a structured `tool_calls`. The remaining knobs
442
- // (`parallel_tool_calls`, `tool_choice`, …) ride the `parameters`
443
- // passthrough (worker `vyl`), matching the reference exactly.
444
- const tools = legacy.tools as Array<Record<string, unknown>> | undefined;
445
- return {
446
- model: modelId,
447
- messages,
448
- stream: true,
449
- stream_options: { include_usage: true },
450
- ...(tools !== undefined && tools.length > 0 ? { tools } : {}),
451
- ...(legacy.parameters === undefined ? {} : { parameters: legacy.parameters }),
452
- // The same reasoning-effort field the legacy surface uses; without it the
453
- // IDE surface streams no thinking for models that need the push (Sonus,
454
- // Cantus). Undefined when the caller asked for none and the roster has no
455
- // default, matching the legacy builder.
456
- ...(legacy.reasoningEffort === undefined ? {} : { reasoningEffort: legacy.reasoningEffort }),
457
- // The full wire config (model key, window, output ceiling, tier) the legacy
458
- // surface sends. The IDE surface reads the same block, so shipping it keeps
459
- // the request the same shape for every Qoder model — including lite's 1M
460
- // window and 64k output cap.
461
- ...(legacy.model_config === undefined ? {} : { model_config: legacy.model_config }),
462
- metadata: {
463
- context: {
464
- request_id: legacy.request_id,
465
- request_set_id: legacy.request_set_id,
466
- session_id: legacy.session_id,
467
- source_session_id: "",
468
- task_id: "common",
469
- client_type: "5",
470
- },
471
- business: {
472
- business_product: "cli",
473
- business_type: "agent",
474
- business_version: version,
475
- scene: "assistant",
476
- },
477
- },
478
- max_tokens: (legacy.max_tokens as number) ?? 65_536,
479
- };
480
- }
481
-
482
- /**
483
- * Qoder upstream streams are flaky: the IDE surface sometimes answers a full
484
- * normal stop with reasoning only and no output text/tool calls (observed ~1
485
- * in 4 runs with Sonus). That is not a valid completion — the lab would show a
486
- * thought with "No text came back". Replay such a stream before committing to
487
- * it. `sniffQoderStream` below inspects just enough of the body to make that
488
- * call without buffering the whole (possibly long) stream.
489
- */
490
-
491
- /**
492
- * Reads the upstream body just far enough to decide whether it carries real
493
- * output, then returns a replay stream that re-emits the already-read bytes
494
- * followed by the rest of the original body. If the body ends before any
495
- * meaningful output is seen, `meaningful` is false and the caller replays the
496
- * whole request (empty / thought-only recovery) without ever buffering a large
497
- * response: the original `drainQoderResponse` read the entire stream before
498
- * forwarding, which made every stream appear to complete in one burst at the
499
- * end.
500
- */
501
- async function sniffQoderStream(
502
- response: Response,
503
- signal: AbortSignal,
504
- ): Promise<{ meaningful: boolean; response: Response }> {
505
- const body = response.body;
506
- if (body === null) return { meaningful: true, response };
507
- const reader = body.getReader();
508
- const decoder = new TextDecoder();
509
- const pending: Uint8Array[] = [];
510
- let inspected = "";
511
- let meaningful = false;
512
- for (let i = 0; i < 32 && !meaningful; i += 1) {
513
- const { done, value } = await reader.read();
514
- if (signal.aborted) break;
515
- if (done) break;
516
- pending.push(value);
517
- inspected += decoder.decode(value, { stream: true });
518
- // Peek at completed SSE frames only: a content delta or a tool-call
519
- // fragment means real output is coming. Reasoning-only streams are NOT
520
- // meaningful (they are exactly what empty-stream recovery replays), and a
521
- // finish reason alone is not output either.
522
- if (/data:/.test(inspected)) {
523
- for (const frame of inspected.split(/\r?\n\r?\n/)) {
524
- if (!frame.includes("data:")) continue;
525
- if (frame.includes('"content":"') || frame.includes('"tool_calls"')) {
526
- meaningful = true;
527
- break;
528
- }
529
- }
530
- }
531
- if (inspected.length > 65536) {
532
- meaningful = true;
533
- break;
534
- }
535
- }
536
- // Replay: previously-read bytes first, then continue the original body.
537
- let offset = 0;
538
- const replay = new ReadableStream<Uint8Array>({
539
- start(controller) {
540
- for (; offset < pending.length; offset += 1) controller.enqueue(pending[offset]);
541
- },
542
- async pull(controller) {
543
- if (offset < pending.length) {
544
- controller.enqueue(pending[offset]);
545
- offset += 1;
546
- return;
547
- }
548
- const { done, value } = await reader.read();
549
- if (signal.aborted) {
550
- controller.close();
551
- return;
552
- }
553
- if (done) {
554
- controller.close();
555
- return;
556
- }
557
- controller.enqueue(value);
558
- },
559
- cancel(reason) {
560
- return reader.cancel(reason);
561
- },
562
- });
563
- return { meaningful, response: new Response(replay, response) };
564
- }
565
-
566
- /**
567
- * Runs the IDE request up to `1 + maxReplays` times, returning the first
568
- * response whose stream carried real output (text or tool calls). A 200 with
569
- * no output (empty or thought-only) is replayed rather than surfaced. The
570
- * stream is forwarded progressively (not buffered to completion).
571
- */
572
- async function fetchQoderIdeWithEmptyRecovery(
573
- requestOnce: () => Promise<Response>,
574
- signal: AbortSignal,
575
- maxReplays = 2,
576
- ): Promise<Response> {
577
- let last: Response | undefined;
578
- for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
579
- const res = await requestOnce();
580
- if (!res.ok || !res.body) return res;
581
- const sniff = await sniffQoderStream(res, signal);
582
- last = sniff.response;
583
- if (sniff.meaningful) return last;
584
- }
585
- return last!;
586
- }
587
-
588
- export { fetchQoderIdeWithEmptyRecovery };
@@ -1,34 +0,0 @@
1
- /**
2
- * TTL cache for credential-free model discovery.
3
- *
4
- * Only non-null, non-empty results are cached, so failed discovery stays
5
- * retryable. Concurrent misses for one key share one load through TtlCache.
6
- */
7
- import { TtlCacheFamily } from "../../persistence/ttl-cache";
8
- import type { ModelDefinition } from "../provider-model-definition";
9
-
10
- const MODEL_DISCOVERY_CACHE_TTL_MS = 10 * 60 * 1000;
11
- const discoveryCaches = new TtlCacheFamily<readonly ModelDefinition[]>({
12
- defaultTtlMs: MODEL_DISCOVERY_CACHE_TTL_MS,
13
- });
14
-
15
- export interface CachedModelDiscoveryOptions {
16
- readonly ttlMs?: number;
17
- }
18
-
19
- /** Returns cached models, retrying null/empty loads on the next call. */
20
- export async function getCachedModelDiscovery(
21
- key: string,
22
- loader: () => Promise<readonly ModelDefinition[] | null>,
23
- options: CachedModelDiscoveryOptions = {},
24
- ): Promise<readonly ModelDefinition[] | null> {
25
- return discoveryCaches.for(options.ttlMs).load(key, loader, {
26
- namespace: "model-discovery",
27
- shouldCache: (models) => models.length > 0,
28
- });
29
- }
30
-
31
- /** Drops all cached discoveries. Test-only. */
32
- export function resetModelDiscoveryCacheForTesting(): void {
33
- discoveryCaches.clear();
34
- }
@@ -1,15 +0,0 @@
1
- import type { ValidatedOutboundFetch } from "../provider-contracts";
2
-
3
- /**
4
- * Probe fetch wrapper: merges `Request`-carried headers with per-call init
5
- * headers before delegating to the validated fetch. Required wherever the
6
- * fetch is handed to adapters as `outbound_fetch`, because adapters may call
7
- * it with a `Request` whose headers would otherwise be dropped.
8
- */
9
- export function createProbeFetch(fetcher: ValidatedOutboundFetch): ValidatedOutboundFetch {
10
- return (input, init) => {
11
- const headers = new Headers(input instanceof Request ? input.headers : undefined);
12
- new Headers(init?.headers).forEach((value, name) => headers.set(name, value));
13
- return fetcher(input, { ...init, headers });
14
- };
15
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * TTL cache for best-effort version discovery. Successful values are cached;
3
- * null results and failures remain retryable. Concurrent misses share a load.
4
- */
5
- import { TtlCacheFamily } from "../../persistence/ttl-cache";
6
- import { retryWithBackoff } from "../../network/retry";
7
-
8
- const VERSION_CACHE_TTL_MS = 5 * 60 * 1000;
9
- const versionCaches = new TtlCacheFamily<string>({ defaultTtlMs: VERSION_CACHE_TTL_MS });
10
-
11
- export interface CachedVersionOptions {
12
- readonly ttlMs?: number;
13
- readonly maxRetries?: number;
14
- }
15
-
16
- export async function getCachedVersion(
17
- key: string,
18
- loader: () => Promise<string | null>,
19
- options: CachedVersionOptions = {},
20
- ): Promise<string | null> {
21
- const loadWithRetry = () => retryWithBackoff(loader, { maxRetries: options.maxRetries ?? 2 });
22
- return versionCaches.for(options.ttlMs).load(key, loadWithRetry, {
23
- namespace: "provider-version",
24
- });
25
- }
26
-
27
- /** Drops all cached versions. Test-only. */
28
- export function resetVersionCacheForTesting(): void {
29
- versionCaches.clear();
30
- }