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
@@ -0,0 +1,35 @@
1
+ // Free-pi realm constants.
2
+ //
3
+ // A single deployment with no regional variants: the backend base URL is the
4
+ // same for everyone (FREEPI_BASE_URL defaults to https://api.freepi.ai). The
5
+ // chat wire is OpenAI-completions (`/v1/chat/completions`), and the credential
6
+ // is a JWT issued by the backend (device flow or guest), sent as
7
+ // `Authorization: Bearer <jwt>`.
8
+
9
+ /** Default backend base URL, matching FREEPI_BASE_URL. */
10
+ export const FREEPI_BASE_URL = "https://api.freepi.ai";
11
+
12
+ /** Chat completions path, relative to the base URL (OpenAI-completions). */
13
+ export const FREEPI_CHAT_PATH = "/v1/chat/completions";
14
+
15
+ /** Ad fetch/impression paths, relative to the base URL. */
16
+ export const FREEPI_AD_NEXT_PATH = "/ads/next";
17
+ export const FREEPI_AD_IMPRESSION_PATH = "/ads/impression";
18
+
19
+ /** Client version the gateway reports via `x-client-version`. */
20
+ export const FREEPI_CLI_VERSION = "0.6.14";
21
+
22
+ /** Consent version shipped in 0.6.14. */
23
+ export const FREEPI_CONSENT_VERSION = "4";
24
+
25
+ /** Provider id used by the registry. */
26
+ export const FREEPI_PROVIDER_ID = "freepi";
27
+
28
+ /** Static roster fallback; the live `/client-version` catalog replaces it. */
29
+ export const FREEPI_DEFAULT_MODEL = "deepseek/deepseek-v4-flash";
30
+
31
+ /** Context window the provider reports for every model (the client hardcodes 200k). */
32
+ export const FREEPI_CONTEXT_WINDOW = 200_000;
33
+
34
+ /** Maximum output tokens the client requests (hardcoded in the bundle). */
35
+ export const FREEPI_MAX_OUTPUT_TOKENS = 8192;
@@ -0,0 +1,292 @@
1
+ // Free-pi adapter.
2
+ //
3
+ // OpenAI-completions wire with a JWT bearer. The model catalog is
4
+ // server-driven (`GET /client-version`); dispatch posts to
5
+ // `/v1/chat/completions` and translates the OpenAI-style SSE stream into
6
+ // canonical events.
7
+
8
+ import { randomUUID } from "node:crypto";
9
+ import type {
10
+ CanonicalEvent,
11
+ CanonicalRequest,
12
+ CanonicalMessage,
13
+ CanonicalStopReason,
14
+ } from "../../../protocol/canonical-model";
15
+ import { GatewayError } from "../../../protocol/gateway-error";
16
+ import {
17
+ assertWireFamily,
18
+ readCredentialSecret,
19
+ type ProviderAdapter,
20
+ type ProviderDispatchContext,
21
+ type ProviderDispatchTarget,
22
+ type ProviderId,
23
+ } from "../../provider-contracts";
24
+ import { mapUpstreamHttpError } from "../../../transport/failure-policy";
25
+ import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
26
+ import { resolvePromptCacheKey } from "../../operations/session-resolution";
27
+ import { usageFromProvider } from "../../provider-usage";
28
+ import { buildFreepiChatBody, parseFreepiSse, throwFreepiUpstreamError } from "./freepi-protocol";
29
+ import { buildFreepiHeaders } from "./freepi-headers";
30
+ import {
31
+ FREEPI_AD_IMPRESSION_PATH,
32
+ FREEPI_AD_NEXT_PATH,
33
+ FREEPI_BASE_URL,
34
+ FREEPI_CHAT_PATH,
35
+ FREEPI_PROVIDER_ID,
36
+ } from "./freepi-realm";
37
+
38
+ const STOP_REASONS: Readonly<Record<string, CanonicalStopReason>> = {
39
+ stop: "stop",
40
+ length: "length",
41
+ tool_calls: "tool_use",
42
+ function_call: "tool_use",
43
+ };
44
+
45
+ interface FreepiAdapterOptions {
46
+ readonly providerId?: string;
47
+ readonly fetch?: typeof fetch;
48
+ }
49
+
50
+ export class FreepiAdapter implements ProviderAdapter {
51
+ readonly provider_id: ProviderId;
52
+ private readonly fetchFn: typeof fetch;
53
+ /**
54
+ * One stable ad-registered session id per account credential, so repeated
55
+ * chats reuse a session the upstream knows has shown ads. The session must
56
+ * exist for the chat wire: without it the server answers `ads_required`
57
+ * ("Ads aren't showing for this account, so free completions are paused").
58
+ */
59
+ #sessionByCredential = new Map<string, string>();
60
+
61
+ constructor(options: FreepiAdapterOptions = {}) {
62
+ this.provider_id = (options.providerId ?? FREEPI_PROVIDER_ID) as ProviderId;
63
+ this.fetchFn = options.fetch ?? globalThis.fetch;
64
+ }
65
+
66
+ /**
67
+ * Ensures a session id exists for this credential and that the session has
68
+ * registered at least one ad impression (banner + inline). The upstream
69
+ * refuses the chat wire with `ads_required` until a session has shown ads,
70
+ * so the first call per credential pays the two ad-registration requests.
71
+ *
72
+ * Returns the session id to send as `x-session-id`. Falls back to a fresh
73
+ * random id when registration fails — a broken ad surface must not block
74
+ * chat entirely, and the error is caught upstream anyway.
75
+ */
76
+ async #ensureSession(secret: string, outboundFetch: typeof fetch): Promise<string> {
77
+ const existing = this.#sessionByCredential.get(secret);
78
+ if (existing !== undefined) return existing;
79
+ const sessionId = randomUUID();
80
+ try {
81
+ for (const slot of ["banner", "inline"] as const) {
82
+ const next = await outboundFetch(new URL(`${FREEPI_AD_NEXT_PATH}?slot=${slot}`, FREEPI_BASE_URL), {
83
+ method: "GET",
84
+ headers: buildFreepiHeaders(secret, false, sessionId),
85
+ signal: AbortSignal.timeout(15_000),
86
+ });
87
+ if (!next.ok || next.status === 204) continue;
88
+ const ad = (await next.json()) as Record<string, unknown>;
89
+ const adId = ad["ad_id"];
90
+ const clickToken = ad["click_token"];
91
+ if (typeof adId !== "string" || typeof clickToken !== "string") continue;
92
+ await outboundFetch(new URL(FREEPI_AD_IMPRESSION_PATH, FREEPI_BASE_URL), {
93
+ method: "POST",
94
+ headers: buildFreepiHeaders(secret, false, sessionId),
95
+ body: JSON.stringify({ ad_id: adId, click_token: clickToken, imp_fired: true }),
96
+ signal: AbortSignal.timeout(15_000),
97
+ });
98
+ }
99
+ } catch {
100
+ // The ad surface is best-effort; chat may still work or fail upstream.
101
+ }
102
+ this.#sessionByCredential.set(secret, sessionId);
103
+ return sessionId;
104
+ }
105
+
106
+ async *dispatch(
107
+ request: CanonicalRequest,
108
+ candidate: ProviderDispatchTarget,
109
+ context: ProviderDispatchContext,
110
+ ): AsyncIterable<CanonicalEvent> {
111
+ if (candidate.wire_family !== "chat") {
112
+ assertWireFamily(candidate, "chat", "freepi");
113
+ }
114
+ const secret = readCredentialSecret(context.credential, "Free-pi credential is required");
115
+ const modelId = candidate.model_id || request.model;
116
+ const lifecycle = createUpstreamDeadlineLifecycle(context);
117
+ const outboundFetch =
118
+ (context.outbound_fetch as unknown as typeof fetch | undefined) ?? this.fetchFn;
119
+
120
+ const body = buildFreepiChatBody(modelId, request, candidate.output_limit ?? undefined);
121
+
122
+ const sessionKey = resolvePromptCacheKey(request, context);
123
+ const cacheSessionKey = sessionKey === undefined ? undefined : `${modelId}:${sessionKey}`;
124
+
125
+ const sessionId = await this.#ensureSession(secret, outboundFetch);
126
+ const headers = buildFreepiHeaders(secret, true, sessionId);
127
+ const url = `${FREEPI_BASE_URL}${FREEPI_CHAT_PATH}`;
128
+
129
+ try {
130
+ const response = await outboundFetch(url, {
131
+ method: "POST",
132
+ headers,
133
+ body: JSON.stringify(body),
134
+ signal: lifecycle.signal,
135
+ });
136
+ if (!response.ok) throw await mapUpstreamHttpError(response, this.provider_id);
137
+ if (!response.body) {
138
+ throw new GatewayError("platform_unavailable", 502, "Free-pi returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
139
+ }
140
+ lifecycle.release();
141
+ yield* this.#events(
142
+ response.body as ReadableStream<Uint8Array>,
143
+ lifecycle.signal,
144
+ request.model,
145
+ cacheSessionKey,
146
+ request.messages,
147
+ );
148
+ } catch (err: unknown) {
149
+ const abortError = abortGatewayError(lifecycle, err, context.abort_signal);
150
+ if (abortError) throw abortError;
151
+ throw err;
152
+ } finally {
153
+ lifecycle.release();
154
+ }
155
+ }
156
+
157
+ async *#events(
158
+ body: ReadableStream<Uint8Array>,
159
+ _signal: AbortSignal,
160
+ requestModel: string,
161
+ sessionId?: string,
162
+ _messages?: readonly CanonicalMessage[],
163
+ ): AsyncIterable<CanonicalEvent> {
164
+ let seq = 1;
165
+ yield { type: "response_start", sequence_number: seq++, model: requestModel } as CanonicalEvent;
166
+ let stopReason: CanonicalStopReason = "stop";
167
+ let usage: Record<string, unknown> | undefined;
168
+ const toolCallIds = new Map<string, string>();
169
+ const announced = new Set<string>();
170
+
171
+ for await (const frame of parseFreepiSse(body)) {
172
+ if (frame.event === "done") {
173
+ const data = frame.data as { finish_reason?: unknown };
174
+ if (typeof data.finish_reason === "string") {
175
+ stopReason = STOP_REASONS[data.finish_reason] ?? "stop";
176
+ }
177
+ continue;
178
+ }
179
+ if (typeof frame.data !== "object" || frame.data === null) continue;
180
+ const record = frame.data as Record<string, unknown>;
181
+
182
+ // Upstream error payloads can be HTTP 200 `{"error":{...}}`.
183
+ if (record["error"] !== undefined && typeof record["error"] === "object") {
184
+ const nested = record["error"] as Record<string, unknown>;
185
+ throwFreepiUpstreamError(nested["code"], nested["message"]);
186
+ }
187
+ if (record["code"] !== undefined) {
188
+ throwFreepiUpstreamError(record["code"], record["message"]);
189
+ }
190
+
191
+ const choices = Array.isArray(record["choices"]) ? (record["choices"] as Record<string, unknown>[]) : [];
192
+ const choice = choices[0];
193
+ if (!choice) continue;
194
+ const delta = choice["delta"] as Record<string, unknown> | undefined;
195
+ if (!delta) continue;
196
+
197
+ const text = delta["content"];
198
+ if (typeof text === "string" && text.length > 0) {
199
+ yield {
200
+ type: "content_delta",
201
+ sequence_number: seq++,
202
+ content: { kind: "text", text },
203
+ } as CanonicalEvent;
204
+ }
205
+ const reasoning = delta["reasoning_content"];
206
+ if (typeof reasoning === "string" && reasoning.length > 0) {
207
+ yield {
208
+ type: "content_delta",
209
+ sequence_number: seq++,
210
+ content: { kind: "reasoning", payload: null, summary: reasoning },
211
+ } as CanonicalEvent;
212
+ }
213
+ yield* this.#toolCallDeltas(delta["tool_calls"], announced, toolCallIds, () => seq++);
214
+ if (typeof choice["finish_reason"] === "string" && choice["finish_reason"] !== null) {
215
+ stopReason = STOP_REASONS[String(choice["finish_reason"])] ?? "stop";
216
+ }
217
+ const u = record["usage"];
218
+ if (typeof u === "object" && u !== null) usage = u as Record<string, unknown>;
219
+ }
220
+
221
+ const assessed = usage === undefined ? undefined : usageFromProvider(usage);
222
+ if (assessed !== undefined) {
223
+ yield { type: "usage", sequence_number: seq++, usage: assessed } as unknown as CanonicalEvent;
224
+ }
225
+ yield {
226
+ type: "terminal",
227
+ sequence_number: seq,
228
+ state: "complete",
229
+ stop_reason: stopReason,
230
+ ...(assessed === undefined ? {} : { usage: assessed }),
231
+ ...(sessionId !== undefined ? { prompt_cache_key: sessionId } : {}),
232
+ } as unknown as CanonicalEvent;
233
+ }
234
+
235
+ /**
236
+ * Emits canonical tool-call deltas from one OpenAI `tool_calls` array.
237
+ *
238
+ * The upstream spreads one call over many frames: the first carries id and
239
+ * name, the middle ones carry `arguments` as successive JSON fragments with
240
+ * an empty id, and the last repeats the id with no arguments. Grouping by
241
+ * `index` ties them together — the id is absent exactly when the arguments
242
+ * are present, so keying on id would drop every fragment.
243
+ */
244
+ async *#toolCallDeltas(
245
+ raw: unknown,
246
+ announced: Set<string>,
247
+ toolCallIds: Map<string, string>,
248
+ nextSeq: () => number,
249
+ ): AsyncIterable<CanonicalEvent> {
250
+ if (!Array.isArray(raw)) return;
251
+ for (const entry of raw) {
252
+ if (typeof entry !== "object" || entry === null) continue;
253
+ const call = entry as Record<string, unknown>;
254
+ const fn = call["function"];
255
+ const fnRecord = typeof fn === "object" && fn !== null ? (fn as Record<string, unknown>) : undefined;
256
+ const name = typeof fnRecord?.["name"] === "string" ? (fnRecord["name"] as string) : undefined;
257
+ const args = typeof fnRecord?.["arguments"] === "string" ? (fnRecord["arguments"] as string) : "";
258
+ const declaredId = typeof call["id"] === "string" ? call["id"] : "";
259
+ const index = typeof call["index"] === "number" ? call["index"] : 0;
260
+ const slot = `idx:${index}`;
261
+
262
+ if (declaredId.length > 0) {
263
+ toolCallIds.set(slot, declaredId);
264
+ if (announced.has(declaredId)) continue;
265
+ announced.add(declaredId);
266
+ yield {
267
+ type: "tool_call_delta",
268
+ sequence_number: nextSeq(),
269
+ call_id: declaredId,
270
+ name: name ?? "",
271
+ arguments_delta: "",
272
+ } as CanonicalEvent;
273
+ if (args.length === 0) continue;
274
+ }
275
+
276
+ if (args.length === 0) continue;
277
+ const id = toolCallIds.get(slot);
278
+ if (id === undefined) continue;
279
+ yield {
280
+ type: "tool_call_delta",
281
+ sequence_number: nextSeq(),
282
+ call_id: id,
283
+ arguments_delta: args,
284
+ } as CanonicalEvent;
285
+ }
286
+ }
287
+ }
288
+
289
+ /** Adapter for the international free-pi deployment. */
290
+ export function createFreepiAdapter(options: FreepiAdapterOptions = {}): ProviderAdapter {
291
+ return new FreepiAdapter({ ...options, providerId: FREEPI_PROVIDER_ID });
292
+ }
@@ -241,7 +241,7 @@ class GeminiAdapter implements ProviderAdapter {
241
241
  let seq = 1;
242
242
  yield { type: "response_start", sequence_number: seq++, model: request.model } as CanonicalEvent;
243
243
  if (output.thought) {
244
- yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
244
+ yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
245
245
  }
246
246
  if (output.text) {
247
247
  yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
@@ -277,7 +277,7 @@ class GeminiAdapter implements ProviderAdapter {
277
277
  const { candidate: cand, parts } = geminiCandidate(parsed);
278
278
  const output = responseParts(parts);
279
279
  if (output.thought) {
280
- yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought } } as CanonicalEvent;
280
+ yield { type: "content_delta", sequence_number: seq++, content: { kind: "reasoning", payload: null, summary: output.thought, ...(output.thoughtSignature ? { signature: output.thoughtSignature } : {}) } } as CanonicalEvent;
281
281
  }
282
282
  if (output.text) {
283
283
  yield { type: "content_delta", sequence_number: seq++, content: { kind: "text", text: output.text } } as CanonicalEvent;
@@ -1,13 +1,14 @@
1
1
  /** xAI Grok Build device-code OAuth client. */
2
- import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse } from "../../authentication/oauth-flow-store";
2
+ import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse, record } from "../../authentication/oauth-flow-store";
3
3
  import type {
4
4
  OAuthDeviceFlowContext,
5
5
  OAuthDevicePollResult,
6
6
  OAuthDeviceStartResult,
7
+ OAuthExchangeResult,
7
8
  } from "../../authentication/oauth-flow-store";
8
9
  import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
9
10
  import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
10
- import type { FetchLike } from "../../authentication/oauth-client";
11
+ import type { FetchLike, NormalizedTokenResponse } from "../../authentication/oauth-client";
11
12
  import { XAI_AUTH_SERVER } from "../xai/xai-oauth";
12
13
 
13
14
  /**
@@ -178,56 +179,58 @@ export class GrokOAuthClient extends OAuthDeviceFlow {
178
179
  }
179
180
 
180
181
  override async pollDeviceAuth(deviceAuthId: string, _context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
181
- const body = new URLSearchParams({
182
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
183
- device_code: deviceAuthId,
184
- client_id: GROK_CLIENT_ID,
185
- });
186
- const response = await this.fetchFn(GROK_TOKEN_URL, {
187
- method: "POST",
188
- headers: authHeaders(),
189
- body,
182
+ return this.pollGenericDeviceAuth(deviceAuthId, {
183
+ tokenUrl: GROK_TOKEN_URL,
184
+ deviceAuthUrl: GROK_DEVICE_URL,
185
+ clientId: GROK_CLIENT_ID,
186
+ formHeaders: authHeaders(),
190
187
  });
191
- const raw = await response.text();
192
- let payload: TokenPayload;
193
- try {
194
- payload = JSON.parse(raw) as TokenPayload;
195
- } catch {
196
- return { status: "failed", reason: "grok token endpoint returned invalid JSON" };
197
- }
198
- const error = nonEmptyTrimmedString(payload.error);
199
- const verdict = devicePollBackoff(error, payload.interval);
200
- if (verdict !== undefined) return verdict;
201
- if (!response.ok) {
202
- return { status: "failed", reason: nonEmptyTrimmedString(payload.error_description) ?? error ?? `token polling failed (${response.status})` };
203
- }
188
+ }
189
+
190
+ protected override unparsablePollReason(_status: number): string {
191
+ return "grok token endpoint returned invalid JSON";
192
+ }
193
+
194
+ protected override preVerdict(payload: unknown): OAuthDevicePollResult | undefined {
195
+ const root = record(payload) ?? {};
196
+ // Grok votes on the `error` field before the status: a 200 body can still
197
+ // state the poll state, and a pending/slow_down answer is honored wherever
198
+ // it appears on the wire.
199
+ return devicePollBackoff(nonEmptyTrimmedString(root.error), root.interval);
200
+ }
201
+
202
+ protected override pollFailureReason(
203
+ status: number,
204
+ errorKey: string | undefined,
205
+ errorDescription: string | undefined,
206
+ ): string {
207
+ // Grok's wire prefers the human-readable description over the error code,
208
+ // and falls back to the generic phrasing only when both are absent.
209
+ return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? `token polling failed (${status})`;
210
+ }
211
+
212
+ protected override missingTokenReason(errorKey: string | undefined, errorDescription: string | undefined): string {
204
213
  // A 2xx is not by itself a completed authorization: the endpoint can answer
205
214
  // 200 with an `error` field and no token. Returning `failed` rather than
206
215
  // letting the shared parser throw keeps the verdict inside the poll
207
216
  // contract — the dashboard's poll error path stops polling but shows no
208
217
  // reason, so a throw would look like a spinner that never ends.
209
- if (nonEmptyTrimmedString(payload.access_token) === undefined) {
210
- return {
211
- status: "failed",
212
- reason:
213
- nonEmptyTrimmedString(payload.error_description) ??
214
- error ??
215
- "grok token response omitted access_token",
216
- };
217
- }
218
- const result = this.parseTokenResponse(payload);
219
- const identity = await fetchGrokIdentity(result.access, this.fetchFn);
218
+ return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? "grok token response omitted access_token";
219
+ }
220
+
221
+ protected override async afterDeviceTokenExchange(
222
+ _payload: unknown,
223
+ normalized: NormalizedTokenResponse,
224
+ ): Promise<OAuthExchangeResult> {
225
+ const identity = await fetchGrokIdentity(normalized.access, this.fetchFn);
220
226
  const authState = grokAuthState(identity);
221
- return {
222
- status: "complete",
223
- result: this.toExchangeResult({
224
- ...result,
225
- ...(identity.label === undefined ? {} : { accountLabel: identity.label }),
226
- // Omitted when the proxy reported nothing, which leaves any stored
227
- // state alone rather than blanking it.
228
- ...(authState === undefined ? {} : { auth_state: authState }),
229
- }),
230
- };
227
+ return this.toExchangeResult({
228
+ ...normalized,
229
+ ...(identity.label === undefined ? {} : { accountLabel: identity.label }),
230
+ // Omitted when the proxy reported nothing, which leaves any stored
231
+ // state alone rather than blanking it.
232
+ ...(authState === undefined ? {} : { auth_state: authState }),
233
+ });
231
234
  }
232
235
 
233
236
  override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
@@ -12,6 +12,7 @@
12
12
  // Keyed by account id: two Grok accounts carry different buckets, and a reading
13
13
  // filed against the wrong one is worse than no reading at all.
14
14
 
15
+ import { TtlCache } from "../../../persistence/ttl-cache";
15
16
  import type { ProviderQuotaWindow } from "../../quota/quota-contracts";
16
17
  import { grokHeaderRequestsWindow, grokHeaderWindow, type GrokRateLimitReading } from "./grok-ratelimit";
17
18
 
@@ -27,39 +28,31 @@ const SNAPSHOT_TTL_MS = 5 * 60 * 1000;
27
28
 
28
29
  interface Snapshot {
29
30
  readonly windows: readonly ProviderQuotaWindow[];
30
- readonly capturedAt: number;
31
31
  }
32
32
 
33
- const snapshots = new Map<string, Snapshot>();
33
+ // Bounded FIFO cache keyed by account id; the TTL and `maxEntries` cap are
34
+ // enforced by the cache itself (oldest-first eviction past 4096 entries).
35
+ const snapshots = new TtlCache<Snapshot>({
36
+ ttlMs: SNAPSHOT_TTL_MS,
37
+ maxEntries: 4096,
38
+ });
34
39
 
35
40
  /** Files a reading captured from a real dispatch against one account. */
36
41
  export function rememberGrokRateLimit(
37
42
  accountId: string,
38
43
  reading: GrokRateLimitReading,
39
- now = Date.now(),
40
44
  ): void {
41
45
  const windows = [grokHeaderWindow(reading), grokHeaderRequestsWindow(reading)].filter(
42
46
  (value): value is ProviderQuotaWindow => value !== null,
43
47
  );
44
48
  if (windows.length === 0) return;
45
- snapshots.set(accountId, { windows, capturedAt: now });
46
- if (snapshots.size > 4096) {
47
- // Bounded: an unbounded map keyed by account is a leak dressed as a cache.
48
- for (const [key, value] of snapshots) {
49
- if (now - value.capturedAt > SNAPSHOT_TTL_MS) snapshots.delete(key);
50
- }
51
- }
49
+ snapshots.set(accountId, { windows });
52
50
  }
53
51
 
54
52
  /** The stored window for one account, or `null` when none is fresh enough. */
55
- export function storedGrokRateLimit(accountId: string, now = Date.now()): readonly ProviderQuotaWindow[] | null {
53
+ export function storedGrokRateLimit(accountId: string): readonly ProviderQuotaWindow[] | null {
56
54
  const snapshot = snapshots.get(accountId);
57
- if (!snapshot) return null;
58
- if (now - snapshot.capturedAt > SNAPSHOT_TTL_MS) {
59
- snapshots.delete(accountId);
60
- return null;
61
- }
62
- return snapshot.windows;
55
+ return snapshot?.windows ?? null;
63
56
  }
64
57
 
65
58
  /** Test seam. */
@@ -13,19 +13,25 @@
13
13
  */
14
14
 
15
15
  import type { CanonicalRequest } from "../../../protocol/canonical-model";
16
+ import { TtlCache } from "../../../persistence/ttl-cache";
16
17
 
17
18
  /** Idle window after which a session's remembered index is discarded. */
18
19
  const SESSION_TTL_MS = 30 * 60_000;
19
20
 
20
- /** Bound on remembered sessions; oldest insertion is evicted past this. */
21
+ /** Bound on remembered sessions; least-recently-used is evicted past this. */
21
22
  const MAX_SESSIONS = 5_000;
22
23
 
23
24
  interface TurnEntry {
24
25
  readonly turn: number;
25
- lastUsedAt: number;
26
26
  }
27
27
 
28
- const turns = new Map<string, TurnEntry>();
28
+ // Bounded, LRU-ordered: a session re-inserts on every use so the newest
29
+ // session is the last evicted, exactly as the previous delete-then-set Map.
30
+ const turns = new TtlCache<TurnEntry>({
31
+ ttlMs: SESSION_TTL_MS,
32
+ maxEntries: MAX_SESSIONS,
33
+ lru: true,
34
+ });
29
35
  const requestTurns = new WeakMap<object, number>();
30
36
 
31
37
  /** User turns in the request, floored at 1 (a turn is never index 0). */
@@ -58,25 +64,14 @@ export function resolveGrokTurnIndex(
58
64
  }
59
65
  if (sessionId === undefined || sessionId.length === 0) return fromInput;
60
66
 
61
- const now = Date.now();
62
- const previous = turns.get(sessionId);
63
- const remembered =
64
- previous !== undefined && now - previous.lastUsedAt <= SESSION_TTL_MS
65
- ? previous.turn
66
- : undefined;
67
+ const remembered = turns.get(sessionId)?.turn;
67
68
 
68
69
  // A full-history client already reports the true index; a delta-style client
69
70
  // reports 1 every time, so the stored index is advanced by one for it.
70
71
  const turn = remembered === undefined ? fromInput : Math.max(fromInput, remembered + 1);
71
72
 
72
73
  // Re-insert so the entry is newest for eviction order.
73
- turns.delete(sessionId);
74
- if (turns.size >= MAX_SESSIONS) {
75
- const oldest = turns.keys().next();
76
- if (!oldest.done) turns.delete(oldest.value);
77
- }
78
- turns.set(sessionId, { turn, lastUsedAt: now });
74
+ turns.set(sessionId, { turn });
79
75
  if (requestKey !== undefined) requestTurns.set(requestKey, turn);
80
76
  return turn;
81
77
  }
82
-