@fudrouter/fsrouter 0.6.72 → 0.6.74

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 (314) hide show
  1. package/dist/lib/headroom/detect.js +97 -7
  2. package/dist/lib/headroom/process.js +136 -4
  3. package/dist/open-sse/config/appConstants.js +23 -79
  4. package/dist/open-sse/config/cliFingerprints.js +1 -1
  5. package/dist/open-sse/config/errorConfig.js +0 -11
  6. package/dist/open-sse/config/grokCli.js +10 -0
  7. package/dist/open-sse/config/kiroConstants.js +99 -46
  8. package/dist/open-sse/config/runtimeConfig.js +2 -0
  9. package/dist/open-sse/config/ttsModels.js +2 -0
  10. package/dist/open-sse/executors/antigravity.js +250 -164
  11. package/dist/open-sse/executors/base.js +16 -11
  12. package/dist/open-sse/executors/codex.js +269 -1108
  13. package/dist/open-sse/executors/commandCode.js +1 -1
  14. package/dist/open-sse/executors/commandcode.js +10 -4
  15. package/dist/open-sse/executors/cursor.js +96 -203
  16. package/dist/open-sse/executors/default.js +146 -244
  17. package/dist/open-sse/executors/github.js +128 -73
  18. package/dist/open-sse/executors/grok-cli.js +527 -94
  19. package/dist/open-sse/executors/grok-web.js +5 -7
  20. package/dist/open-sse/executors/index.js +12 -0
  21. package/dist/open-sse/executors/kiro.js +150 -61
  22. package/dist/open-sse/executors/opencode-go.js +12 -4
  23. package/dist/open-sse/executors/opencode.js +1 -1
  24. package/dist/open-sse/executors/perplexity-web.js +20 -96
  25. package/dist/open-sse/executors/qoder.js +28 -18
  26. package/dist/open-sse/executors/vertex.js +53 -7
  27. package/dist/open-sse/executors/xiaomi-tokenplan.js +6 -5
  28. package/dist/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
  29. package/dist/open-sse/handlers/chatCore/requestDetail.js +32 -8
  30. package/dist/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
  31. package/dist/open-sse/handlers/chatCore/streamingHandler.js +74 -25
  32. package/dist/open-sse/handlers/chatCore.js +35 -41
  33. package/dist/open-sse/handlers/embeddingProviders/index.js +1 -0
  34. package/dist/open-sse/handlers/embeddingProviders/openai.js +8 -16
  35. package/dist/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
  36. package/dist/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
  37. package/dist/open-sse/handlers/imageProviders/codex.js +2 -20
  38. package/dist/open-sse/handlers/imageProviders/comfyui.js +5 -18
  39. package/dist/open-sse/handlers/imageProviders/falAi.js +2 -22
  40. package/dist/open-sse/handlers/imageProviders/gemini.js +2 -16
  41. package/dist/open-sse/handlers/imageProviders/huggingface.js +2 -17
  42. package/dist/open-sse/handlers/imageProviders/index.js +3 -4
  43. package/dist/open-sse/handlers/imageProviders/leonardo.js +1 -1
  44. package/dist/open-sse/handlers/imageProviders/nanobanana.js +4 -23
  45. package/dist/open-sse/handlers/imageProviders/openai.js +15 -43
  46. package/dist/open-sse/handlers/imageProviders/runwayml.js +2 -26
  47. package/dist/open-sse/handlers/imageProviders/sdwebui.js +4 -20
  48. package/dist/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
  49. package/dist/open-sse/handlers/imageProviders/weavy.js +1 -1
  50. package/dist/open-sse/handlers/search/chatSearch.js +60 -14
  51. package/dist/open-sse/handlers/ttsProviders/gemini.js +14 -3
  52. package/dist/open-sse/handlers/ttsProviders/index.js +4 -2
  53. package/dist/open-sse/handlers/ttsProviders/openai.js +4 -1
  54. package/dist/open-sse/handlers/ttsProviders/openrouter.js +7 -4
  55. package/dist/open-sse/handlers/videoCore.js +166 -0
  56. package/dist/open-sse/handlers/videoProviders/weavy.js +1 -1
  57. package/dist/open-sse/mcp-server/catalog.js +1 -1
  58. package/dist/open-sse/mcp-server/server.js +2 -2
  59. package/dist/open-sse/mcp-server/tools/advancedTools.js +2 -2
  60. package/dist/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
  61. package/dist/open-sse/mcp-server/tools/compressionTools.js +4 -4
  62. package/dist/open-sse/mcp-server/tools/gamificationTools.js +10 -10
  63. package/dist/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
  64. package/dist/open-sse/mcp-server/tools/memoryTools.js +3 -3
  65. package/dist/open-sse/mcp-server/tools/notionTools.js +2 -2
  66. package/dist/open-sse/mcp-server/tools/obsidianTools.js +2 -2
  67. package/dist/open-sse/mcp-server/tools/pickFastestModel.js +1 -1
  68. package/dist/open-sse/mcp-server/tools/pluginTools.js +4 -4
  69. package/dist/open-sse/mcp-server/tools/skillTools.js +2 -2
  70. package/dist/open-sse/providers/capabilities.js +24 -4
  71. package/dist/open-sse/providers/registry/alicode-intl.js +1 -1
  72. package/dist/open-sse/providers/registry/anthropic.js +1 -8
  73. package/dist/open-sse/providers/registry/codex.js +0 -9
  74. package/dist/open-sse/providers/registry/cohere.js +0 -2
  75. package/dist/open-sse/providers/registry/gemini.js +0 -2
  76. package/dist/open-sse/providers/registry/github.js +9 -6
  77. package/dist/open-sse/providers/registry/grok-cli.js +28 -18
  78. package/dist/open-sse/providers/registry/index.js +74 -80
  79. package/dist/open-sse/providers/registry/kiro.js +12 -0
  80. package/dist/open-sse/providers/registry/mistral.js +0 -3
  81. package/dist/open-sse/providers/registry/openai.js +0 -3
  82. package/dist/open-sse/providers/registry/perplexity.js +0 -2
  83. package/dist/open-sse/providers/registry/xai.js +5 -3
  84. package/dist/open-sse/rtk/autodetect.js +10 -2
  85. package/dist/open-sse/rtk/caveman.js +2 -93
  86. package/dist/open-sse/rtk/cavemanPrompts.js +34 -2
  87. package/dist/open-sse/rtk/constants.js +7 -2
  88. package/dist/open-sse/rtk/headroom.js +133 -0
  89. package/dist/open-sse/rtk/ponytail.js +1 -1
  90. package/dist/open-sse/rtk/registry.js +2 -0
  91. package/dist/open-sse/services/accountFallback.js +0 -108
  92. package/dist/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
  93. package/dist/open-sse/services/combo/quotaShareStrategy.js +1 -1
  94. package/dist/open-sse/services/combo/resolveAutoStrategy.js +1 -1
  95. package/dist/open-sse/services/combo/sessionStickiness.js +1 -1
  96. package/dist/open-sse/services/combo/targetSorters.js +2 -2
  97. package/dist/open-sse/services/combo/validateQuality.js +1 -1
  98. package/dist/open-sse/services/comboConfig.js +1 -1
  99. package/dist/open-sse/services/emergencyFallback.js +1 -1
  100. package/dist/open-sse/services/grokCliModels.js +127 -0
  101. package/dist/open-sse/services/model.js +7 -1
  102. package/dist/open-sse/services/provider.js +1 -1
  103. package/dist/open-sse/services/proxyAutoSelector.js +1 -1
  104. package/dist/open-sse/services/tokenRefresh/providers.js +3 -3
  105. package/dist/open-sse/services/usage/codex.js +148 -214
  106. package/dist/open-sse/services/usage/grok-cli.js +61 -7
  107. package/dist/open-sse/services/usage/misc.js +0 -19
  108. package/dist/open-sse/services/usage.js +0 -2
  109. package/dist/open-sse/translator/concerns/paramSupport.js +2 -2
  110. package/dist/open-sse/translator/concerns/thinkingUnified.js +6 -0
  111. package/dist/open-sse/translator/index.js +97 -59
  112. package/dist/open-sse/translator/request/claude-to-kiro.js +67 -40
  113. package/dist/open-sse/translator/request/openai-responses.js +1 -0
  114. package/dist/open-sse/translator/request/openai-to-commandcode.js +0 -4
  115. package/dist/open-sse/translator/request/openai-to-kiro.js +49 -20
  116. package/dist/open-sse/translator/response/commandcode-to-openai.js +0 -4
  117. package/dist/open-sse/utils/bypassHandler.js +18 -3
  118. package/dist/open-sse/utils/claudeCloaking.js +15 -5
  119. package/dist/open-sse/utils/cursorImages.js +1 -1
  120. package/dist/open-sse/utils/error.js +0 -17
  121. package/dist/open-sse/utils/keepaliveThreshold.js +4 -4
  122. package/dist/open-sse/utils/kiroSessionReplay.js +125 -0
  123. package/dist/open-sse/utils/noThinkingAlias.js +1 -1
  124. package/dist/open-sse/utils/proxyFetch.js +0 -1
  125. package/dist/open-sse/utils/reasoningContentInjector.js +4 -7
  126. package/dist/open-sse/utils/responsesStatePolicy.js +1 -1
  127. package/dist/open-sse/utils/responsesStreamHelpers.js +1 -0
  128. package/dist/open-sse/utils/sessionManager.js +49 -22
  129. package/dist/open-sse/utils/stream.js +54 -77
  130. package/dist/open-sse/utils/streamHandler.js +19 -15
  131. package/dist/open-sse/utils/streamHelpers.js +2 -17
  132. package/dist/open-sse/utils/usageTracking.js +103 -12
  133. package/dist/routes/headroom/extras/route.js +58 -0
  134. package/dist/routes/headroom/restart/route.js +33 -0
  135. package/open-sse/config/appConstants.js +23 -79
  136. package/open-sse/config/errorConfig.js +0 -11
  137. package/open-sse/config/grokCli.js +10 -0
  138. package/open-sse/config/kiroConstants.js +99 -46
  139. package/open-sse/config/runtimeConfig.js +2 -0
  140. package/open-sse/config/ttsModels.js +2 -0
  141. package/open-sse/executors/antigravity.js +250 -164
  142. package/open-sse/executors/base.js +16 -11
  143. package/open-sse/executors/codex.js +269 -1108
  144. package/open-sse/executors/commandcode.js +10 -4
  145. package/open-sse/executors/cursor.js +96 -203
  146. package/open-sse/executors/default.js +146 -244
  147. package/open-sse/executors/github.js +128 -73
  148. package/open-sse/executors/grok-cli.js +527 -94
  149. package/open-sse/executors/grok-web.js +5 -7
  150. package/open-sse/executors/index.js +12 -0
  151. package/open-sse/executors/kiro.js +150 -61
  152. package/open-sse/executors/opencode-go.js +12 -4
  153. package/open-sse/executors/opencode.js +1 -1
  154. package/open-sse/executors/perplexity-web.js +20 -96
  155. package/open-sse/executors/qoder.js +28 -18
  156. package/open-sse/executors/vertex.js +53 -7
  157. package/open-sse/executors/xiaomi-tokenplan.js +6 -5
  158. package/open-sse/handlers/chatCore/nonStreamingHandler.js +97 -85
  159. package/open-sse/handlers/chatCore/requestDetail.js +32 -8
  160. package/open-sse/handlers/chatCore/sseToJsonHandler.js +13 -7
  161. package/open-sse/handlers/chatCore/streamingHandler.js +74 -25
  162. package/open-sse/handlers/chatCore.js +35 -41
  163. package/open-sse/handlers/embeddingProviders/index.js +1 -0
  164. package/open-sse/handlers/embeddingProviders/openai.js +8 -16
  165. package/open-sse/handlers/imageProviders/blackForestLabs.js +2 -21
  166. package/open-sse/handlers/imageProviders/cloudflareAi.js +2 -30
  167. package/open-sse/handlers/imageProviders/codex.js +2 -20
  168. package/open-sse/handlers/imageProviders/comfyui.js +5 -18
  169. package/open-sse/handlers/imageProviders/falAi.js +2 -22
  170. package/open-sse/handlers/imageProviders/gemini.js +2 -16
  171. package/open-sse/handlers/imageProviders/huggingface.js +2 -17
  172. package/open-sse/handlers/imageProviders/index.js +3 -4
  173. package/open-sse/handlers/imageProviders/leonardo.js +1 -1
  174. package/open-sse/handlers/imageProviders/nanobanana.js +4 -23
  175. package/open-sse/handlers/imageProviders/openai.js +15 -43
  176. package/open-sse/handlers/imageProviders/runwayml.js +2 -26
  177. package/open-sse/handlers/imageProviders/sdwebui.js +4 -20
  178. package/open-sse/handlers/imageProviders/stabilityAi.js +2 -26
  179. package/open-sse/handlers/imageProviders/weavy.js +1 -1
  180. package/open-sse/handlers/search/chatSearch.js +60 -14
  181. package/open-sse/handlers/ttsProviders/gemini.js +14 -3
  182. package/open-sse/handlers/ttsProviders/index.js +4 -2
  183. package/open-sse/handlers/ttsProviders/openai.js +4 -1
  184. package/open-sse/handlers/ttsProviders/openrouter.js +7 -4
  185. package/open-sse/handlers/videoCore.js +166 -0
  186. package/open-sse/handlers/videoProviders/weavy.js +1 -1
  187. package/open-sse/mcp-server/audit.js +1 -1
  188. package/open-sse/mcp-server/catalog.js +2 -2
  189. package/open-sse/mcp-server/descriptionCompressor.js +1 -1
  190. package/open-sse/mcp-server/mcpCallerIdentity.js +1 -1
  191. package/open-sse/mcp-server/schemas/tools.js +1 -1
  192. package/open-sse/mcp-server/server.js +4 -4
  193. package/open-sse/mcp-server/tools/advancedTools.js +5 -5
  194. package/open-sse/mcp-server/tools/agentSkillTools.js +1 -1
  195. package/open-sse/mcp-server/tools/compressionTools.js +4 -4
  196. package/open-sse/mcp-server/tools/gamificationTools.js +10 -10
  197. package/open-sse/mcp-server/tools/githubSkillTools.js +1 -1
  198. package/open-sse/mcp-server/tools/memoryTools.js +3 -3
  199. package/open-sse/mcp-server/tools/notionTools.js +2 -2
  200. package/open-sse/mcp-server/tools/obsidianTools.js +2 -2
  201. package/open-sse/mcp-server/tools/pickFastestModel.js +3 -3
  202. package/open-sse/mcp-server/tools/pluginTools.js +4 -4
  203. package/open-sse/mcp-server/tools/skillTools.js +2 -2
  204. package/open-sse/providers/capabilities.js +24 -4
  205. package/open-sse/providers/registry/alicode-intl.js +1 -1
  206. package/open-sse/providers/registry/anthropic.js +1 -8
  207. package/open-sse/providers/registry/codex.js +0 -9
  208. package/open-sse/providers/registry/cohere.js +0 -2
  209. package/open-sse/providers/registry/gemini.js +0 -2
  210. package/open-sse/providers/registry/github.js +9 -6
  211. package/open-sse/providers/registry/grok-cli.js +28 -18
  212. package/open-sse/providers/registry/index.js +74 -80
  213. package/open-sse/providers/registry/kiro.js +12 -0
  214. package/open-sse/providers/registry/mistral.js +0 -3
  215. package/open-sse/providers/registry/openai.js +0 -3
  216. package/open-sse/providers/registry/perplexity.js +0 -2
  217. package/open-sse/providers/registry/xai.js +5 -3
  218. package/open-sse/rtk/autodetect.js +10 -2
  219. package/open-sse/rtk/caveman.js +2 -93
  220. package/open-sse/rtk/cavemanPrompts.js +34 -2
  221. package/open-sse/rtk/constants.js +7 -2
  222. package/open-sse/rtk/headroom.js +133 -0
  223. package/open-sse/rtk/ponytail.js +1 -1
  224. package/open-sse/rtk/registry.js +2 -0
  225. package/open-sse/services/accountFallback.js +0 -108
  226. package/open-sse/services/combo/quotaExhaustionCutoff.js +2 -2
  227. package/open-sse/services/combo/quotaShareStrategy.js +1 -1
  228. package/open-sse/services/combo/resolveAutoStrategy.js +1 -1
  229. package/open-sse/services/combo/sessionStickiness.js +1 -1
  230. package/open-sse/services/combo/shadowRouting.js +1 -1
  231. package/open-sse/services/combo/targetSorters.js +4 -4
  232. package/open-sse/services/combo/validateQuality.js +1 -1
  233. package/open-sse/services/evalRouting.js +1 -1
  234. package/open-sse/services/grokCliModels.js +127 -0
  235. package/open-sse/services/model.js +7 -1
  236. package/open-sse/services/modelCapabilities.js +1 -1
  237. package/open-sse/services/modelFamilyFallback.js +1 -1
  238. package/open-sse/services/provider.js +1 -1
  239. package/open-sse/services/providerCooldownTracker.js +1 -1
  240. package/open-sse/services/rateLimitManager.js +1 -1
  241. package/open-sse/services/tokenRefresh/providers.js +3 -3
  242. package/open-sse/services/usage/codex.js +148 -214
  243. package/open-sse/services/usage/grok-cli.js +61 -7
  244. package/open-sse/services/usage/misc.js +0 -19
  245. package/open-sse/services/usage.js +0 -2
  246. package/open-sse/translator/concerns/paramSupport.js +2 -2
  247. package/open-sse/translator/concerns/thinkingUnified.js +6 -0
  248. package/open-sse/translator/index.js +97 -59
  249. package/open-sse/translator/request/claude-to-kiro.js +67 -40
  250. package/open-sse/translator/request/openai-responses.js +1 -0
  251. package/open-sse/translator/request/openai-to-commandcode.js +0 -4
  252. package/open-sse/translator/request/openai-to-kiro.js +49 -20
  253. package/open-sse/translator/response/commandcode-to-openai.js +0 -4
  254. package/open-sse/utils/bypassHandler.js +18 -3
  255. package/open-sse/utils/claudeCloaking.js +15 -5
  256. package/open-sse/utils/error.js +0 -17
  257. package/open-sse/utils/kiroSessionReplay.js +125 -0
  258. package/open-sse/utils/logger.js +1 -1
  259. package/open-sse/utils/proxyFetch.js +0 -1
  260. package/open-sse/utils/reasoningContentInjector.js +4 -7
  261. package/open-sse/utils/responsesStreamHelpers.js +1 -0
  262. package/open-sse/utils/sessionManager.js +49 -22
  263. package/open-sse/utils/stream.js +54 -77
  264. package/open-sse/utils/streamHandler.js +19 -15
  265. package/open-sse/utils/streamHelpers.js +2 -17
  266. package/open-sse/utils/usageTracking.js +103 -12
  267. package/package.json +4 -7
  268. package/public/assets/EndpointPageClient.C2WMTYy0.js +6 -0
  269. package/public/assets/{Loading.Bj-WGl4k.js → Loading.BHPtQ9vj.js} +1 -1
  270. package/public/assets/{NoAuthProxyCard.qAv57b-0.js → NoAuthProxyCard.BDcXaWvM.js} +1 -1
  271. package/public/assets/index.Be3gbrBl.js +89 -0
  272. package/public/assets/index.COMgQZYj.css +1 -0
  273. package/public/assets/{page.CbkENBd5.js → page.-lpNgYAw.js} +1 -1
  274. package/public/assets/{page.CssmrzTT.js → page.11hpNKmq.js} +1 -1
  275. package/public/assets/{page.C72FTTJr.js → page.B-LxsnqU.js} +1 -1
  276. package/public/assets/{page.sOFOrxqq.js → page.B-dzeIUC.js} +1 -1
  277. package/public/assets/{page.CsxWmPoI.js → page.B-nBR75u.js} +1 -1
  278. package/public/assets/{page.Dx_GRRdT.js → page.B3QqvLep.js} +1 -1
  279. package/public/assets/{page.DPyOkPVo.js → page.B8xdkCWA.js} +1 -1
  280. package/public/assets/{page.DUI8kqOY.js → page.BFb7yUd8.js} +1 -1
  281. package/public/assets/page.BbPPdoas.js +1 -0
  282. package/public/assets/page.BdS20a1O.js +1 -0
  283. package/public/assets/{page.DibhF7f-.js → page.BfmI9KYe.js} +1 -1
  284. package/public/assets/{page.DX4Nno6M.js → page.BgGJovOg.js} +1 -1
  285. package/public/assets/{page.BnmTC5jG.js → page.C0gBvR4O.js} +1 -1
  286. package/public/assets/{page.D5gj98Oe.js → page.C6bYizL7.js} +1 -1
  287. package/public/assets/{page.vla5Mzki.js → page.C8sLEMa6.js} +1 -1
  288. package/public/assets/{page.CXd_R9yU.js → page.CCNsZBXp.js} +1 -1
  289. package/public/assets/{page.CQlVGoXg.js → page.CF_YSLwP.js} +1 -1
  290. package/public/assets/{page.BbO1pspY.js → page.CPTtRDxw.js} +1 -1
  291. package/public/assets/{page.D1oKew1j.js → page.CdMmlUEm.js} +1 -1
  292. package/public/assets/{page.DxMDTbSx.js → page.Cged1bOZ.js} +1 -1
  293. package/public/assets/{page.Cj5h09Zo.js → page.Ch647dVk.js} +1 -1
  294. package/public/assets/{page.DVIHRbxB.js → page.CpvXDkQF.js} +1 -1
  295. package/public/assets/{page.i4KDuWcM.js → page.CrBl7f8N.js} +1 -1
  296. package/public/assets/{page.BSLJ4tx5.js → page.CrP7niA2.js} +1 -1
  297. package/public/assets/{page.CnTop8Zt.js → page.CvUFZzXr.js} +1 -1
  298. package/public/assets/{page.O2ddJYoT.js → page.CyUqJPmx.js} +1 -1
  299. package/public/assets/{page.DBhu4Arl.js → page.D8Rp8fFR.js} +1 -1
  300. package/public/assets/{page.CPZav1_c.js → page.Df9qtrdG.js} +1 -1
  301. package/public/assets/{page.Qkkv8roc.js → page.DiL4Gwqe.js} +1 -1
  302. package/public/assets/{page.BdmJ3yY3.js → page.Dl1apZE2.js} +1 -1
  303. package/public/assets/{page.1qjqvrwX.js → page.HV0w1d5C.js} +1 -1
  304. package/public/assets/{page.ChX1NqmC.js → page.N1x4YL8_.js} +1 -1
  305. package/public/assets/{page.Dh5f0XJC.js → page.P2Ny5veR.js} +1 -1
  306. package/public/assets/{page.CSs3-gdt.js → page.fOOCr18J.js} +1 -1
  307. package/public/assets/{page.Ir7KvKcF.js → page.sUIPWIiF.js} +1 -1
  308. package/public/assets/{page.CzlQTyAL.js → page.tE7PpWYW.js} +1 -1
  309. package/public/assets/{page.DzZLqI5Q.js → page.uziaGZsT.js} +1 -1
  310. package/public/index.html +2 -2
  311. package/public/assets/EndpointPageClient.LyNNvZ4z.js +0 -6
  312. package/public/assets/index.BuPfZebu.css +0 -1
  313. package/public/assets/index.CShjRV_B.js +0 -89
  314. package/public/assets/page.DD_czjjP.js +0 -1
@@ -1,6 +1,7 @@
1
1
  import { BaseExecutor } from "./base.js";
2
2
  import { PROVIDERS } from "../config/providers.js";
3
- import { proxyAwareFetch } from "../utils/proxyFetch.js";
3
+ import { SSE_DONE, SSE_HEADERS_NO_BUFFER } from "../utils/sseConstants.js";
4
+ import { sseChunk } from "../utils/sse.js";
4
5
 
5
6
  const PPLX_SSE_ENDPOINT = PROVIDERS["perplexity-web"].baseUrl;
6
7
  const PPLX_API_VERSION = "2.18";
@@ -145,24 +146,6 @@ function parseOpenAIMessages(messages) {
145
146
  else if (Array.isArray(msg.content)) {
146
147
  content = msg.content.filter((c) => c.type === "text").map((c) => String(c.text || "")).join(" ");
147
148
  }
148
-
149
- // Handle tool role messages — convert to user message with tool result context
150
- if (role === "tool") {
151
- const toolName = msg.name || "unknown_tool";
152
- const toolCallId = msg.tool_call_id || "";
153
- content = `[Tool Result for ${toolName}${toolCallId ? ` (call_id: ${toolCallId})` : ""}]:\n${content || "(empty result)"}`;
154
- role = "user";
155
- }
156
-
157
- // Handle assistant messages with tool_calls — serialize them as text
158
- if (role === "assistant" && msg.tool_calls && !content) {
159
- const tcText = msg.tool_calls.map(tc => {
160
- const fn = tc.function || {};
161
- return `<tool_call>{"name":"${fn.name}","arguments":${fn.arguments || "{}"}}</tool_call>`;
162
- }).join("\n");
163
- content = tcText;
164
- }
165
-
166
149
  if (!content.trim()) continue;
167
150
  if (role === "system") systemMsg += content + "\n";
168
151
  else if (role === "user" || role === "assistant") history.push({ role, content });
@@ -200,37 +183,13 @@ function buildPplxRequestBody(query, mode, modelPref, followUpUuid) {
200
183
 
201
184
  function formatToolsHint(tools) {
202
185
  if (!Array.isArray(tools) || tools.length === 0) return "";
203
- const defs = tools.map((t) => {
186
+ const lines = tools.map((t) => {
204
187
  const fn = t?.function || t || {};
205
188
  const name = fn.name || "unnamed";
206
- const desc = fn.description || "";
207
- const params = fn.parameters ? JSON.stringify(fn.parameters) : "{}";
208
- return `{"name":"${name}","description":${JSON.stringify(desc)},"parameters":${params}}`;
189
+ const desc = (fn.description || "").split("\n")[0].slice(0, 200);
190
+ return `- ${name}: ${desc}`;
209
191
  });
210
- return `You have access to these tools:\n[${defs.join(",")}]\n\nTo call a tool, output EXACTLY this format with no other text:\n<tool_call>{"name":"<tool_name>","arguments":{<args>}}</tool_call>\n\nYou may call multiple tools by outputting multiple <tool_call> blocks. If no tool is needed, respond normally.`;
211
- }
212
-
213
- // Parse tool calls from Perplexity text response
214
- const TOOL_CALL_RE = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g;
215
- function parseToolCalls(text) {
216
- const calls = [];
217
- let match;
218
- while ((match = TOOL_CALL_RE.exec(text)) !== null) {
219
- try {
220
- const obj = JSON.parse(match[1]);
221
- if (obj.name) {
222
- calls.push({
223
- id: `call_${Math.random().toString(36).slice(2, 14)}`,
224
- type: "function",
225
- function: { name: obj.name, arguments: JSON.stringify(obj.arguments || {}) },
226
- });
227
- }
228
- } catch { /* skip malformed */ }
229
- }
230
- return calls;
231
- }
232
- function stripToolCallTags(text) {
233
- return text.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "").replace(/\n{3,}/g, "\n\n").trim();
192
+ return `Available tools (reference only, cannot invoke):\n${lines.join("\n")}`;
234
193
  }
235
194
 
236
195
  function buildQuery(parsed, followUpUuid, tools) {
@@ -332,10 +291,6 @@ async function* extractContent(eventStream, signal) {
332
291
  yield { delta: "", answer: fullAnswer, backendUuid: backendUuid ?? undefined, done: true };
333
292
  }
334
293
 
335
- function sseChunk(data) {
336
- return `data: ${JSON.stringify(data)}\n\n`;
337
- }
338
-
339
294
  function buildStreamingResponse(eventStream, model, cid, created, history, currentMsg, signal) {
340
295
  const encoder = new TextEncoder();
341
296
  return new ReadableStream({
@@ -379,37 +334,19 @@ function buildStreamingResponse(eventStream, model, cid, created, history, curre
379
334
  if (chunk.answer) fullAnswer = chunk.answer;
380
335
  }
381
336
 
382
- // Parse tool calls from final answer
383
- const toolCalls = parseToolCalls(fullAnswer);
384
- const cleanAnswer = stripToolCallTags(fullAnswer);
385
-
386
- if (toolCalls.length > 0) {
387
- // Emit tool calls as delta chunks
388
- for (const tc of toolCalls) {
389
- controller.enqueue(encoder.encode(sseChunk({
390
- id: cid, object: "chat.completion.chunk", created, model, system_fingerprint: null,
391
- choices: [{ index: 0, delta: { tool_calls: [tc] }, finish_reason: null, logprobs: null }],
392
- })));
393
- }
394
- controller.enqueue(encoder.encode(sseChunk({
395
- id: cid, object: "chat.completion.chunk", created, model, system_fingerprint: null,
396
- choices: [{ index: 0, delta: {}, finish_reason: "tool_calls", logprobs: null }],
397
- })));
398
- } else {
399
- controller.enqueue(encoder.encode(sseChunk({
400
- id: cid, object: "chat.completion.chunk", created, model, system_fingerprint: null,
401
- choices: [{ index: 0, delta: {}, finish_reason: "stop", logprobs: null }],
402
- })));
403
- }
404
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
337
+ controller.enqueue(encoder.encode(sseChunk({
338
+ id: cid, object: "chat.completion.chunk", created, model, system_fingerprint: null,
339
+ choices: [{ index: 0, delta: {}, finish_reason: "stop", logprobs: null }],
340
+ })));
341
+ controller.enqueue(encoder.encode(SSE_DONE));
405
342
 
406
- sessionStore(history, currentMsg, cleanResponse(cleanAnswer), respBackendUuid);
343
+ sessionStore(history, currentMsg, cleanResponse(fullAnswer), respBackendUuid);
407
344
  } catch (err) {
408
345
  controller.enqueue(encoder.encode(sseChunk({
409
346
  id: cid, object: "chat.completion.chunk", created, model, system_fingerprint: null,
410
347
  choices: [{ index: 0, delta: { content: `[Stream error: ${err.message || String(err)}]` }, finish_reason: "stop", logprobs: null }],
411
348
  })));
412
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
349
+ controller.enqueue(encoder.encode(SSE_DONE));
413
350
  } finally {
414
351
  controller.close();
415
352
  }
@@ -435,27 +372,14 @@ async function buildNonStreamingResponse(eventStream, model, cid, created, histo
435
372
  }
436
373
 
437
374
  fullAnswer = cleanResponse(fullAnswer);
438
- sessionStore(history, currentMsg, stripToolCallTags(fullAnswer), respBackendUuid);
375
+ sessionStore(history, currentMsg, fullAnswer, respBackendUuid);
439
376
 
440
377
  const reasoningContent = thinkingParts.length > 0 ? thinkingParts.join("\n") : undefined;
441
- const toolCalls = parseToolCalls(fullAnswer);
442
- const cleanAnswer = stripToolCallTags(fullAnswer);
378
+ const msg = { role: "assistant", content: fullAnswer };
379
+ if (reasoningContent) msg.reasoning_content = reasoningContent;
443
380
 
444
381
  const promptTokens = Math.ceil(currentMsg.length / 4);
445
- const completionTokens = Math.ceil(cleanAnswer.length / 4);
446
-
447
- if (toolCalls.length > 0) {
448
- const msg = { role: "assistant", content: cleanAnswer || null, tool_calls: toolCalls };
449
- if (reasoningContent) msg.reasoning_content = reasoningContent;
450
- return new Response(JSON.stringify({
451
- id: cid, object: "chat.completion", created, model, system_fingerprint: null,
452
- choices: [{ index: 0, message: msg, finish_reason: "tool_calls", logprobs: null }],
453
- usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens, total_tokens: promptTokens + completionTokens },
454
- }), { status: 200, headers: { "Content-Type": "application/json" } });
455
- }
456
-
457
- const msg = { role: "assistant", content: cleanAnswer };
458
- if (reasoningContent) msg.reasoning_content = reasoningContent;
382
+ const completionTokens = Math.ceil(fullAnswer.length / 4);
459
383
 
460
384
  return new Response(JSON.stringify({
461
385
  id: cid, object: "chat.completion", created, model, system_fingerprint: null,
@@ -469,7 +393,7 @@ export class PerplexityWebExecutor extends BaseExecutor {
469
393
  super("perplexity-web", PROVIDERS["perplexity-web"]);
470
394
  }
471
395
 
472
- async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) {
396
+ async execute({ model, body, stream, credentials, signal, log }) {
473
397
  const messages = body?.messages;
474
398
  if (!messages || !Array.isArray(messages) || messages.length === 0) {
475
399
  const errResp = new Response(JSON.stringify({
@@ -531,7 +455,7 @@ export class PerplexityWebExecutor extends BaseExecutor {
531
455
 
532
456
  let response;
533
457
  try {
534
- response = await proxyAwareFetch(PPLX_SSE_ENDPOINT, fetchOptions, proxyOptions);
458
+ response = await fetch(PPLX_SSE_ENDPOINT, fetchOptions);
535
459
  } catch (err) {
536
460
  log?.error?.("PPLX-WEB", `Fetch failed: ${err.message || String(err)}`);
537
461
  const errResp = new Response(JSON.stringify({
@@ -567,7 +491,7 @@ export class PerplexityWebExecutor extends BaseExecutor {
567
491
  const sseStream = buildStreamingResponse(response.body, model, cid, created, parsed.history, parsed.currentMsg, signal);
568
492
  finalResponse = new Response(sseStream, {
569
493
  status: 200,
570
- headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "X-Accel-Buffering": "no" },
494
+ headers: { ...SSE_HEADERS_NO_BUFFER },
571
495
  });
572
496
  } else {
573
497
  finalResponse = await buildNonStreamingResponse(response.body, model, cid, created, parsed.history, parsed.currentMsg, signal);
@@ -20,18 +20,20 @@
20
20
  * different model upstream, so a missing entry is a hard error.
21
21
  */
22
22
 
23
- import { qoderEncodeBody } from '../../../dist/lib/qoder/encoding.js';
24
- import { buildCosyHeaders } from '../../../dist/lib/qoder/cosy.js';
23
+ import { qoderEncodeBody } from "../shared/qoder/encoding.js";
24
+ import { buildCosyHeaders } from "../shared/qoder/cosy.js";
25
25
  import { v4 as uuidv4 } from "uuid";
26
26
  import { createHash } from "crypto";
27
27
 
28
28
  import { BaseExecutor } from "./base.js";
29
29
  import { PROVIDERS } from "../config/providers.js";
30
30
  import { proxyAwareFetch } from "../utils/proxyFetch.js";
31
+ import { SSE_DONE } from "../utils/sseConstants.js";
32
+ import { FETCH_CONNECT_TIMEOUT_MS } from "../config/runtimeConfig.js";
31
33
  import {
32
34
  QODER_CHAT_URL_ENCODED,
33
35
  QODER_MODEL_MAP,
34
- } from '../../../dist/lib/qoder/constants.js';
36
+ } from "../shared/qoder/constants.js";
35
37
  import { getQoderModelConfig, resolveQoderModels } from "../services/qoderModels.js";
36
38
 
37
39
  /**
@@ -124,7 +126,7 @@ function truncate(s, n) {
124
126
  * Map the OpenAI-style request body into the exact shape Qoder expects.
125
127
  */
126
128
  async function buildQoderRequestBody({ model, body, credentials, log, proxyOptions, signal }) {
127
- const qoderKey = String(model || "").replace(/^(qoder|qd)\//, "");
129
+ const qoderKey = String(model || "").replace(/^qoder\//, "");
128
130
 
129
131
  // Fetch model config from dynamic API instead of relying on static QODER_MODEL_MAP.
130
132
  // This allows support for new Qoder models (e.g., qmodel_latest) without code changes.
@@ -240,9 +242,8 @@ function wrapQoderSSE(response, model) {
240
242
 
241
243
  const data = trimmed.slice(5).trimStart();
242
244
  if (data === "[DONE]") {
243
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
245
+ controller.enqueue(encoder.encode(SSE_DONE));
244
246
  doneEmitted = true;
245
- try { controller.terminate(); } catch {}
246
247
  return;
247
248
  }
248
249
 
@@ -260,16 +261,14 @@ function wrapQoderSSE(response, model) {
260
261
  choices: [{ index: 0, delta: { content: `\n[qoder error ${statusVal}: ${truncate(msg, 200)}]` }, finish_reason: "stop" }],
261
262
  });
262
263
  controller.enqueue(encoder.encode(`data: ${errChunk}\n\n`));
263
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
264
+ controller.enqueue(encoder.encode(SSE_DONE));
264
265
  doneEmitted = true;
265
- try { controller.terminate(); } catch {}
266
266
  return;
267
267
  }
268
268
  if (!inner) return;
269
269
  if (inner === "[DONE]") {
270
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
270
+ controller.enqueue(encoder.encode(SSE_DONE));
271
271
  doneEmitted = true;
272
- try { controller.terminate(); } catch {}
273
272
  return;
274
273
  }
275
274
  // Inner is an OpenAI-shaped chunk. Strip any embedded newlines so the
@@ -303,7 +302,7 @@ function wrapQoderSSE(response, model) {
303
302
  buffer = "";
304
303
  }
305
304
  if (!doneEmitted) {
306
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
305
+ controller.enqueue(encoder.encode(SSE_DONE));
307
306
  doneEmitted = true;
308
307
  }
309
308
  },
@@ -410,15 +409,21 @@ export class QoderExecutor extends BaseExecutor {
410
409
  ...cosyHeaders,
411
410
  };
412
411
 
412
+ // Abort if upstream doesn't return response headers within connect timeout.
413
+ const timeoutMs = this.config?.timeoutMs || FETCH_CONNECT_TIMEOUT_MS;
414
+ const connectCtrl = new AbortController();
415
+ const connectTimer = setTimeout(() => connectCtrl.abort(new Error("fetch connect timeout")), timeoutMs);
416
+ const mergedSignal = signal ? AbortSignal.any([signal, connectCtrl.signal]) : connectCtrl.signal;
417
+
413
418
  let response;
414
419
  try {
415
420
  response = await proxyAwareFetch(
416
421
  url,
417
- { method: "POST", headers, body: encodedBodyBuf, signal },
422
+ { method: "POST", headers, body: encodedBodyBuf, signal: mergedSignal },
418
423
  proxyOptions,
419
424
  );
420
- } catch (err) {
421
- throw err;
425
+ } finally {
426
+ clearTimeout(connectTimer);
422
427
  }
423
428
 
424
429
  if (!response.ok) {
@@ -430,10 +435,15 @@ export class QoderExecutor extends BaseExecutor {
430
435
  return { response: wrapped, url, headers, transformedBody: payload };
431
436
  }
432
437
 
433
- // Refresh Qoder device tokens using openapi.qoder.sh/api/v1/deviceToken/refresh.
434
- async refreshCredentials(credentials, log, proxyOptions = null) {
435
- const { refreshQoderToken } = await import("../services/tokenRefresh.js");
436
- return refreshQoderToken(credentials.refreshToken, log, proxyOptions);
438
+ // Qoder device tokens don't refresh through OAuth — the upstream returns
439
+ // 403 for our flow. Surfacing failure via 401-on-chat is enough; the
440
+ // dashboard tells users to re-login when their token expires (~30 days).
441
+ async refreshCredentials() {
442
+ return null;
443
+ }
444
+
445
+ needsRefresh() {
446
+ return false;
437
447
  }
438
448
  }
439
449
 
@@ -1,11 +1,33 @@
1
1
  import { BaseExecutor } from "./base.js";
2
2
  import { PROVIDERS } from "../config/providers.js";
3
- import { parseVertexSaJson, refreshVertexToken } from "../services/tokenRefresh.js";
3
+ import { parseVertexSaJson, refreshVertexToken, refreshGoogleToken } from "../services/tokenRefresh.js";
4
4
  import { proxyAwareFetch } from "../utils/proxyFetch.js";
5
5
 
6
6
  // Cache project IDs resolved from raw API keys { apiKey → projectId }
7
7
  const projectIdCache = new Map();
8
8
 
9
+ /**
10
+ * Parse Google ADC user credential JSON from apiKey string.
11
+ * This is the format produced by `gcloud auth application-default login`.
12
+ */
13
+ function parseVertexAdcJson(apiKey) {
14
+ if (typeof apiKey !== "string") return null;
15
+ try {
16
+ const parsed = JSON.parse(apiKey);
17
+ if (
18
+ parsed.type === "authorized_user" &&
19
+ parsed.client_id &&
20
+ parsed.client_secret &&
21
+ parsed.refresh_token
22
+ ) {
23
+ return parsed;
24
+ }
25
+ return null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
9
31
  /**
10
32
  * Resolve GCP project ID from a raw Vertex API key.
11
33
  * Sends a dummy 404 request and parses "projects/{id}" from the error message.
@@ -43,8 +65,13 @@ export class VertexExecutor extends BaseExecutor {
43
65
 
44
66
  buildUrl(model, stream, urlIndex = 0, credentials = null) {
45
67
  const saJson = parseVertexSaJson(credentials?.apiKey);
46
- const rawKey = !saJson ? credentials?.apiKey : null;
47
- const projectId = saJson?.project_id || credentials?.providerSpecificData?.projectId;
68
+ const adcJson = parseVertexAdcJson(credentials?.apiKey);
69
+ const usesOAuth = !!saJson || !!adcJson || !!credentials?.accessToken;
70
+ const rawKey = !usesOAuth ? credentials?.apiKey : null;
71
+ const projectId =
72
+ saJson?.project_id ||
73
+ adcJson?.quota_project_id ||
74
+ credentials?.providerSpecificData?.projectId;
48
75
 
49
76
  if (this.provider === "vertex-partner") {
50
77
  // Partner models require project_id in path regardless of auth method
@@ -56,8 +83,14 @@ export class VertexExecutor extends BaseExecutor {
56
83
  // Gemini on Vertex
57
84
  const action = stream ? "streamGenerateContent" : "generateContent";
58
85
 
59
- if (saJson) {
60
- // SA JSON + Bearer token: must use project-scoped path to avoid RESOURCE_PROJECT_INVALID
86
+ if (usesOAuth) {
87
+ // SA JSON / ADC / pre-set accessToken: must use project-scoped path to avoid RESOURCE_PROJECT_INVALID
88
+ if (!projectId) {
89
+ throw new Error(
90
+ "Vertex OAuth/ADC requires a project_id. " +
91
+ "Add quota_project_id to your ADC JSON or set providerSpecificData.projectId."
92
+ );
93
+ }
61
94
  const location = credentials?.providerSpecificData?.location || "us-central1";
62
95
  let url = `https://aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:${action}`;
63
96
  if (stream) url += "?alt=sse";
@@ -97,16 +130,29 @@ export class VertexExecutor extends BaseExecutor {
97
130
 
98
131
  async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) {
99
132
  const saJson = parseVertexSaJson(credentials?.apiKey);
133
+ const adcJson = parseVertexAdcJson(credentials?.apiKey);
100
134
 
101
- // SA JSON flow: mint Bearer token (cached)
135
+ // SA JSON flow: mint Bearer token via JWT assertion (cached)
102
136
  if (saJson) {
103
137
  const result = await refreshVertexToken(saJson, log);
104
138
  if (!result?.accessToken) throw new Error("Vertex: failed to mint access token from Service Account JSON");
105
139
  credentials.accessToken = result.accessToken;
106
140
  }
107
141
 
142
+ // ADC user credential flow: refresh Bearer token via Google OAuth2 token endpoint
143
+ if (adcJson) {
144
+ const result = await refreshGoogleToken(
145
+ adcJson.refresh_token,
146
+ adcJson.client_id,
147
+ adcJson.client_secret,
148
+ log
149
+ );
150
+ if (!result?.accessToken) throw new Error("Vertex: failed to refresh access token from ADC JSON (authorized_user)");
151
+ credentials.accessToken = result.accessToken;
152
+ }
153
+
108
154
  // vertex-partner with raw key: auto-resolve project_id if not provided
109
- if (this.provider === "vertex-partner" && !saJson && !credentials?.providerSpecificData?.projectId) {
155
+ if (this.provider === "vertex-partner" && !saJson && !adcJson && !credentials?.providerSpecificData?.projectId) {
110
156
  const projectId = await resolveProjectId(credentials.apiKey);
111
157
  if (!projectId) throw new Error("Vertex: could not resolve project_id from API key. Please add it manually in provider settings.");
112
158
  log?.debug?.("VERTEX", `Resolved project_id: ${projectId}`);
@@ -1,18 +1,19 @@
1
1
  import { DefaultExecutor } from "./default.js";
2
2
  import { resolveXiaomiTokenplanBaseUrl } from "../config/providers.js";
3
- import { getModelTargetFormat } from "../config/providerModels.js";
4
- import { FORMATS } from "../translator/formats.js";
3
+ // import { getModelTargetFormat } from "../config/providerModels.js";
4
+ // import { FORMATS } from "../translator/formats.js";
5
5
 
6
6
  export class XiaomiTokenplanExecutor extends DefaultExecutor {
7
7
  constructor() {
8
8
  super("xiaomi-tokenplan");
9
9
  }
10
10
 
11
- // Claude-native aliases route to the Anthropic-compatible messages endpoint
11
+ // Token Plan keys are region-specific. Route per sourceFormat-matched transport:
12
+ // claude → Anthropic /anthropic/v1/messages, openai → /chat/completions.
12
13
  buildUrl(model, stream, urlIndex = 0, credentials = null) {
13
14
  const baseUrl = resolveXiaomiTokenplanBaseUrl(credentials);
14
- if (getModelTargetFormat(model, model) === FORMATS.CLAUDE) {
15
- return `${baseUrl.replace(/\/v1\/?$/, "/anthropic/v1")}/messages`;
15
+ if (credentials?.runtimeTransport?.format === "claude") {
16
+ return `${baseUrl.replace(/\/v1\/?$/, "")}/anthropic/v1/messages`;
16
17
  }
17
18
  return `${baseUrl}/chat/completions`;
18
19
  }