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,60 @@
1
+ // Free-pi model roster.
2
+ //
3
+ // The live roster is server-driven: `GET /client-version` returns the full
4
+ // model list (`models[]`, with `section` and `tier`). The static roster here
5
+ // is only the fallback when that call fails, mirroring the Trae pattern. All
6
+ // rows use the chat completions wire.
7
+ //
8
+ // Capability/funding facts (from the live 2026-11-08 sweep on a GitHub
9
+ // account): the four "Free Unlimited" rows (particle, deepseek-v4.1-flash,
10
+ // glm-5.3-flash, deepseek-v4-flash) stream for everyone; everything else is
11
+ // either premium (billed from the allowance) or defense (section "defense").
12
+ // Context is 200k and output 8k per the client bundle; these are the client's
13
+ // own claims, not upstream model truth.
14
+
15
+ import { defineModel, type ModelDefinition } from "../../provider-model-definition";
16
+ import {
17
+ FREEPI_CHAT_PATH,
18
+ FREEPI_CONTEXT_WINDOW,
19
+ FREEPI_MAX_OUTPUT_TOKENS,
20
+ FREEPI_PROVIDER_ID,
21
+ } from "./freepi-realm";
22
+
23
+ interface FreepiRow {
24
+ readonly id: string;
25
+ readonly displayName: string;
26
+ readonly free?: boolean;
27
+ readonly proOnly?: boolean;
28
+ }
29
+
30
+ const FREEPI_ROWS: readonly FreepiRow[] = [
31
+ // Only the "Free Unlimited" rows ship in the static roster: they are the
32
+ // ones the live sweep confirmed stream for every account type (guest +
33
+ // GitHub). Paid/defense rows are left to live discovery, which keeps the
34
+ // whole catalog; the fallback must never advertise a model the account
35
+ // cannot serve.
36
+ { id: "deepseek-v4.1-flash", displayName: "DeepSeek V4.1 Flash", free: true },
37
+ { id: "glm-5.3-flash", displayName: "GLM 5.3 Flash", free: true },
38
+ { id: "particle", displayName: "Particle · Free Unlimited", free: true },
39
+ ];
40
+
41
+ export const FREEPI_MODELS: readonly ModelDefinition[] = FREEPI_ROWS.map((row) =>
42
+ defineModel({
43
+ id: row.id,
44
+ providerId: FREEPI_PROVIDER_ID,
45
+ wireFamily: "chat",
46
+ endpoint: FREEPI_CHAT_PATH,
47
+ ctx: FREEPI_CONTEXT_WINDOW,
48
+ out: FREEPI_MAX_OUTPUT_TOKENS,
49
+ // No vision/audio/document claims: the upstream `/client-version` catalog
50
+ // publishes only id/name/section/tier, and the live sweep verified text
51
+ // completions but no other modality. Everything streamed reasoning
52
+ // content, and the agent wire carries tools, so reasoning + toolCall are
53
+ // the only declared capabilities.
54
+ vision: false,
55
+ reasoning: true,
56
+ toolCall: true,
57
+ ...(row.free ? { free: true, freeTier: true } : {}),
58
+ ...(row.proOnly ? { proOnly: true } : {}),
59
+ }),
60
+ );
@@ -0,0 +1,82 @@
1
+ // Free-pi live model discovery.
2
+ //
3
+ // `GET /client-version` (Bearer) returns the full model catalog:
4
+ // `models[]` with `{id, name, section?, tier?}` plus `fusion`, `model`, and
5
+ // `notice`. Discovery parses that into `ModelDefinition` rows. The static
6
+ // roster remains the fallback when the live call fails (mirrors the Trae
7
+ // pattern).
8
+ //
9
+ // Normalization:
10
+ // - The CLI's default id is `deepseek/deepseek-v4-flash`, while the catalog
11
+ // lists the bare `deepseek-v4-flash`. They are one model; discovery maps
12
+ // the bare id onto the default id so a single row survives (no duplicate,
13
+ // no ambiguous pair inside the provider).
14
+ // - Every discovered row is kept as-is (no free/pro filtering); the static
15
+ // roster is the fallback and carries only the free rows.
16
+ // - No vision/audio/document claims: the catalog publishes only
17
+ // id/name/section/tier, and the live sweep verified text completions only.
18
+
19
+ import { defineModel, type ModelDefinition } from "../../provider-model-definition";
20
+ import {
21
+ FREEPI_CHAT_PATH,
22
+ FREEPI_CLI_VERSION,
23
+ FREEPI_CONTEXT_WINDOW,
24
+ FREEPI_DEFAULT_MODEL,
25
+ FREEPI_MAX_OUTPUT_TOKENS,
26
+ FREEPI_PROVIDER_ID,
27
+ } from "./freepi-realm";
28
+
29
+ /** Catalog id → canonical roster id for one model. */
30
+ const CATALOG_ID_REMAP: Readonly<Record<string, string>> = {
31
+ "deepseek-v4-flash": FREEPI_DEFAULT_MODEL,
32
+ };
33
+
34
+ /** Fetches the live model catalog. */
35
+ export async function discoverFreepiModels(input: {
36
+ readonly token: string;
37
+ readonly fetcher: typeof fetch;
38
+ readonly signal?: AbortSignal;
39
+ readonly providerId?: string;
40
+ }): Promise<readonly ModelDefinition[]> {
41
+ const url = new URL("/client-version", "https://api.freepi.ai");
42
+ const response = await input.fetcher(url, {
43
+ method: "GET",
44
+ headers: {
45
+ authorization: `Bearer ${input.token}`,
46
+ accept: "application/json",
47
+ "x-client-version": FREEPI_CLI_VERSION,
48
+ },
49
+ ...(input.signal ? { signal: input.signal } : {}),
50
+ });
51
+ if (!response.ok) return [];
52
+ let parsed: Record<string, unknown>;
53
+ try {
54
+ parsed = (await response.json()) as Record<string, unknown>;
55
+ } catch {
56
+ return [];
57
+ }
58
+ const rows = Array.isArray(parsed["models"]) ? (parsed["models"] as Record<string, unknown>[]) : [];
59
+ const seen = new Set<string>();
60
+ const out: ModelDefinition[] = [];
61
+ for (const row of rows) {
62
+ const rawId = String(row["id"] ?? "").trim();
63
+ if (!rawId) continue;
64
+ const id = CATALOG_ID_REMAP[rawId] ?? rawId;
65
+ if (seen.has(id)) continue;
66
+ seen.add(id);
67
+ out.push(
68
+ defineModel({
69
+ id,
70
+ providerId: input.providerId ?? FREEPI_PROVIDER_ID,
71
+ wireFamily: "chat",
72
+ endpoint: FREEPI_CHAT_PATH,
73
+ ctx: FREEPI_CONTEXT_WINDOW,
74
+ out: FREEPI_MAX_OUTPUT_TOKENS,
75
+ vision: false,
76
+ reasoning: true,
77
+ toolCall: true,
78
+ }),
79
+ );
80
+ }
81
+ return out;
82
+ }
@@ -0,0 +1,21 @@
1
+ // Free-pi request headers.
2
+ //
3
+ // The client sends `Authorization: Bearer <jwt>` plus `x-client-version` (the
4
+ // CLI version) on the chat wire, and uses `application/json`. There is no
5
+ // device fingerprint — the JWT is the whole identity.
6
+
7
+ import { FREEPI_CLI_VERSION } from "./freepi-realm";
8
+
9
+ /** Builds the header set for one free-pi request. */
10
+ export function buildFreepiHeaders(jwt: string, stream = false, sessionId?: string): Record<string, string> {
11
+ const headers: Record<string, string> = {
12
+ authorization: `Bearer ${jwt}`,
13
+ "content-type": "application/json",
14
+ accept: stream ? "text/event-stream" : "application/json",
15
+ "x-client-version": FREEPI_CLI_VERSION,
16
+ };
17
+ if (sessionId !== undefined && sessionId.length > 0) {
18
+ headers["x-session-id"] = sessionId;
19
+ }
20
+ return headers;
21
+ }
@@ -0,0 +1,142 @@
1
+ // Free-pi credential login (device flow) and refresh.
2
+ //
3
+ // The backend has no standard OAuth token endpoint; the device flow is:
4
+ //
5
+ // 1. POST /auth/github/device -> { session_id, user_code, verification_uri, interval }
6
+ // 2. POST /auth/consent -> { session_id, consent_version: "4" }
7
+ // 3. POST /auth/token (poll) -> pending / { token }
8
+ //
9
+ // The issued token is a JWT used as `Authorization: Bearer` on every data-plane
10
+ // call. There is no refresh token: the JWT is long-lived (the CLI just re-runs
11
+ // the flow). Pasting a raw JWT is supported via `importCredential`.
12
+
13
+ import { OAuthClient } from "../../authentication/oauth-client";
14
+ import { decodeJwtPayload } from "../../authentication/oauth-flow-store";
15
+ import type {
16
+ OAuthDeviceStartResult,
17
+ OAuthDevicePollResult,
18
+ OAuthDeviceFlowContext,
19
+ OAuthExchangeResult,
20
+ OAuthImportInput,
21
+ } from "../../authentication/oauth-flow-store";
22
+
23
+ const FREEPI_DEVICE_START_PATH = "/auth/github/device";
24
+ const FREEPI_CONSENT_PATH = "/auth/consent";
25
+ const FREEPI_TOKEN_PATH = "/auth/token";
26
+ const FREEPI_BASE_URL = "https://api.freepi.ai";
27
+ const FREEPI_CONSENT_VERSION = "4";
28
+
29
+ export class FreepiOAuthClient extends OAuthClient {
30
+ readonly supportsDeviceCode = true;
31
+ readonly supportsBrowserCode = false;
32
+ protected readonly providerLabel = "free-pi";
33
+ protected readonly clientId = "";
34
+ protected readonly tokenUrl = "";
35
+ protected readonly scopes = "";
36
+
37
+ async startDeviceAuth(): Promise<OAuthDeviceStartResult> {
38
+ const res = await this.fetchFn(new URL(FREEPI_DEVICE_START_PATH, FREEPI_BASE_URL), {
39
+ method: "POST",
40
+ headers: { "content-type": "application/json", accept: "application/json" },
41
+ body: JSON.stringify({}),
42
+ });
43
+ if (!res.ok) {
44
+ const text = await res.text().catch(() => "");
45
+ throw new Error(`free-pi device flow start failed: HTTP ${res.status} ${text.slice(0, 300)}`);
46
+ }
47
+ const body = (await res.json()) as Record<string, unknown>;
48
+ const sessionId = String(body["session_id"] ?? "");
49
+ // The upstream requires consent before the token poll answers: without it,
50
+ // `/auth/token` returns 403 `consent_required`. Submit it once here so
51
+ // the device flow is complete before the dashboard starts polling.
52
+ if (sessionId) {
53
+ try {
54
+ await this.fetchFn(new URL(FREEPI_CONSENT_PATH, FREEPI_BASE_URL), {
55
+ method: "POST",
56
+ headers: { "content-type": "application/json", accept: "application/json" },
57
+ body: JSON.stringify({ session_id: sessionId, consent_version: FREEPI_CONSENT_VERSION }),
58
+ });
59
+ } catch {
60
+ // Consent failure is non-fatal at start; the poll will surface it.
61
+ }
62
+ }
63
+ return {
64
+ verificationUri: String(body["verification_uri"] ?? ""),
65
+ userCode: String(body["user_code"] ?? ""),
66
+ deviceAuthId: sessionId,
67
+ intervalSeconds: Number(body["interval"] ?? 5),
68
+ expiresInSeconds: Number(body["expires_in"] ?? 900),
69
+ providerState: JSON.stringify({ session_id: sessionId }),
70
+ };
71
+ }
72
+
73
+ async pollDeviceAuth(deviceAuthId: string, _context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
74
+ let providerState: Record<string, unknown> = {};
75
+ try {
76
+ providerState = JSON.parse(_context?.providerState ?? "{}") as Record<string, unknown>;
77
+ } catch {
78
+ /* ignore */
79
+ }
80
+ const sessionId = String(providerState["session_id"] ?? deviceAuthId);
81
+ const res = await this.fetchFn(new URL(FREEPI_TOKEN_PATH, FREEPI_BASE_URL), {
82
+ method: "POST",
83
+ headers: { "content-type": "application/json", accept: "application/json" },
84
+ body: JSON.stringify({ session_id: sessionId }),
85
+ });
86
+ if (!res.ok) {
87
+ return { status: "failed", reason: `token poll failed: HTTP ${res.status}` };
88
+ }
89
+ const body = (await res.json()) as Record<string, unknown>;
90
+ if (typeof body["token"] === "string" && (body["token"] as string).length > 0) {
91
+ const token = body["token"] as string;
92
+ // The free-pi JWT carries the GitHub user identity (`sub` = GitHub user
93
+ // id, `email` when the account has one). Resolving it keeps one account
94
+ // per GitHub user — without it every device login minted the same
95
+ // generic label and the second account replaced the first (the identity
96
+ // fingerprint collided on the static label).
97
+ const claims = decodeJwtPayload(token);
98
+ const userId = typeof claims?.["sub"] === "string" ? claims["sub"] : undefined;
99
+ const email = typeof claims?.["email"] === "string" ? (claims["email"] as string).trim() : "";
100
+ const label = email.length > 0 ? email : userId ? `freepi-${userId}` : "freepi-device";
101
+ return {
102
+ status: "complete",
103
+ result: {
104
+ access: token,
105
+ refresh: "",
106
+ expiresAt: new Date(Date.now() + 30 * 24 * 3600 * 1000),
107
+ accountLabel: label,
108
+ // The upstream identity the token resolved, stored beside the token
109
+ // so a later login of the same GitHub user lands on the same row
110
+ // (and a *different* user on a different row).
111
+ ...(userId || email
112
+ ? { auth_state: { ...(userId ? { freepi_user_id: userId } : {}), ...(email ? { email } : {}) } }
113
+ : {}),
114
+ },
115
+ };
116
+ }
117
+ if (body["code"] === "consent_required") {
118
+ return { status: "failed", reason: "consent_required" };
119
+ }
120
+ return { status: "pending" };
121
+ }
122
+
123
+ async importCredential(input: OAuthImportInput): Promise<OAuthExchangeResult> {
124
+ const token = input.credential.trim();
125
+ if (!token) throw new Error("free-pi: a JWT credential is required");
126
+ const claims = decodeJwtPayload(token);
127
+ const userId = typeof claims?.["sub"] === "string" ? claims["sub"] : undefined;
128
+ const email = typeof claims?.["email"] === "string" ? (claims["email"] as string).trim() : "";
129
+ const label = email.length > 0 ? email : userId ? `freepi-${userId}` : input.accountLabel ?? "freepi-jwt";
130
+ return {
131
+ access: token,
132
+ refresh: "",
133
+ expiresAt: new Date(Date.now() + 30 * 24 * 3600 * 1000),
134
+ accountLabel: label,
135
+ ...(userId || email
136
+ ? { auth_state: { ...(userId ? { freepi_user_id: userId } : {}), ...(email ? { email } : {}) } }
137
+ : {}),
138
+ };
139
+ }
140
+ }
141
+
142
+ export const freepiOAuthClient = new FreepiOAuthClient();
@@ -0,0 +1,242 @@
1
+ // Free-pi wire protocol: request body and SSE translation.
2
+ //
3
+ // The wire is OpenAI-completions (`/v1/chat/completions`) with a JWT bearer.
4
+ // Streaming returns OpenAI-style SSE: `data:` JSON chunks (no `event:` name),
5
+ // terminated by `[DONE]`. Errors come as HTTP status + JSON
6
+ // `{code, message, error: {code, message}}`.
7
+ //
8
+ // The client (0.6.14) sends `model` (the catalog id), `messages`, `stream`,
9
+ // and optionally `max_tokens`; the upstream resolves the catalog id to the
10
+ // real model and echoes it in each chunk (`choices[0].delta.model`).
11
+
12
+ import type { CanonicalRequest, ContentPart } from "../../../protocol/canonical-model";
13
+ import { GatewayError } from "../../../protocol/gateway-error";
14
+
15
+ /** Upstream chat body (OpenAI-completions shape). */
16
+ export interface FreepiChatBody {
17
+ readonly model: string;
18
+ readonly messages: readonly FreepiMessage[];
19
+ readonly stream: boolean;
20
+ readonly max_tokens?: number;
21
+ readonly temperature?: number;
22
+ readonly top_p?: number;
23
+ readonly tools?: readonly FreepiToolDeclaration[];
24
+ }
25
+
26
+ export interface FreepiMessage {
27
+ readonly role: "system" | "user" | "assistant" | "tool";
28
+ readonly content: string;
29
+ readonly tool_call_id?: string;
30
+ readonly tool_calls?: readonly FreepiToolCall[];
31
+ }
32
+
33
+ export interface FreepiToolDeclaration {
34
+ readonly type: "function";
35
+ readonly function: {
36
+ readonly name: string;
37
+ readonly description?: string;
38
+ readonly parameters: unknown;
39
+ };
40
+ }
41
+
42
+ export interface FreepiToolCall {
43
+ readonly id: string;
44
+ readonly type: "function";
45
+ readonly function: { readonly name: string; readonly arguments: string };
46
+ }
47
+
48
+ function textOf(part: ContentPart): string {
49
+ return part.kind === "text" ? part.text : "";
50
+ }
51
+
52
+ function toolArguments(args: unknown): string {
53
+ if (typeof args === "string") return args;
54
+ try {
55
+ return JSON.stringify(args ?? {});
56
+ } catch {
57
+ return "{}";
58
+ }
59
+ }
60
+
61
+ function toolResultText(content: readonly ContentPart[] | string): string {
62
+ if (typeof content === "string") return content;
63
+ return content
64
+ .map((part) => (part.kind === "text" ? part.text : ""))
65
+ .filter((text) => text.length > 0)
66
+ .join("\n");
67
+ }
68
+
69
+ /** Builds the upstream chat body from a canonical request. */
70
+ export function buildFreepiChatBody(
71
+ modelId: string,
72
+ request: CanonicalRequest,
73
+ maxOutputTokens?: number,
74
+ ): FreepiChatBody {
75
+ const messages: FreepiMessage[] = [];
76
+ const systemParts: string[] = [];
77
+ for (const part of request.system ?? []) {
78
+ const text = textOf(part);
79
+ if (text.length > 0) systemParts.push(text);
80
+ }
81
+ for (const part of request.instructions ?? []) {
82
+ const text = textOf(part);
83
+ if (text.length > 0) systemParts.push(text);
84
+ }
85
+ if (systemParts.length > 0) {
86
+ messages.push({ role: "system", content: systemParts.join("\n\n") });
87
+ }
88
+
89
+ for (const message of request.messages) {
90
+ if (message.role === "system" || message.role === "developer") {
91
+ const text = message.content.map(textOf).filter((t) => t.length > 0).join("\n\n");
92
+ if (text.length > 0) systemParts.push(text);
93
+ continue;
94
+ }
95
+ const parts: string[] = [];
96
+ const toolCalls: FreepiToolCall[] = [];
97
+ let toolResult: { callId: string; text: string } | undefined;
98
+ for (const part of message.content) {
99
+ if (part.kind === "toolCall") {
100
+ toolCalls.push({
101
+ id: part.call_id,
102
+ type: "function",
103
+ function: { name: part.name, arguments: toolArguments(part.arguments) },
104
+ });
105
+ continue;
106
+ }
107
+ if (part.kind === "toolResult") {
108
+ toolResult = { callId: part.call_id, text: toolResultText(part.content) };
109
+ continue;
110
+ }
111
+ const text = textOf(part);
112
+ if (text.length > 0) parts.push(text);
113
+ }
114
+ if (toolResult !== undefined) {
115
+ messages.push({ role: "tool", content: toolResult.text, tool_call_id: toolResult.callId });
116
+ continue;
117
+ }
118
+ if (parts.length === 0 && toolCalls.length === 0) continue;
119
+ messages.push({
120
+ role: message.role === "assistant" ? "assistant" : "user",
121
+ content: parts.join("\n"),
122
+ ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
123
+ });
124
+ }
125
+
126
+ const body: FreepiChatBody = {
127
+ model: modelId,
128
+ messages,
129
+ stream: request.stream,
130
+ };
131
+ const extra: Record<string, unknown> = {};
132
+ if (maxOutputTokens !== undefined && Number.isFinite(maxOutputTokens) && maxOutputTokens > 0) {
133
+ extra["max_tokens"] = Math.min(maxOutputTokens, 8192);
134
+ }
135
+ const controls = request.generation_controls;
136
+ if (typeof controls.temperature === "number" && Number.isFinite(controls.temperature)) {
137
+ extra["temperature"] = controls.temperature;
138
+ }
139
+ if (typeof controls.top_p === "number" && Number.isFinite(controls.top_p)) {
140
+ extra["top_p"] = controls.top_p;
141
+ }
142
+ const tools = request.tools;
143
+ if (tools !== undefined && tools.length > 0) {
144
+ extra["tools"] = tools.map((tool) => ({
145
+ type: "function",
146
+ function: {
147
+ name: tool.name,
148
+ ...(tool.description ? { description: tool.description } : {}),
149
+ parameters: tool.jsonSchema,
150
+ },
151
+ }));
152
+ }
153
+ return { ...body, ...extra } as FreepiChatBody;
154
+ }
155
+
156
+ /** One parsed SSE frame. */
157
+ export interface FreepiSseFrame {
158
+ readonly event: string;
159
+ readonly data: unknown;
160
+ }
161
+
162
+ /** Parses an SSE byte stream into frames (OpenAI-style, `data:` only). */
163
+ export async function* parseFreepiSse(
164
+ body: ReadableStream<Uint8Array>,
165
+ ): AsyncIterable<FreepiSseFrame> {
166
+ const decoder = new TextDecoder();
167
+ const reader = body.getReader();
168
+ let buffer = "";
169
+ let data: string[] = [];
170
+ try {
171
+ while (true) {
172
+ const { done, value } = await reader.read();
173
+ if (done) break;
174
+ buffer += decoder.decode(value, { stream: true });
175
+ let newline = buffer.indexOf("\n");
176
+ while (newline >= 0) {
177
+ const line = buffer.slice(0, newline).replace(/\r$/, "");
178
+ buffer = buffer.slice(newline + 1);
179
+ if (line === "") {
180
+ if (data.length > 0) {
181
+ const payload = data.join("\n");
182
+ data = [];
183
+ if (payload.trim() === "[DONE]") {
184
+ yield { event: "done", data: { finish_reason: "stop" } };
185
+ } else {
186
+ let parsed: unknown = payload;
187
+ try {
188
+ parsed = JSON.parse(payload);
189
+ } catch {
190
+ parsed = payload;
191
+ }
192
+ yield { event: "message", data: parsed };
193
+ }
194
+ }
195
+ } else if (line.startsWith(":")) {
196
+ continue;
197
+ } else if (line.startsWith("data")) {
198
+ let value = line.slice(4);
199
+ if (value.startsWith(" ")) value = value.slice(1);
200
+ data.push(value);
201
+ }
202
+ newline = buffer.indexOf("\n");
203
+ }
204
+ }
205
+ if (data.length > 0) {
206
+ const payload = data.join("\n");
207
+ if (payload.trim() === "[DONE]") {
208
+ yield { event: "done", data: { finish_reason: "stop" } };
209
+ } else {
210
+ let parsed: unknown = payload;
211
+ try {
212
+ parsed = JSON.parse(payload);
213
+ } catch {
214
+ parsed = payload;
215
+ }
216
+ yield { event: "message", data: parsed };
217
+ }
218
+ }
219
+ } finally {
220
+ reader.releaseLock();
221
+ }
222
+ }
223
+
224
+ /** Maps upstream error JSON (code/message/error nested) to a GatewayError. */
225
+ export function throwFreepiUpstreamError(code: unknown, message: unknown): never {
226
+ const msg = typeof message === "string" && message.trim() ? message : "free-pi upstream error";
227
+ const codeStr = code === undefined ? undefined : String(code);
228
+ // Entitlement/plan walls the gateway classifies as policy rejection (403):
229
+ // the account is not allowed to use this model/credential type, which is
230
+ // not a transient rate limit (quota_exceeded = 429).
231
+ if (codeStr === "guest_unavailable" || codeStr === "model_requires_credits") {
232
+ throw new GatewayError("policy_rejected", 403, `${msg} (request {requestId}). The free-pi account cannot serve this — check credits or sign in. 💸`, { upstream_code: codeStr }, "upstream");
233
+ }
234
+ // Server-side model unavailability → platform unavailable (503).
235
+ if (codeStr === "model_at_capacity" || codeStr === "model_unavailable") {
236
+ throw new GatewayError("platform_unavailable", 503, `${msg} (request {requestId}). Upstream has this model hard-down right now. 🔄`, { upstream_code: codeStr }, "upstream");
237
+ }
238
+ if (codeStr === "upstream_error") {
239
+ throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The upstream model host errored. 🌙`, { upstream_code: codeStr }, "upstream");
240
+ }
241
+ throw new GatewayError("platform_unavailable", 502, `${msg} (request {requestId}). The free-pi upstream refused the request. 🌙`, { upstream_code: codeStr }, "upstream");
242
+ }
@@ -0,0 +1,126 @@
1
+ // Free-pi quota collector.
2
+ //
3
+ // Reads `GET /me` (Bearer) — the backend's account/allowance surface. The
4
+ // response carries the USD budget (`cap_usd_today`, `remaining_usd_today`),
5
+ // credit counts (`allowance_credits`, `remaining_credits_today`,
6
+ // `bonus_wallet_credits`, `premium_credits`, …), a `refill_at` reset time, and
7
+ // a usage lane (`standard`/`fallback`/`wait`). Never throws: a failed read
8
+ // returns a null-result so the router treats it as unknown rather than dead.
9
+
10
+ import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
11
+ import { cleanError, percentWindow, text } from "../../quota/quota-contracts";
12
+ import type { QuotaCollectionContext } from "../../quota/quota-support";
13
+ import { buildFreepiHeaders } from "./freepi-headers";
14
+ import { FREEPI_BASE_URL } from "./freepi-realm";
15
+
16
+ function readMe(baseUrl: string, credential: string, fetcher: FetchLike): Promise<Record<string, unknown> | null> {
17
+ return fetcher(new URL("/me", baseUrl), {
18
+ method: "GET",
19
+ headers: buildFreepiHeaders(credential),
20
+ signal: AbortSignal.timeout(15_000),
21
+ })
22
+ .then(async (res) => {
23
+ if (!res.ok) return null;
24
+ try {
25
+ return (await res.json()) as Record<string, unknown>;
26
+ } catch {
27
+ return null;
28
+ }
29
+ })
30
+ .catch(() => null);
31
+ }
32
+
33
+ /** Reads the account quota and returns a normalized result. */
34
+ export async function fetchFreepiQuota(
35
+ credential: string,
36
+ fetcher: FetchLike,
37
+ _context?: QuotaCollectionContext,
38
+ ): Promise<ProviderQuotaResult> {
39
+ try {
40
+ const me = await readMe(FREEPI_BASE_URL, credential, fetcher);
41
+ if (!me) {
42
+ return {
43
+ source: "freepi",
44
+ plan: null,
45
+ windows: [],
46
+ error: "free-pi /me unavailable",
47
+ };
48
+ }
49
+ const plan = me["tier"] === undefined ? null : text(me["tier"]);
50
+ const windows: ProviderQuotaWindow[] = [];
51
+
52
+ const capUsd = me["cap_usd_today"];
53
+ const remainingUsd = me["remaining_usd_today"];
54
+ const refillAt = me["refill_at"];
55
+ if (typeof capUsd === "number" && typeof remainingUsd === "number") {
56
+ const limit = capUsd as number;
57
+ const remaining = remainingUsd as number;
58
+ windows.push(
59
+ percentWindow(
60
+ "usd_daily",
61
+ "Daily USD budget",
62
+ remaining,
63
+ refillAt === undefined ? null : text(refillAt),
64
+ limit - remaining,
65
+ limit,
66
+ "pooled",
67
+ ),
68
+ );
69
+ }
70
+
71
+ const allowance = me["allowance_credits"];
72
+ const remainingCredits = me["remaining_credits_today"];
73
+ if (typeof allowance === "number" && typeof remainingCredits === "number") {
74
+ const limit = allowance as number;
75
+ const remaining = remainingCredits as number;
76
+ windows.push(
77
+ percentWindow(
78
+ "credits_daily",
79
+ "Daily credits",
80
+ remaining,
81
+ refillAt === undefined ? null : text(refillAt),
82
+ limit - remaining,
83
+ limit,
84
+ "pooled",
85
+ ),
86
+ );
87
+ }
88
+
89
+ const bonus = me["bonus_wallet_credits"];
90
+ if (typeof bonus === "number") {
91
+ windows.push({
92
+ kind: "bonus_credits",
93
+ label: "Bonus credits",
94
+ accounting: "pooled",
95
+ usedPercent: null,
96
+ remainingPercent: null,
97
+ resetsAt: null,
98
+ remaining: bonus as number,
99
+ });
100
+ }
101
+
102
+ const premium = me["premium_credits"];
103
+ if (typeof premium === "number") {
104
+ windows.push({
105
+ kind: "premium_credits",
106
+ label: "Premium credits",
107
+ accounting: "pooled",
108
+ usedPercent: null,
109
+ remainingPercent: null,
110
+ resetsAt: null,
111
+ remaining: premium as number,
112
+ });
113
+ }
114
+
115
+ const handle = me["handle"];
116
+ return {
117
+ source: "freepi",
118
+ plan,
119
+ windows,
120
+ error: null,
121
+ ...(typeof handle === "string" && handle.trim() ? { accountLabel: handle } : {}),
122
+ };
123
+ } catch (error) {
124
+ return { source: "freepi", plan: null, windows: [], error: cleanError(error) };
125
+ }
126
+ }