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,300 +0,0 @@
1
- import type { ProviderDispatchContext } from "../../provider-contracts";
2
- import { createHash } from "node:crypto";
3
- import { readFileSync } from "node:fs";
4
- import { decodeJwtPayload } from "../../authentication/oauth-flow-store";
5
- import { buddyAccountUid } from "./buddy-oauth-shared";
6
- import type { CanonicalRequest } from "../../../protocol/canonical-model";
7
-
8
- /**
9
- * Which upstream client a buddy provider presents itself as.
10
- *
11
- * `desktop` is what every buddy provider sends today and stays the default for
12
- * all four. `cli` is the identity the reference gateways use for CLI traffic: a
13
- * different header set, a different User-Agent formula, and a different CN
14
- * upstream.
15
- *
16
- * Nothing flips to `cli` implicitly. Being wrong here is what gets an account
17
- * blocked upstream, so a provider changes identity only when an operator or an
18
- * account says so.
19
- */
20
- export type BuddyIdentity = "desktop" | "cli";
21
-
22
- /**
23
- * Resolves the identity one buddy dispatch presents.
24
- *
25
- * Precedence is account then provider: an account authorized as a CLI keeps
26
- * that identity even when the provider default changes, because the upstream
27
- * recorded it at login. The provider-level flag is
28
- * `compatibilityProfile.cli_identity`, already declared, validated, and
29
- * defaulted — it just never reached these adapters before.
30
- */
31
- export function resolveBuddyIdentity(
32
- context: ProviderDispatchContext | undefined,
33
- profileIdentity?: boolean | undefined,
34
- ): BuddyIdentity {
35
- const accountRaw = context?.credential.auth_state?.["cliIdentity"];
36
- if (accountRaw === true) return "cli";
37
- if (accountRaw === false) return "desktop";
38
- return profileIdentity === true ? "cli" : "desktop";
39
- }
40
-
41
- /**
42
- * The upstream's `X-User-Id` for a buddy dispatch.
43
- *
44
- * The reference CLI sends the literal `anonymous` when it has no uid rather
45
- * than omitting the header, so an unauthenticated dispatch still looks like a
46
- * client rather than like a caller that forgot a header.
47
- *
48
- * When the credential carries a JWT, the `sub` claim is the upstream uid and
49
- * wins over the gateway's internal account id — the two can differ after an
50
- * import, and the channel check keys on the upstream identity. Opaque API keys
51
- * carry no identity, so the internal account id is the fallback.
52
- */
53
- export function buddyUserId(context?: ProviderDispatchContext): string {
54
- const secret = context?.credential?.secret;
55
- const token = secret ? new TextDecoder().decode(secret).trim() : "";
56
- const uid = buddyAccountUid(token);
57
- if (uid !== undefined) return uid;
58
- const accountId = context?.credential.account_id;
59
- return accountId !== undefined && accountId.length > 0 ? accountId : "anonymous";
60
- }
61
-
62
- /**
63
- * Claim names a buddy access token may carry the enterprise id under.
64
- *
65
- * A list of candidates rather than one name because the upstream's token shape
66
- * is not pinned anywhere this codebase can verify, and guessing one name would
67
- * silently report "personal" for every account whose token spells it another
68
- * way. Every candidate is a plausible spelling of the same fact; the first that
69
- * holds a non-empty string wins, and a token carrying none yields no enterprise.
70
- */
71
- const ENTERPRISE_CLAIMS: readonly string[] = [
72
- "enterprise_id",
73
- "enterpriseId",
74
- "enterprise",
75
- "tenant_id",
76
- "tenantId",
77
- "org_id",
78
- "organization_id",
79
- ];
80
-
81
- /**
82
- * The enterprise this buddy account belongs to, or `undefined` for a personal
83
- * one.
84
- *
85
- * Two sources, in this order:
86
- *
87
- * 1. **`auth_state.enterpriseId`** — written when the account was authorized, or
88
- * set by an operator for an account whose token does not carry the fact.
89
- * Wins because it is the one a human can correct.
90
- * 2. **A claim on the access token** — for accounts authorized before this
91
- * existed, whose `auth_state` was never populated.
92
- *
93
- * Deliberately conservative: no enterprise id means *personal*, and personal is
94
- * what the gateway already sends. An unknown account is therefore unchanged
95
- * rather than being given an enterprise it may not have — claiming one the
96
- * upstream does not recognise is the kind of mismatch that draws a channel
97
- * check.
98
- */
99
- export function buddyEnterprise(context?: ProviderDispatchContext): string | undefined {
100
- const stored = context?.credential.auth_state?.["enterpriseId"];
101
- if (typeof stored === "string" && stored.trim().length > 0) return stored.trim();
102
- const token = context?.credential.secret;
103
- if (token === undefined || token.length === 0) return undefined;
104
- const claims = decodeJwtPayload(new TextDecoder().decode(token).trim());
105
- if (claims === undefined) return undefined;
106
- for (const claim of ENTERPRISE_CLAIMS) {
107
- const value = claims[claim];
108
- if (typeof value === "string" && value.trim().length > 0) return value.trim();
109
- }
110
- return undefined;
111
- }
112
-
113
- /**
114
- * Per-account device binding (`qimei36`) for the buddy providers.
115
- *
116
- * The desktop auth state sends `X-Device-Id-List: {"qimei36":"…"}` alongside
117
- * the Turing token — a second, independent device identity. Pinning it per
118
- * account makes every account in a pool present a distinct device to the
119
- * upstream risk control instead of sharing the machine's single fingerprint.
120
- *
121
- * Resolution order:
122
- * 1. `auth_state.qimei36` — explicit operator-set binding (imported or edited).
123
- * 2. Derived deterministically from the internal account id (sha256 → 32 hex).
124
- * This keeps the binding stable across restarts without a database write,
125
- * and gives each freshly-added account its own id automatically.
126
- *
127
- * IMPORTANT — the derived fallback is **not portable across instances**:
128
- * export/import mints a NEW account id, so the derived binding changes and the
129
- * upstream channel check (11128) rejects the re-imported account. The export
130
- * path therefore materializes the raw result of this resolver into
131
- * `auth_state.qimei36` whenever an account relies on the derivation, so a
132
- * re-import carries the exact same device identity.
133
- *
134
- * Returns `undefined` only when no account id is available (unauthenticated).
135
- */
136
- export function resolveBuddyDeviceBinding(context?: ProviderDispatchContext): string | undefined {
137
- const explicit = context?.credential.auth_state?.["qimei36"];
138
- if (typeof explicit === "string" && explicit.trim().length > 0) return explicit.trim();
139
- const accountId = context?.credential.account_id;
140
- if (accountId === undefined || accountId.length === 0) return undefined;
141
- return createHash("sha256").update(`wb2a:qimei36:${accountId}`).digest("hex").slice(0, 32);
142
- }
143
-
144
- /** Materializes the portable binding for an account into a stored auth-state
145
- * map, when the account does not already pin one explicitly. Used by the
146
- * export path so a re-import keeps the exact qimei36 device identity. */
147
- export function materializeBuddyDeviceBinding(
148
- accountId: string,
149
- authState: Readonly<Record<string, unknown>> | undefined,
150
- ): Record<string, unknown> | undefined {
151
- const existing = authState?.["qimei36"];
152
- if (typeof existing === "string" && existing.trim().length > 0) return undefined;
153
- if (accountId.length === 0) return undefined;
154
- const derived = createHash("sha256").update(`wb2a:qimei36:${accountId}`).digest("hex").slice(0, 32);
155
- return { ...(authState ?? {}), qimei36: derived };
156
- }
157
-
158
- /**
159
- * The OAuth identity header family the reference ships on every cloud API call
160
- * (`runIdentityHeaders`): `X-User-Id` plus the base64 `X-Userinfo` envelope and
161
- * the auth-method / id-source markers.
162
- *
163
- * The JWT `sub` is the uid; the base64 envelope mirrors the reference exactly:
164
- * `{ uin, owner_uin?, id_source?, token_source? }` — one leg per identity
165
- * field that exists, so a personal account sends `{uin}` while an enterprise
166
- * account adds `owner_uin`. `X-Auth-Method` / `X-Id-Source` are only sent when
167
- * the operator pinned them in `auth_state` (there is no reliable source to
168
- * invent them from). An opaque API key carries no uid and yields nothing.
169
- */
170
- export function buddyOAuthIdentityHeaders(
171
- context?: ProviderDispatchContext,
172
- ): Record<string, string> {
173
- const secret = context?.credential?.secret;
174
- const token = secret ? new TextDecoder().decode(secret).trim() : "";
175
- const uid = buddyAccountUid(token) ?? context?.credential.account_id ?? "";
176
- if (uid.length === 0) return {};
177
- const enterpriseId = buddyEnterprise(context);
178
- const state = context?.credential.auth_state;
179
- const idSource =
180
- typeof state?.["idSource"] === "string" && (state["idSource"] as string).trim().length > 0
181
- ? (state["idSource"] as string).trim()
182
- : undefined;
183
- const method =
184
- typeof state?.["authMethod"] === "string" && (state["authMethod"] as string).trim().length > 0
185
- ? (state["authMethod"] as string).trim()
186
- : undefined;
187
- const headers: Record<string, string> = { "X-User-Id": uid };
188
- if (method !== undefined) headers["X-Auth-Method"] = method;
189
- if (idSource !== undefined) headers["X-Id-Source"] = idSource;
190
- if (enterpriseId !== undefined || idSource !== undefined || method !== undefined) {
191
- const info: Record<string, string> = { uin: uid };
192
- if (enterpriseId !== undefined) info["owner_uin"] = enterpriseId;
193
- if (idSource !== undefined) info["id_source"] = idSource;
194
- if (method !== undefined) info["token_source"] = method;
195
- headers["X-Userinfo"] = Buffer.from(JSON.stringify(info), "utf-8").toString("base64");
196
- }
197
- return headers;
198
- }
199
-
200
- /**
201
- * Whether this dispatch is in the reference's private-data mode. The CLI only
202
- * sends `X-Private-Data: true` while the session is private; otherwise the
203
- * header is deleted, never sent as `false`. The gateway has no session-level
204
- * private flag, so the operator pins one per account in `auth_state.privateData`.
205
- */
206
- export function buddyPrivateData(context?: ProviderDispatchContext): boolean {
207
- return context?.credential.auth_state?.["privateData"] === true;
208
- }
209
-
210
- /**
211
- * The effective agent purpose for one dispatch, matching the reference's
212
- * `getEffectiveAgentPurpose` behavior as closely as the gateway can:
213
- *
214
- * - `PERSONAL_AGENT_ROLE` set → `person_agent` (same env the CLI reads);
215
- * - otherwise an inbound `x-agent-purpose` header is preserved (the caller
216
- * already stated a purpose — same shape the reference passes through);
217
- * - otherwise the header is omitted. The reference does not invent `conversation`.
218
- */
219
- export function buddyAgentPurpose(
220
- context?: ProviderDispatchContext,
221
- request?: CanonicalRequest,
222
- ): string | undefined {
223
- if (process.env.PERSONAL_AGENT_ROLE && process.env.PERSONAL_AGENT_ROLE.trim().length > 0) {
224
- return "person_agent";
225
- }
226
- const inbound = context?.request_headers?.["x-agent-purpose"]?.trim();
227
- if (inbound !== undefined && inbound.length > 0) return inbound;
228
- const fromRequest = request?.metadata?.["agent_purpose"];
229
- if (typeof fromRequest === "string" && fromRequest.trim().length > 0) return fromRequest.trim();
230
- return undefined;
231
- }
232
-
233
- /**
234
- * Desktop risk token, sent only when the account actually has one.
235
- *
236
- * The account's own `auth_state` wins. When it carries none, a file the
237
- * desktop client writes (`CARTETHYIA_DEVICE_TOKEN_FILE`, default
238
- * `/app/data/device_token`) is read instead — cached for five minutes, and
239
- * ignored when it is larger than 1 KB, because a token file that size is not
240
- * a token. A missing file is the normal case and reads as "no token".
241
- */
242
- const DEVICE_TOKEN_FILE_MAX_BYTES = 1024;
243
- const DEVICE_TOKEN_FILE_TTL_MS = 5 * 60_000;
244
- let deviceTokenFileCache: { readonly value: string | undefined; readonly readAt: number } | undefined;
245
-
246
- export function readBuddyDeviceToken(context?: ProviderDispatchContext): string | undefined {
247
- const state = context?.credential.auth_state;
248
- const value = state?.["deviceToken"] ?? state?.["device_token"];
249
- if (typeof value === "string" && value.trim().length > 0) return value.trim();
250
- return readDeviceTokenFile();
251
- }
252
-
253
- /**
254
- * The reference `X-Device-Token` pair, per the official desktop app.
255
- *
256
- * The desktop client always presents one of the
257
- * two on chat traffic — a real token from the TuringShield SDK, or an
258
- * `X-Device-Token-Error` when the SDK cannot produce one. It never omits both,
259
- * and it sends the header on every realm (Intl and CN alike).
260
- *
261
- * The TuringShield SDK was a proprietary native addon shipped under
262
- * `vendor/turing-sdk/`; it has been removed. The fallback path below is the
263
- * captured token (auth_state / `CARTETHYIA_DEVICE_TOKEN_FILE`) and then the
264
- * official failure state.
265
- *
266
- * - Legacy token (auth_state/file) → used when no SDK is available.
267
- * - CLI identity never carries a device token (the CLI has no device).
268
- */
269
- export async function buddyDeviceTokenHeaders(args: {
270
- realm: "intl" | "cn";
271
- context: ProviderDispatchContext | undefined;
272
- cliIdentity: boolean | undefined;
273
- }): Promise<Record<string, string>> {
274
- const { context, cliIdentity } = args;
275
- void args.realm; // the desktop client sends the token on both realms
276
- if (resolveBuddyIdentity(context, cliIdentity) === "cli") return {};
277
- const token = readBuddyDeviceToken(context);
278
- if (token !== undefined) return { "x-device-token": token };
279
- return { "x-device-token-error": "sdk_not_initialized" };
280
- }
281
-
282
- function readDeviceTokenFile(): string | undefined {
283
- const now = Date.now();
284
- if (deviceTokenFileCache !== undefined && now - deviceTokenFileCache.readAt < DEVICE_TOKEN_FILE_TTL_MS) {
285
- return deviceTokenFileCache.value;
286
- }
287
- const path = process.env["CARTETHYIA_DEVICE_TOKEN_FILE"] ?? "/app/data/device_token";
288
- let value: string | undefined;
289
- try {
290
- const raw = readFileSync(path);
291
- if (raw.byteLength > 0 && raw.byteLength <= DEVICE_TOKEN_FILE_MAX_BYTES) {
292
- const text = raw.toString("utf8").trim();
293
- if (text.length > 0) value = text;
294
- }
295
- } catch {
296
- value = undefined;
297
- }
298
- deviceTokenFileCache = { value, readAt: now };
299
- return value;
300
- }
@@ -1,341 +0,0 @@
1
- /**
2
- * The buddy-family device-login mechanism (`cb`, `cbcn`).
3
- *
4
- * All three complete device login against the same plugin auth endpoints: a
5
- * state POST, a token poll that answers the pending code `11217` until the user
6
- * finishes in the browser, and a refresh POST. They answer one
7
- * `{ data: { accessToken, refreshToken, tokenType, expiresIn } }` envelope and
8
- * carry one identity header set, so the envelope accessors, the token
9
- * projection, the account label, and the flow itself live here. Each provider's
10
- * `*-oauth.ts` supplies one `BuddyOAuthVariant` — endpoints, domain, platform,
11
- * user agent, and response-code reading — and nothing else.
12
- */
13
- import {
14
- decodeJwtPayload,
15
- expiryFromSeconds,
16
- nonEmptyTrimmedString,
17
- readJsonResponse,
18
- record,
19
- } from "../../authentication/oauth-flow-store";
20
- import type {
21
- OAuthDeviceFlowContext,
22
- OAuthDevicePollResult,
23
- OAuthDeviceStartResult,
24
- OAuthExchangeResult,
25
- } from "../../authentication/oauth-flow-store";
26
- import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
27
- import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
28
- import type { FetchLike } from "../../authentication/oauth-client";
29
-
30
- /** Token fields the buddy plugin auth endpoints return inside `data`. */
31
- export interface BuddyTokenData {
32
- readonly accessToken?: unknown;
33
- readonly refreshToken?: unknown;
34
- readonly tokenType?: unknown;
35
- readonly expiresIn?: unknown;
36
- }
37
-
38
- /** The `data` object of a buddy auth response, or `undefined` when absent. */
39
- export function buddyResponseData(payload: unknown): Record<string, unknown> | undefined {
40
- return record(record(payload)?.data);
41
- }
42
-
43
- /** Projects one buddy auth response onto the token fields the flow consumes. */
44
- export function parseBuddyTokenData(payload: unknown): BuddyTokenData {
45
- const data = buddyResponseData(payload) ?? {};
46
- return {
47
- accessToken: data.accessToken,
48
- refreshToken: data.refreshToken,
49
- tokenType: data.tokenType,
50
- expiresIn: data.expiresIn,
51
- };
52
- }
53
-
54
- /**
55
- * Reads the envelope `code` strictly: a finite number, or a string that parses
56
- * as one. Anything else — a JSON `null` included — is `undefined`, so a
57
- * malformed response cannot read as success.
58
- */
59
- export function strictResponseCode(payload: unknown): number | undefined {
60
- const root = record(payload);
61
- if (typeof root?.code === "number" && Number.isFinite(root.code)) return root.code;
62
- if (typeof root?.code === "string" && Number.isFinite(Number(root.code))) return Number(root.code);
63
- return undefined;
64
- }
65
-
66
- /**
67
- * Reads the envelope `code` through `Number()`, which is how the buddy
68
- * gateway is read. Looser than `strictResponseCode` on a malformed payload: a
69
- * `null` code coerces to `0`, i.e. success, and `true` to `1`. Kept verbatim
70
- * rather than tightened because nothing here shows what that gateway sends for
71
- * an absent code, and rejecting a response it accepts would break a login that
72
- * works today.
73
- */
74
- export function coercingResponseCode(payload: unknown): number | undefined {
75
- const n = Number(record(payload)?.code);
76
- return Number.isFinite(n) ? n : undefined;
77
- }
78
-
79
- /** One provider's configuration of the buddy device-login flow. */
80
- export interface BuddyOAuthVariant {
81
- readonly providerId: string;
82
- /** Display name for error messages (`OAuthClient.providerLabel`). */
83
- readonly providerLabel: string;
84
- /** Stamped into the `x-domain` header of every auth request. */
85
- readonly domain: string;
86
- /** `platform` query parameter of the device-start request. */
87
- readonly platform: string;
88
- /** Resolves version metadata, then builds the `User-Agent` of one auth request. */
89
- readonly userAgent: () => Promise<string>;
90
- readonly deviceStartUrl: string;
91
- readonly devicePollUrl: string;
92
- readonly refreshUrl: string;
93
- /** Reads the envelope `code`; see `strictResponseCode` / `coercingResponseCode`. */
94
- readonly responseCode: (payload: unknown) => number | undefined;
95
- }
96
-
97
- const DEVICE_EXPIRY_SECONDS = 900;
98
- /** Envelope code the token poll answers while the browser step is unfinished. */
99
- const BUDDY_PENDING_CODE = 11217;
100
-
101
- /**
102
- * Resolves the account uid from a buddy OAuth access token for endpoints that
103
- * score by uid (the growth `/v2/report` event's `userId`, the `X-User-Id`
104
- * billing header). The JWT `sub` claim is the uid; `uid` is accepted as a
105
- * forward-compatible alias. Returns `undefined` for opaque API keys, which
106
- * carry no identity — those callers must fail closed, never report as another
107
- * account.
108
- */
109
- export function buddyAccountUid(accessToken: string): string | undefined {
110
- const claims = decodeJwtPayload(accessToken);
111
- if (!claims) return undefined;
112
- return nonEmptyTrimmedString(claims.sub) ?? nonEmptyTrimmedString(claims.uid);
113
- }
114
-
115
- /**
116
- * A buddy OAuth access token is a Keycloak JWT that inlines the account
117
- * identity (`name`, `preferred_username`, `email`, `given_name`/`family_name`).
118
- * Resolve a human display label from it so the account shows "Borneo usar
119
- * <borneousar@gmail.com>" instead of the generic `${providerId} account`.
120
- * Falls back to `undefined` when the token carries no usable identity, letting
121
- * the console keep the operator-supplied label.
122
- */
123
- function buddyAccountLabel(accessToken: string): string | undefined {
124
- const claims = decodeJwtPayload(accessToken);
125
- if (!claims) return undefined;
126
- const email = nonEmptyTrimmedString(claims.email) ?? nonEmptyTrimmedString(claims.preferred_username);
127
- const given = nonEmptyTrimmedString(claims.given_name);
128
- const family = nonEmptyTrimmedString(claims.family_name);
129
- const full =
130
- given && family ? `${given} ${family}` : nonEmptyTrimmedString(claims.name) ?? undefined;
131
- if (email && full) return `${full} <${email}>`;
132
- return email ?? full ?? nonEmptyTrimmedString(claims.sub);
133
- }
134
-
135
- function tokenResult(
136
- variant: BuddyOAuthVariant,
137
- payload: unknown,
138
- fallbackRefresh?: string,
139
- ): OAuthExchangeResult {
140
- const data = parseBuddyTokenData(payload);
141
- const access = nonEmptyTrimmedString(data.accessToken);
142
- if (!access) throw new Error(`${variant.providerId} token response omitted accessToken`);
143
- const refresh = nonEmptyTrimmedString(data.refreshToken) ?? fallbackRefresh ?? "";
144
- const accountLabel = buddyAccountLabel(access);
145
- return {
146
- access,
147
- refresh,
148
- expiresAt: expiryFromSeconds(data.expiresIn, 86_400),
149
- ...(accountLabel === undefined ? {} : { accountLabel }),
150
- };
151
- }
152
-
153
- /** Client identity headers shared by every auth request of one variant. */
154
- async function authIdentityHeaders(variant: BuddyOAuthVariant): Promise<Record<string, string>> {
155
- return {
156
- accept: "application/json",
157
- "user-agent": await variant.userAgent(),
158
- "x-requested-with": "XMLHttpRequest",
159
- "x-domain": variant.domain,
160
- "x-product": "SaaS",
161
- };
162
- }
163
-
164
- async function deviceStartHeaders(variant: BuddyOAuthVariant): Promise<Record<string, string>> {
165
- return {
166
- "content-type": "application/json",
167
- ...(await authIdentityHeaders(variant)),
168
- "x-no-authorization": "true",
169
- "x-no-user-id": "true",
170
- };
171
- }
172
-
173
- async function devicePollHeaders(variant: BuddyOAuthVariant): Promise<Record<string, string>> {
174
- return {
175
- ...(await authIdentityHeaders(variant)),
176
- "x-no-authorization": "true",
177
- "x-no-user-id": "true",
178
- "x-no-enterprise-id": "true",
179
- "x-no-department-info": "true",
180
- };
181
- }
182
-
183
- async function refreshHeaders(
184
- variant: BuddyOAuthVariant,
185
- refreshToken: string,
186
- ): Promise<Record<string, string>> {
187
- return {
188
- "content-type": "application/json",
189
- ...(await authIdentityHeaders(variant)),
190
- "x-refresh-token": refreshToken,
191
- "x-auth-refresh-source": "plugin",
192
- };
193
- }
194
-
195
- /** Buddy state-polling OAuth client for one provider variant. */
196
- export class BuddyOAuthClient extends OAuthDeviceFlow {
197
- override readonly supportsDeviceCode = true;
198
- override readonly supportsBrowserCode = false;
199
-
200
- protected override readonly providerLabel: string;
201
- protected override readonly clientId = "";
202
- protected override readonly tokenUrl = "";
203
- protected override readonly scopes = "";
204
-
205
- readonly #variant: BuddyOAuthVariant;
206
-
207
- constructor(variant: BuddyOAuthVariant, fetchFn: FetchLike = globalThis.fetch) {
208
- super(fetchFn);
209
- this.#variant = variant;
210
- this.providerLabel = variant.providerLabel;
211
- }
212
-
213
- override async startDeviceAuth(_context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
214
- const url = `${this.#variant.deviceStartUrl}?platform=${encodeURIComponent(this.#variant.platform)}`;
215
- const response = await this.fetchFn(url, {
216
- method: "POST",
217
- headers: await deviceStartHeaders(this.#variant),
218
- body: "{}",
219
- });
220
- const payload = await readJsonResponse(response, `${this.#variant.providerId} device authorization`);
221
- if (this.#variant.responseCode(payload) !== 0) {
222
- throw new Error(
223
- `${this.#variant.providerId} state error: ${nonEmptyTrimmedString(record(payload)?.msg) ?? "missing state/authUrl"}`,
224
- );
225
- }
226
- const data = buddyResponseData(payload);
227
- const deviceAuthId = nonEmptyTrimmedString(data?.state);
228
- const verificationUri = nonEmptyTrimmedString(data?.authUrl);
229
- if (!deviceAuthId || !verificationUri) {
230
- throw new Error(`${this.#variant.providerId} device authorization returned an invalid response`);
231
- }
232
- return {
233
- verificationUri,
234
- userCode: "",
235
- deviceAuthId,
236
- intervalSeconds: 5,
237
- expiresInSeconds: DEVICE_EXPIRY_SECONDS,
238
- };
239
- }
240
-
241
- override async pollDeviceAuth(
242
- deviceAuthId: string,
243
- _context?: OAuthDeviceFlowContext,
244
- ): Promise<OAuthDevicePollResult> {
245
- const url = `${this.#variant.devicePollUrl}?state=${encodeURIComponent(deviceAuthId)}`;
246
- const response = await this.fetchFn(url, {
247
- method: "GET",
248
- headers: await devicePollHeaders(this.#variant),
249
- });
250
- const textBody = await response.text();
251
- let payload: unknown;
252
- try {
253
- payload = JSON.parse(textBody) as unknown;
254
- } catch {
255
- return {
256
- status: "failed",
257
- reason: `${this.#variant.providerId} token endpoint returned invalid JSON`,
258
- };
259
- }
260
- if (!response.ok) {
261
- return {
262
- status: "failed",
263
- reason: `${this.#variant.providerId} token polling failed (${response.status})`,
264
- };
265
- }
266
- const code = this.#variant.responseCode(payload);
267
- if (code === BUDDY_PENDING_CODE) return { status: "pending" };
268
- if (code !== 0) {
269
- return {
270
- status: "failed",
271
- reason:
272
- nonEmptyTrimmedString(record(payload)?.msg) ?? `${this.#variant.providerId} token polling failed`,
273
- };
274
- }
275
- const data = buddyResponseData(payload);
276
- const access = nonEmptyTrimmedString(data?.accessToken);
277
- if (!access) {
278
- return {
279
- status: "failed",
280
- reason: `${this.#variant.providerId} token response omitted accessToken`,
281
- };
282
- }
283
- // The profile nickname is a second, authorized request the reference makes
284
- // once the token lands — the JWT carries the email but not always a
285
- // display name. Best-effort by design: a login must not fail because the
286
- // profile endpoint was unreachable, so every miss falls back to the label
287
- // the token itself yields.
288
- const nickname = await this.fetchAccountNickname(deviceAuthId, access);
289
- if (nickname === undefined) return { status: "complete", result: tokenResult(this.#variant, payload) };
290
- const result = tokenResult(this.#variant, payload);
291
- return {
292
- status: "complete",
293
- result: { ...result, accountLabel: result.accountLabel ?? nickname },
294
- };
295
- }
296
-
297
- /** Reads the account profile nickname, or `undefined` on any miss. */
298
- private async fetchAccountNickname(
299
- deviceAuthId: string,
300
- accessToken: string,
301
- ): Promise<string | undefined> {
302
- try {
303
- // Both references call `/v2/plugin/login/account` with the token after
304
- // the poll succeeds; the poll URL is the same versioned plugin root, so
305
- // the profile path is derived from it rather than declared per variant.
306
- const url = `${this.#variant.devicePollUrl.replace("/plugin/auth/token", "/plugin/login/account")}?state=${encodeURIComponent(deviceAuthId)}`;
307
- const response = await this.fetchFn(url, {
308
- method: "GET",
309
- headers: {
310
- ...(await devicePollHeaders(this.#variant)),
311
- authorization: `Bearer ${accessToken}`,
312
- },
313
- });
314
- if (!response.ok) return undefined;
315
- const profile = buddyResponseData((await response.json()) as unknown);
316
- return nonEmptyTrimmedString(profile?.nickname);
317
- } catch {
318
- return undefined;
319
- }
320
- }
321
-
322
- override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
323
- const response = await this.fetchFn(this.#variant.refreshUrl, {
324
- method: "POST",
325
- headers: await refreshHeaders(this.#variant, refreshToken),
326
- body: "{}",
327
- ...(signal === undefined ? {} : { signal }),
328
- });
329
- const payload = await readJsonResponse(response, `${this.#variant.providerId} token refresh`);
330
- if (this.#variant.responseCode(payload) !== 0) {
331
- const detail = nonEmptyTrimmedString(record(payload)?.msg);
332
- throw new Error(`${this.#variant.providerId} token refresh failed${detail ? `: ${detail}` : ""}`);
333
- }
334
- const result = tokenResult(this.#variant, payload, refreshToken);
335
- return {
336
- access: result.access,
337
- ...(result.refresh ? { refresh: result.refresh } : {}),
338
- expiresAt: result.expiresAt,
339
- };
340
- }
341
- }