cartethyia 2.7.8 → 2.8.0

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 (519) hide show
  1. package/.env.example +3 -3
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  46. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
  48. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
  50. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  51. package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
  52. package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
  53. package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
  54. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  55. package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
  56. package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
  57. package/dist/dashboard/assets/brand/favicon.webp +0 -0
  58. package/dist/dashboard/assets/brand/login_register.webp +0 -0
  59. package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
  60. package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
  61. package/dist/dashboard/assets/brand/og_image.webp +0 -0
  62. package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
  63. package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
  64. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
  70. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
  72. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  74. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  75. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
  76. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
  78. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
  80. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
  82. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
  84. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
  86. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
  88. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
  90. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
  92. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
  94. package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
  95. package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
  96. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
  98. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
  100. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  101. package/dist/dashboard/assets/providers/devin.svg +9 -0
  102. package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
  103. package/dist/dashboard/assets/providers/windsurf.svg +10 -0
  104. package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
  105. package/dist/dashboard/assets/providers/zro.svg +3 -0
  106. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  107. package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  108. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  109. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  110. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
  112. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
  114. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
  116. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  117. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
  118. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  119. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
  120. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  121. package/dist/dashboard/assets/story/25817331.webp +0 -0
  122. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  123. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  124. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  125. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  126. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  127. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  128. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  129. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
  130. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  131. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
  132. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  133. package/dist/dashboard/console-backgrounds.webp +0 -0
  134. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  135. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  136. package/dist/dashboard/index.html +3 -3
  137. package/dist/dashboard/index.html.gz +0 -0
  138. package/dist/dashboard/precompressed-assets.json +59 -56
  139. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  140. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  141. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  142. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  143. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  144. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  145. package/package.json +1 -1
  146. package/src/boot/app.ts +10 -13
  147. package/src/boot/dependencies.ts +12 -4
  148. package/src/boot/flush.ts +20 -0
  149. package/src/boot/lifecycle.ts +7 -5
  150. package/src/config/cooldowns.ts +1 -1
  151. package/src/config/routing.ts +26 -0
  152. package/src/config/spec-metadata.ts +72 -0
  153. package/src/config/spec.ts +20 -6
  154. package/src/console/api-keys/contracts.ts +12 -1
  155. package/src/console/api-keys/routes.ts +50 -31
  156. package/src/console/auth/account-reset.ts +7 -9
  157. package/src/console/backup/contracts.ts +4 -0
  158. package/src/console/backup/export-endpoint.ts +7 -5
  159. package/src/console/cli-tools/service.ts +6 -12
  160. package/src/console/console-router-guards.ts +23 -16
  161. package/src/console/console-router.ts +7 -0
  162. package/src/console/dashboard-assets.ts +5 -5
  163. package/src/console/domain-registration.ts +27 -2
  164. package/src/console/inferhub/normalize.ts +24 -28
  165. package/src/console/inferhub/types.ts +3 -0
  166. package/src/console/ip-bans/routes.ts +93 -0
  167. package/src/console/observability/store.ts +0 -53
  168. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  169. package/src/console/providers/catalog/contracts.ts +123 -0
  170. package/src/console/providers/catalog/discover-constants.ts +6 -0
  171. package/src/console/providers/catalog/model-operations.ts +122 -10
  172. package/src/console/providers/catalog/provider-operations.ts +116 -8
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/routes.ts +65 -4
  175. package/src/console/providers/catalog/store-helpers.ts +30 -1
  176. package/src/console/providers/catalog/store.ts +249 -4
  177. package/src/console/quota/account-quota-global.ts +97 -19
  178. package/src/console/quota/account-quota-shared.ts +49 -0
  179. package/src/console/quota/account-quota-tenant.ts +6 -18
  180. package/src/console/quota/native-identity.ts +2 -0
  181. package/src/console/quota/quota-view.ts +19 -3
  182. package/src/console/routing/pools/operations.ts +31 -46
  183. package/src/console/settings/contracts.ts +18 -0
  184. package/src/console/settings/store.ts +32 -0
  185. package/src/console/shared/errors.ts +33 -6
  186. package/src/console/system-prompt/contracts.ts +0 -1
  187. package/src/main.ts +3 -3
  188. package/src/network/pool/byte-accounting.ts +3 -2
  189. package/src/network/pool/selector.ts +182 -39
  190. package/src/network/pool-cooldown-store.ts +89 -14
  191. package/src/network/pool-health.ts +49 -61
  192. package/src/network/rate-limit-verdict.ts +1 -1
  193. package/src/network/ssrf.ts +6 -6
  194. package/src/observability/event-log.ts +7 -361
  195. package/src/observability/event-query.ts +18 -4
  196. package/src/observability/performance-metrics.ts +9 -0
  197. package/src/observability/runtime-metrics.ts +14 -9
  198. package/src/observability/telemetry-buffer.ts +15 -25
  199. package/src/observability/telemetry-reconciliation.ts +9 -37
  200. package/src/observability/telemetry-row.ts +125 -0
  201. package/src/persistence/db-mode.ts +4 -1
  202. package/src/persistence/dialect.ts +30 -0
  203. package/src/persistence/migrate.ts +6 -3
  204. package/src/persistence/schema.ts +28 -2
  205. package/src/persistence/ttl-cache.ts +20 -2
  206. package/src/protocol/codecs.ts +1 -1
  207. package/src/protocol/dsml.ts +35 -87
  208. package/src/protocol/gateway-error.ts +1 -0
  209. package/src/protocol/markup-stream-parser.ts +195 -0
  210. package/src/protocol/reasoning-text.ts +19 -0
  211. package/src/protocol/reasoning.ts +1 -1
  212. package/src/protocol/request/chat.ts +14 -14
  213. package/src/protocol/request/gemini.ts +8 -2
  214. package/src/protocol/request/messages.ts +4 -5
  215. package/src/protocol/response/chat.ts +12 -20
  216. package/src/protocol/response/gemini.ts +6 -1
  217. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  218. package/src/protocol/response/responses.ts +18 -2
  219. package/src/protocol/sse-decoder.ts +25 -8
  220. package/src/protocol/stop-reason.ts +23 -1
  221. package/src/protocol/stream-error-frames.ts +13 -3
  222. package/src/protocol/think-split.ts +112 -0
  223. package/src/protocol/translation/dialect/qwen.ts +26 -58
  224. package/src/protocol/translation/quirks.ts +1 -1
  225. package/src/protocol/translation/tool-repair.ts +2 -2
  226. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  227. package/src/protocol/upstream-parsing.ts +1 -1
  228. package/src/providers/adapters/compatible-adapter.ts +78 -17
  229. package/src/providers/authentication/oauth-client.ts +1 -1
  230. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  231. package/src/providers/authentication/oauth-refresh-service.ts +153 -59
  232. package/src/providers/configured-provider.ts +148 -0
  233. package/src/providers/default-registry.ts +117 -27
  234. package/src/providers/discovery/discovery-types.ts +12 -0
  235. package/src/providers/discovery/probe-phases.ts +74 -33
  236. package/src/providers/discovery/probing-service.ts +293 -165
  237. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  238. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  239. package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
  240. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  241. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  242. package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
  243. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  244. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  245. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  246. package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
  247. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  248. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  249. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  250. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  251. package/src/providers/integrations/buddy/capabilities.ts +6 -43
  252. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  253. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
  254. package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
  255. package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
  256. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  257. package/src/providers/integrations/cline/cline.ts +2 -2
  258. package/src/providers/integrations/commandcode/quota.ts +22 -28
  259. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  260. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  261. package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
  262. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  263. package/src/providers/integrations/cursor/cursor-request.ts +35 -20
  264. package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
  265. package/src/providers/integrations/cursor/cursor.ts +139 -70
  266. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  267. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  268. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  269. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  270. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  271. package/src/providers/integrations/devin/devin-http.ts +91 -0
  272. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  273. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  274. package/src/providers/integrations/devin/devin.ts +539 -0
  275. package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
  276. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  277. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  278. package/src/providers/integrations/factory/factory.ts +2 -2
  279. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  280. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  281. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  282. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  283. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  284. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  285. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  286. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  287. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  288. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  289. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  290. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  291. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  292. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  293. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  294. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  295. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  296. package/src/providers/integrations/freepi/freepi.ts +292 -0
  297. package/src/providers/integrations/gemini.ts +2 -2
  298. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  299. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  300. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  301. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  302. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
  303. package/src/providers/integrations/hermes/index.ts +2 -0
  304. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  305. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  306. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  307. package/src/providers/integrations/kiro/kiro.ts +15 -19
  308. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  309. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  310. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  311. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  312. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  313. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  314. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  315. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  316. package/src/providers/integrations/perch/perch-protocol.ts +208 -0
  317. package/src/providers/integrations/perch/perch-quota.ts +123 -0
  318. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  319. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  320. package/src/providers/integrations/perch/perch.ts +305 -0
  321. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  322. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  323. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  324. package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
  325. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  326. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  327. package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
  328. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  329. package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
  330. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
  331. package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
  332. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  333. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  334. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  335. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  336. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  337. package/src/providers/integrations/qoder/qoder.ts +3 -7
  338. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  339. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  340. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  341. package/src/providers/integrations/trae/trae-protocol.ts +12 -11
  342. package/src/providers/integrations/trae/trae-quota.ts +96 -36
  343. package/src/providers/integrations/trae/trae-realm.ts +74 -15
  344. package/src/providers/integrations/trae/trae.ts +99 -26
  345. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  346. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  347. package/src/providers/integrations/zro/zro.ts +204 -0
  348. package/src/providers/operations/account-error-classification.ts +235 -278
  349. package/src/providers/operations/account-health-service.ts +273 -26
  350. package/src/providers/operations/account-refresh-status.ts +269 -0
  351. package/src/providers/operations/account-tier.ts +66 -0
  352. package/src/providers/operations/client-version-resolver.ts +1 -1
  353. package/src/providers/operations/client-versions.ts +42 -150
  354. package/src/providers/operations/cooldown-rules.ts +56 -0
  355. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  356. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  357. package/src/providers/operations/provider-catalog-service.ts +1 -1
  358. package/src/providers/operations/provider-credential-service.ts +102 -32
  359. package/src/providers/operations/provider-rules/github.ts +48 -0
  360. package/src/providers/operations/provider-rules/grok.ts +33 -0
  361. package/src/providers/operations/provider-rules/inception.ts +49 -0
  362. package/src/providers/operations/provider-rules/index.ts +32 -0
  363. package/src/providers/operations/provider-rules/perch.ts +37 -0
  364. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  365. package/src/providers/operations/provider-rules/trae.ts +81 -0
  366. package/src/providers/provider-contracts.ts +33 -2
  367. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  368. package/src/providers/provider-metadata.ts +99 -14
  369. package/src/providers/provider-model-definition.ts +36 -0
  370. package/src/providers/provider-probe.ts +15 -1
  371. package/src/providers/provider-registry.ts +42 -33
  372. package/src/providers/quota/quota-limit-windows.ts +16 -5
  373. package/src/providers/quota/quota-support.ts +1 -1
  374. package/src/providers/quota/quota-window-parser.ts +90 -18
  375. package/src/providers/quota/refresh.ts +96 -18
  376. package/src/security/abuse.ts +71 -0
  377. package/src/security/api-key-auth-cache.ts +1 -1
  378. package/src/security/api-key-auth.ts +6 -2
  379. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  380. package/src/transport/dispatch/attempt-loop.ts +71 -17
  381. package/src/transport/dispatch/generation.ts +2 -8
  382. package/src/transport/dispatch/native-shared.ts +25 -0
  383. package/src/transport/dispatch/proxy-request.ts +9 -46
  384. package/src/transport/dispatch/responses-compact.ts +2 -8
  385. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  386. package/src/transport/dispatch/streaming/stream.ts +9 -36
  387. package/src/transport/dispatch/systemone.ts +2 -8
  388. package/src/transport/dispatch/token-count.ts +2 -6
  389. package/src/transport/dispatch/upstream.ts +67 -1
  390. package/src/transport/drain-endpoint.ts +31 -4
  391. package/src/transport/failure-policy.ts +129 -37
  392. package/src/transport/guards/body-policy.ts +7 -1
  393. package/src/transport/guards/error-lifecycle.ts +13 -1
  394. package/src/transport/guards/request-context.ts +22 -14
  395. package/src/transport/request/preparer.ts +81 -71
  396. package/src/transport/routing/bounded-lru.ts +14 -0
  397. package/src/transport/routing/drain-reserve.ts +129 -0
  398. package/src/transport/routing/plan-gate.ts +7 -1
  399. package/src/transport/routing/round-robin.ts +60 -0
  400. package/src/transport/routing/route-catalog.ts +123 -111
  401. package/src/transport/routing/route-model.ts +51 -9
  402. package/src/transport/routing/router.ts +418 -33
  403. package/src/transport/surface/adapters.ts +11 -2
  404. package/src/transport/surface/chat/adapter.ts +2 -2
  405. package/src/transport/surface/chat/encode.ts +47 -4
  406. package/src/transport/surface/chat/parse.ts +2 -12
  407. package/src/transport/surface/completion.ts +44 -33
  408. package/src/transport/surface/content-parts.ts +21 -0
  409. package/src/transport/surface/messages/adapter.ts +9 -2
  410. package/src/transport/surface/messages/encode.ts +11 -3
  411. package/src/transport/surface/messages/parse.ts +2 -1
  412. package/src/transport/surface/messages/stream.ts +33 -17
  413. package/src/transport/surface/responses/encode.ts +34 -6
  414. package/src/transport/surface/responses/parse.ts +3 -11
  415. package/src/workers/daily-checkin.ts +1 -1
  416. package/src/workers/quota-refresh-sweep.ts +1 -1
  417. package/src/workers/scheduler.ts +22 -4
  418. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  419. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  420. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  421. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  422. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  423. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  424. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  425. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  426. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  427. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  428. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  429. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  430. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  431. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  432. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  433. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  434. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  435. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  436. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  437. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  438. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  439. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  440. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  441. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  442. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
  443. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  444. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  445. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  446. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  447. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  448. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  449. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  450. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  451. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  452. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  453. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  454. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  455. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  456. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  457. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  458. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  459. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  460. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  461. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  462. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  463. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  464. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  465. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  466. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  467. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  468. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  469. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  470. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  471. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  472. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  473. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  474. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  475. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  476. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  477. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  478. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  479. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  480. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  481. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  482. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  483. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  484. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  485. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  486. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  487. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  488. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  489. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  490. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  491. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  492. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  493. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  494. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  495. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  496. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  497. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  498. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  499. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  500. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  501. package/dist/dashboard/console-backgrounds.jpg +0 -0
  502. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  503. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  504. package/src/providers/bundled-discovery-providers.ts +0 -98
  505. package/src/providers/configured-openai-providers.ts +0 -120
  506. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  507. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  508. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  509. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  510. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  511. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  512. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  513. package/src/providers/operations/model-discovery-cache.ts +0 -34
  514. package/src/providers/operations/probe-fetch.ts +0 -15
  515. package/src/providers/operations/provider-version-cache.ts +0 -30
  516. package/src/providers/provider-capabilities.ts +0 -33
  517. package/src/providers/provider-credential-envelope.ts +0 -43
  518. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
  519. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -14,9 +14,10 @@ import type {
14
14
  } from "../../../protocol/canonical-model";
15
15
  import { toolResultParts, toolCallParts } from "../../../protocol/canonical-model";
16
16
  import { reasoningEffortFromIntent } from "../../../protocol/reasoning";
17
+ import { reasoningTextForReplay } from "../../../protocol/reasoning-text";
17
18
  import { resolveImageSource } from "../../../protocol/codecs";
18
19
  import { getQoderVersion } from "../../operations/client-versions";
19
- import { qoderDiscoveredMaxContexts, QODER_CN_MODEL_CONFIGS, QODER_INTL_MODEL_CONFIGS } from "./qoder-catalog";
20
+ import { qoderMaxContextFor, qoderRosterMaxOutput } from "./qoder-catalog";
20
21
  import { transformQoderBody, qoderBuildCosyHeaders } from "./qoder-cosy";
21
22
  import { qoderSessionId } from "./qoder-auth";
22
23
  import { flattenCanonicalContent, qoderToolChoice, qoderTools } from "./qoder-body";
@@ -69,12 +70,6 @@ function qoderStableRecordId(modelId: string, messages: CanonicalRequest["messag
69
70
  * never capped at the *currently active* window (often 180k) when the account
70
71
  * can use 1M. Falls back to the static roster's stated window otherwise.
71
72
  */
72
- function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
73
- const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
74
- if (discovered !== undefined) return discovered;
75
- return modelConfig.max_input_tokens;
76
- }
77
-
78
73
  function qoderRequestedMaxTokens(
79
74
  request: CanonicalRequest,
80
75
  modelConfig: QoderModelConfig,
@@ -91,7 +86,7 @@ function qoderRequestedMaxTokens(
91
86
  const configured =
92
87
  typeof modelConfig.max_output_tokens === "number"
93
88
  ? modelConfig.max_output_tokens
94
- : qoderRosterMaxOutput(modelConfig.id, realm) ?? 65_536;
89
+ : qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelConfig.id) ?? 65_536;
95
90
  const controls = request.generation_controls as Record<string, unknown>;
96
91
  const requested =
97
92
  typeof controls["max_tokens"] === "number"
@@ -102,17 +97,6 @@ function qoderRequestedMaxTokens(
102
97
  return typeof requested === "number" && requested > 0 ? Math.min(requested, configured) : configured;
103
98
  }
104
99
 
105
- /**
106
- * The static roster's stated output ceiling for one key, when the roster states
107
- * one. Dispatch builds a live-discovered model's config without an output limit
108
- * (upstream's model list reports none); this lookup restores the roster's
109
- * ceiling so such a model is not capped at the 65_536 last resort.
110
- */
111
- function qoderRosterMaxOutput(modelId: string, realm: QoderRealm): number | undefined {
112
- const configs = realm === "cn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
113
- return configs[modelId]?.max_output_tokens;
114
- }
115
-
116
100
  /**
117
101
  * Whether this model needs the prior turn's reasoning echoed back.
118
102
  *
@@ -159,7 +143,9 @@ function qoderReasoningEcho(
159
143
  return hasReasoningTrace ? { reasoning_content: "" } : {};
160
144
  }
161
145
  const text = reasoning
162
- .map((part) => (part.kind === "reasoning" ? (part.summary ?? part.payload ?? "") : ""))
146
+ .map((part) =>
147
+ part.kind === "reasoning" ? (reasoningTextForReplay(part) ?? part.payload ?? "") : "",
148
+ )
163
149
  .join("");
164
150
  return { reasoning_content: text };
165
151
  }
@@ -448,7 +434,7 @@ export function buildQoderRequest(
448
434
  // for every model alike, so the request would declare a cap the catalog
449
435
  // does not publish. A discovered model's config carries no output limit,
450
436
  // so consult the roster for the key before the 65_536 last resort.
451
- max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(modelId, realm) ?? 65_536,
437
+ max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelId) ?? 65_536,
452
438
  source: modelConfig.source ?? "system",
453
439
  },
454
440
  messages: qoderMessages,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Qoder daily check-in (streak / 100 Credits per day), ported from the
3
- * reference bridge (`qoder_accounts.py` in `qoder2api-hub`).
3
+ * reference bridge (`qoder_accounts.py` in the donor hub).
4
4
  *
5
5
  * Two upstream surfaces, tried in order:
6
6
  *
@@ -79,12 +79,12 @@ function campaignHeaders(credential: string): Record<string, string> {
79
79
  return buildCampaignHeaders(credential);
80
80
  }
81
81
 
82
- function asRecord(value: unknown): Record<string, unknown> | null {
82
+ export function asRecord(value: unknown): Record<string, unknown> | null {
83
83
  if (typeof value !== "object" || value === null) return null;
84
84
  return value as Record<string, unknown>;
85
85
  }
86
86
 
87
- function asNumber(value: unknown): number | null {
87
+ export function asNumber(value: unknown): number | null {
88
88
  if (typeof value === "number" && Number.isFinite(value)) return value;
89
89
  if (typeof value === "string" && value.trim() !== "") {
90
90
  const parsed = Number(value);
@@ -93,7 +93,7 @@ function asNumber(value: unknown): number | null {
93
93
  return null;
94
94
  }
95
95
 
96
- function safeParse(raw: string): unknown {
96
+ export function safeParse(raw: string): unknown {
97
97
  try {
98
98
  return JSON.parse(raw);
99
99
  } catch {
@@ -127,7 +127,7 @@ function asState(state: string, today: string, lastClaimedAt: unknown): { active
127
127
  * consumes. The API returns camelCase keys (`campaignId`, `actionType`,
128
128
  * `claimStatus`); the fixtures and the donor hub use snake_case — accept both.
129
129
  */
130
- function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
130
+ export function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
131
131
  const benefit = asRecord(raw["benefit"]);
132
132
  return {
133
133
  campaign_id: String(raw["campaignId"] ?? raw["campaign_id"] ?? ""),
@@ -443,7 +443,7 @@ export async function fetchQoderDailyCheckin(args: {
443
443
  }
444
444
  }
445
445
  if (!bound && store) {
446
- const minted = await allocateQoderNativeIdentity(store, providerId, accountId);
446
+ const minted = await allocateQoderNativeIdentity(store, providerId, accountId, { realm });
447
447
  if (minted) {
448
448
  boundRecord = minted;
449
449
  bound = {
@@ -20,7 +20,8 @@ import { join } from "node:path";
20
20
  import * as grpc from "@grpc/grpc-js";
21
21
  import * as protoLoader from "@grpc/proto-loader";
22
22
  import { GatewayError } from "../../../protocol/gateway-error";
23
- import type { CanonicalEvent, CanonicalRequest, CanonicalStopReason } from "../../../protocol/canonical-model";
23
+ import type { CanonicalEvent, CanonicalRequest, CanonicalStopReason, UsageRecord } from "../../../protocol/canonical-model";
24
+ import type { ProviderCaptureExchange } from "../../provider-contracts";
24
25
  import { usageFromProvider } from "../../provider-usage";
25
26
  import { getQoderVersion } from "../../operations/client-versions";
26
27
  import { qoderMachineOs, qoderMachineHostname } from "../../operations/cli-platform";
@@ -361,7 +362,6 @@ export function buildQoderGrpcRequest(
361
362
  modelId: string,
362
363
  request: CanonicalRequest,
363
364
  userId: string,
364
- _contextWindow = 1_000_000,
365
365
  legacy?: {
366
366
  readonly messages: Array<Record<string, unknown>>;
367
367
  readonly tools?: Array<Record<string, unknown>>;
@@ -440,6 +440,65 @@ export function qoderGrpcMetadata(auth: QoderAuth, modelId = ""): grpc.Metadata
440
440
  return metadata;
441
441
  }
442
442
 
443
+ /**
444
+ * gRPC metadata headers safe to persist in the provider-request panel.
445
+ *
446
+ * Mirrors the HTTP capture allowlist (`CAPTURED_REQUEST_HEADERS` in
447
+ * `transport/dispatch/attempt-finalize.ts`): session/identity framing is kept,
448
+ * but credentials are never stored. `authorization` (the Bearer job token) is
449
+ * deliberately excluded — the HTTP wrapper never captures it, and the gRPC
450
+ * capture must not either.
451
+ */
452
+ const GRPC_CAPTURED_HEADERS = [
453
+ "x-request-id",
454
+ "x-session-id",
455
+ "cosy-version",
456
+ "cosy-clienttype",
457
+ "cosy-machineos",
458
+ "cosy-machinehostname",
459
+ "x-opencode-client",
460
+ "x-opencode-session",
461
+ "x-opencode-session-id",
462
+ "x-opencode-parent-session-id",
463
+ "x-opencode-request",
464
+ "x-opencode-project",
465
+ "x-conversation-id",
466
+ "x-conversation-request-id",
467
+ "x-conversation-message-id",
468
+ "x-root-request-id",
469
+ "x-trace-id",
470
+ "traceparent",
471
+ "b3",
472
+ ] as const;
473
+
474
+ /**
475
+ * A gRPC exchange is not a `fetch`, so the HTTP capture wrapper never sees it.
476
+ * The adapter fills the sink directly with a faithful rendering of the proto
477
+ * call: the request line (grpc method, host, allowlisted metadata, and the
478
+ * translated body) and a deferred response that resolves to the collected
479
+ * stream summary.
480
+ */
481
+ export function captureQoderGrpcRequest(
482
+ request: Record<string, unknown>,
483
+ metadata: grpc.Metadata,
484
+ realm: QoderRealm,
485
+ ): Record<string, unknown> {
486
+ const host = qoderGrpcHost(realm);
487
+ const map = metadata.getMap() as Record<string, string | string[]>;
488
+ const headers: Record<string, string> = {};
489
+ for (const name of GRPC_CAPTURED_HEADERS) {
490
+ const value = map[name];
491
+ if (value === undefined) continue;
492
+ headers[name] = Array.isArray(value) ? value.join(", ") : String(value);
493
+ }
494
+ return {
495
+ method: "POST",
496
+ url: `grpc://${host}/model.chat.ChatService/ChatCompletionStream`,
497
+ headers,
498
+ body: request,
499
+ };
500
+ }
501
+
443
502
  /** Maps a proto `finish_reason` to the canonical stop reason. */
444
503
  function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefined {
445
504
  switch (raw) {
@@ -450,6 +509,10 @@ function mapFinishReason(raw: string | undefined): CanonicalStopReason | undefin
450
509
  return "tool_use";
451
510
  case "length":
452
511
  return "length";
512
+ case "error":
513
+ return "error";
514
+ case "refusal":
515
+ return "refusal";
453
516
  default:
454
517
  return undefined;
455
518
  }
@@ -466,6 +529,7 @@ export async function* qoderGrpcToCanonicalEvents(
466
529
  metadata: grpc.Metadata,
467
530
  realm: QoderRealm,
468
531
  signal: AbortSignal,
532
+ capture?: ProviderCaptureExchange,
469
533
  ): AsyncGenerator<CanonicalEvent> {
470
534
  const ChatService = qoderGrpcClient();
471
535
  const host = qoderGrpcHost(realm);
@@ -483,6 +547,122 @@ export async function* qoderGrpcToCanonicalEvents(
483
547
  // clients reject content after the finish reason. A second terminal is
484
548
  // skipped too (the upstream can re-emit the finish frame).
485
549
  let finished = false;
550
+ // Last usage chunk seen; attached to the terminal event (the upstream
551
+ // streams usage after the finish-reason chunk, and a standalone `usage`
552
+ // event after terminal breaks strict Messages encoders).
553
+ let pendingUsage: UsageRecord | undefined;
554
+ /** Whether the terminal already carried the pending usage. */
555
+ let terminalEmitted = false;
556
+ // Capture sinks fed from the same `for await` loop that drives production
557
+ // events (see `feedCapture`). Declared here so the loop below can call
558
+ // them; they are assigned when capture is enabled.
559
+ let feedCapture: ((chunk: unknown) => void) | undefined;
560
+ let flushToolCalls: (() => void) | undefined;
561
+ if (capture !== undefined) {
562
+ capture.request = captureQoderGrpcRequest(request, metadata, realm);
563
+ capture.response = new Promise((resolve) => {
564
+ const summary: Record<string, unknown> = {
565
+ transport: "grpc",
566
+ chunks: 0,
567
+ answer_chars: 0,
568
+ reasoning_chars: 0,
569
+ tool_calls: 0,
570
+ models: [] as string[],
571
+ usage: undefined,
572
+ stop_reason: undefined,
573
+ status_code: 0,
574
+ // Materialized response content (bounded): the text the model
575
+ // emitted, the reasoning trace, and the tool calls that were
576
+ // announced. This is what Request Detail shows instead of a bare
577
+ // statistics block.
578
+ text: "",
579
+ reasoning: "",
580
+ toolCalls: [] as Array<{ index: number; id?: string; name?: string; arguments?: string }>,
581
+ };
582
+ // Per-index tool-call fragments (the upstream streams one call at a
583
+ // time: id + empty args, then argument fragments, index always 0).
584
+ const toolCallsByIndex = new Map<number, { id?: string; name?: string; arguments?: string }>();
585
+ const MAX_TEXT_CHARS = 16_000;
586
+ const MAX_TOOL_CALLS = 50;
587
+ const MAX_TOOL_ARGS_CHARS = 2_000;
588
+ // The summary is fed from the SAME `for await` loop that drives the
589
+ // production event stream (see `feedCapture` below) — a separate
590
+ // `data` listener raced the iterator and saw nothing (or a different
591
+ // decode), so tool calls and text never made it into the capture.
592
+ feedCapture = (chunk: unknown) => {
593
+ const rec = chunk as Record<string, unknown>;
594
+ summary["chunks"] = (summary["chunks"] as number) + 1;
595
+ const model = rec["model"];
596
+ if (typeof model === "string" && model.length > 0) {
597
+ const models = summary["models"] as string[];
598
+ if (!models.includes(model)) models.push(model);
599
+ }
600
+ const usage = rec["usage"];
601
+ if (usage !== undefined && typeof usage === "object") summary["usage"] = usage;
602
+ const choices = Array.isArray(rec["choices"]) ? (rec["choices"] as Array<Record<string, unknown>>) : [];
603
+ for (const choice of choices) {
604
+ const delta = choice["delta"] as Record<string, unknown> | undefined;
605
+ const content = delta?.["content"];
606
+ if (typeof content === "string" && content.length > 0) {
607
+ summary["answer_chars"] = (summary["answer_chars"] as number) + content.length;
608
+ const text = summary["text"] as string;
609
+ if (text.length < MAX_TEXT_CHARS) {
610
+ summary["text"] = (text + content).slice(0, MAX_TEXT_CHARS);
611
+ }
612
+ }
613
+ const reasoning = delta?.["reasoning_content"];
614
+ if (typeof reasoning === "string" && reasoning.length > 0) {
615
+ summary["reasoning_chars"] = (summary["reasoning_chars"] as number) + reasoning.length;
616
+ const reasoningText = summary["reasoning"] as string;
617
+ if (reasoningText.length < MAX_TEXT_CHARS) {
618
+ summary["reasoning"] = (reasoningText + reasoning).slice(0, MAX_TEXT_CHARS);
619
+ }
620
+ }
621
+ const calls = delta?.["tool_calls"];
622
+ if (Array.isArray(calls) && calls.length > 0) {
623
+ summary["tool_calls"] = (summary["tool_calls"] as number) + calls.length;
624
+ for (const rawTc of calls) {
625
+ const tc = fromProtoStruct(rawTc);
626
+ const index = typeof tc["index"] === "number" ? (tc["index"] as number) : 0;
627
+ const entry = toolCallsByIndex.get(index) ?? {};
628
+ const declaredId = tc["id"];
629
+ if (typeof declaredId === "string" && declaredId.trim().length > 0) entry.id = declaredId.trim();
630
+ const fn = (tc["function"] ?? {}) as Record<string, unknown>;
631
+ const name = fn["name"];
632
+ if (typeof name === "string" && name.trim().length > 0) entry.name = name.trim();
633
+ const args = fn["arguments"];
634
+ if (typeof args === "string" && args.length > 0) {
635
+ entry.arguments = ((entry.arguments ?? "") + args).slice(0, MAX_TOOL_ARGS_CHARS);
636
+ }
637
+ toolCallsByIndex.set(index, entry);
638
+ }
639
+ }
640
+ const finish = choice["finish_reason"];
641
+ if (typeof finish === "string" && finish.length > 0) summary["stop_reason"] = finish;
642
+ }
643
+ };
644
+ flushToolCalls = () => {
645
+ const entries = [...toolCallsByIndex.entries()]
646
+ .sort((a, b) => a[0] - b[0])
647
+ .slice(0, MAX_TOOL_CALLS)
648
+ .map(([index, entry]) => ({ index, ...entry }));
649
+ if (entries.length > 0) summary["toolCalls"] = entries;
650
+ };
651
+ stream.on("status", (s: { code?: number; details?: string }) => {
652
+ summary["status_code"] = s.code ?? 0;
653
+ summary["grpc_message"] = s.details ?? "";
654
+ });
655
+ stream.on("error", (err: Error) => {
656
+ summary["error"] = err.message;
657
+ flushToolCalls?.();
658
+ resolve(summary);
659
+ });
660
+ stream.on("end", () => {
661
+ flushToolCalls?.();
662
+ resolve(summary);
663
+ });
664
+ });
665
+ }
486
666
 
487
667
  const abortHandler = () => {
488
668
  aborted = true;
@@ -502,6 +682,8 @@ export async function* qoderGrpcToCanonicalEvents(
502
682
  const fragmentState: QoderToolFragmentState = {};
503
683
  for await (const chunk of stream) {
504
684
  if (aborted || signal.aborted) break;
685
+ // Feed the capture summary from this same chunk (single consumer).
686
+ feedCapture?.(chunk);
505
687
  // The upstream names the model it actually ran in the chunk's `model`
506
688
  // field. Surface it once as `response_start` so probes and telemetry
507
689
  // can report the real echoed model (the gRPC surface has no HTTP body
@@ -599,24 +781,45 @@ export async function* qoderGrpcToCanonicalEvents(
599
781
  yield {
600
782
  type: "terminal",
601
783
  sequence_number: nextSeq(),
602
- state: stop === "stop" || stop === "tool_use" ? "complete" : "failed",
784
+ // `tool_use` and `refusal` are *successful* terminal verdicts:
785
+ // the model decided to call a tool / declined to answer, but the
786
+ // response itself is valid — `complete`, never `failed`. A
787
+ // `failed` state here made attempt-finalize treat a healthy tool
788
+ // turn as an upstream failure ("empty-handed") and replay it.
789
+ // A key `error` (or `length`) is the failed case.
790
+ state: stop === "stop" || stop === "tool_use" || stop === "refusal" ? "complete" : "failed",
603
791
  stop_reason: stop,
604
792
  provider_stop_reason: choice.finish_reason,
793
+ // The upstream streams `usage` in a trailing chunk *after* the
794
+ // finish-reason chunk. A separate `usage` event after the
795
+ // terminal is rejected by strict Anthropic Messages encoders
796
+ // (the 2.7.8 regression), so the usage is carried here on the
797
+ // terminal instead — exactly like the IDE surface does.
798
+ ...(pendingUsage === undefined ? {} : { usage: pendingUsage }),
605
799
  };
800
+ if (pendingUsage !== undefined) terminalEmitted = true;
606
801
  }
607
802
  }
608
803
  }
609
- if (chunk.usage && !finished) {
804
+ if (chunk.usage) {
610
805
  const usage = usageFromProvider(chunk.usage);
611
806
  if (usage !== undefined) {
612
- yield { type: "usage", sequence_number: nextSeq(), usage };
807
+ // Keep the last usage chunk; it is attached to the terminal above
808
+ // (and only if no terminal ever arrives, emitted as a standalone
809
+ // event at stream end).
810
+ pendingUsage = usage;
613
811
  }
614
812
  }
615
813
  }
616
814
  // Tool-call deltas are emitted as they arrive — there is no end-of-stream
617
815
  // buffer to flush; the terminal event is yielded by the finish-reason
618
816
  // branch above (or synthesized by the caller if the stream ends without
619
- // one).
817
+ // one). If no terminal carried the pending usage (the stream ended
818
+ // without a finish reason), emit it as a standalone event so telemetry
819
+ // still sees the upstream's numbers.
820
+ if (!terminalEmitted && pendingUsage !== undefined) {
821
+ yield { type: "usage", sequence_number: nextSeq(), usage: pendingUsage };
822
+ }
620
823
  } catch (err: any) {
621
824
  const status = err?.code;
622
825
  const details = err?.details ?? err?.message ?? "";
@@ -644,7 +847,7 @@ export async function* qoderGrpcToCanonicalEvents(
644
847
  "quota_exceeded",
645
848
  429,
646
849
  "Qoder quota exhausted on the gRPC surface (request {requestId})",
647
- {},
850
+ { providerCode: "quota_exceeded" },
648
851
  "upstream",
649
852
  );
650
853
  }
@@ -163,6 +163,8 @@ function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
163
163
  if (raw === "length") return "length";
164
164
  if (raw === "tool_calls") return "tool_use";
165
165
  if (raw === "content_filter") return "content_filter";
166
+ if (raw === "error") return "error";
167
+ if (raw === "refusal") return "refusal";
166
168
  return undefined;
167
169
  }
168
170
 
@@ -402,7 +404,9 @@ export async function* qoderBodyToCanonicalEvents(
402
404
  yield {
403
405
  type: "terminal",
404
406
  sequence_number: seq++,
405
- state: "complete",
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",
406
410
  ...(stopReason === undefined ? {} : { stop_reason: stopReason }),
407
411
  ...(_providerStop === undefined ? {} : { provider_stop_reason: _providerStop }),
408
412
  usage: usageFromProvider(rawUsage),
@@ -475,110 +479,3 @@ export function buildQoderIdeRequest(
475
479
  };
476
480
  }
477
481
 
478
- /**
479
- * Qoder upstream streams are flaky: the IDE surface sometimes answers a full
480
- * normal stop with reasoning only and no output text/tool calls (observed ~1
481
- * in 4 runs with Sonus). That is not a valid completion — the lab would show a
482
- * thought with "No text came back". Replay such a stream before committing to
483
- * it. `sniffQoderStream` below inspects just enough of the body to make that
484
- * call without buffering the whole (possibly long) stream.
485
- */
486
-
487
- /**
488
- * Reads the upstream body just far enough to decide whether it carries real
489
- * output, then returns a replay stream that re-emits the already-read bytes
490
- * followed by the rest of the original body. If the body ends before any
491
- * meaningful output is seen, `meaningful` is false and the caller replays the
492
- * whole request (empty / thought-only recovery) without ever buffering a large
493
- * response: the original `drainQoderResponse` read the entire stream before
494
- * forwarding, which made every stream appear to complete in one burst at the
495
- * end.
496
- */
497
- async function sniffQoderStream(
498
- response: Response,
499
- signal: AbortSignal,
500
- ): Promise<{ meaningful: boolean; response: Response }> {
501
- const body = response.body;
502
- if (body === null) return { meaningful: true, response };
503
- const reader = body.getReader();
504
- const decoder = new TextDecoder();
505
- const pending: Uint8Array[] = [];
506
- let inspected = "";
507
- let meaningful = false;
508
- for (let i = 0; i < 32 && !meaningful; i += 1) {
509
- const { done, value } = await reader.read();
510
- if (signal.aborted) break;
511
- if (done) break;
512
- pending.push(value);
513
- inspected += decoder.decode(value, { stream: true });
514
- // Peek at completed SSE frames only: a content delta or a tool-call
515
- // fragment means real output is coming. Reasoning-only streams are NOT
516
- // meaningful (they are exactly what empty-stream recovery replays), and a
517
- // finish reason alone is not output either.
518
- if (/data:/.test(inspected)) {
519
- for (const frame of inspected.split(/\r?\n\r?\n/)) {
520
- if (!frame.includes("data:")) continue;
521
- if (frame.includes('"content":"') || frame.includes('"tool_calls"')) {
522
- meaningful = true;
523
- break;
524
- }
525
- }
526
- }
527
- if (inspected.length > 65536) {
528
- meaningful = true;
529
- break;
530
- }
531
- }
532
- // Replay: previously-read bytes first, then continue the original body.
533
- let offset = 0;
534
- const replay = new ReadableStream<Uint8Array>({
535
- start(controller) {
536
- for (; offset < pending.length; offset += 1) controller.enqueue(pending[offset]);
537
- },
538
- async pull(controller) {
539
- if (offset < pending.length) {
540
- controller.enqueue(pending[offset]);
541
- offset += 1;
542
- return;
543
- }
544
- const { done, value } = await reader.read();
545
- if (signal.aborted) {
546
- controller.close();
547
- return;
548
- }
549
- if (done) {
550
- controller.close();
551
- return;
552
- }
553
- controller.enqueue(value);
554
- },
555
- cancel(reason) {
556
- return reader.cancel(reason);
557
- },
558
- });
559
- return { meaningful, response: new Response(replay, response) };
560
- }
561
-
562
- /**
563
- * Runs the IDE request up to `1 + maxReplays` times, returning the first
564
- * response whose stream carried real output (text or tool calls). A 200 with
565
- * no output (empty or thought-only) is replayed rather than surfaced. The
566
- * stream is forwarded progressively (not buffered to completion).
567
- */
568
- async function fetchQoderIdeWithEmptyRecovery(
569
- requestOnce: () => Promise<Response>,
570
- signal: AbortSignal,
571
- maxReplays = 2,
572
- ): Promise<Response> {
573
- let last: Response | undefined;
574
- for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
575
- const res = await requestOnce();
576
- if (!res.ok || !res.body) return res;
577
- const sniff = await sniffQoderStream(res, signal);
578
- last = sniff.response;
579
- if (sniff.meaningful) return last;
580
- }
581
- return last!;
582
- }
583
-
584
- export { fetchQoderIdeWithEmptyRecovery };