@fudrouter/fsrouter 0.6.61 → 0.6.63

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 (557) hide show
  1. package/bin/copyJs.js +13 -9
  2. package/open-sse/AGENTS.md +39 -0
  3. package/open-sse/config/anthropicHeaders.js +87 -0
  4. package/open-sse/config/appConstants.js +238 -0
  5. package/open-sse/config/audioRegistry.js +506 -0
  6. package/open-sse/config/cliFingerprints.js +304 -0
  7. package/open-sse/config/codexInstructions.js +119 -0
  8. package/open-sse/config/codexQuotaScopes.js +42 -0
  9. package/open-sse/config/constants.js +4 -0
  10. package/open-sse/config/contextEditing.js +68 -0
  11. package/open-sse/config/credentialLoader.js +83 -0
  12. package/open-sse/config/defaultThinkingSignature.js +12 -0
  13. package/open-sse/config/embeddingRegistry.js +264 -0
  14. package/open-sse/config/errorConfig.js +96 -0
  15. package/open-sse/config/freeModelCatalog.data.js +121 -0
  16. package/open-sse/config/freeModelCatalog.js +90 -0
  17. package/open-sse/config/freeTierCatalog.js +81 -0
  18. package/open-sse/config/googleTtsLanguages.js +62 -0
  19. package/open-sse/config/imageParamDefs.js +360 -0
  20. package/open-sse/config/imageRegistry.js +493 -0
  21. package/open-sse/config/kiroConstants.js +268 -0
  22. package/open-sse/config/mediaConfig.js +27 -0
  23. package/open-sse/config/mediaServiceKinds.js +71 -0
  24. package/open-sse/config/models.js +13 -0
  25. package/open-sse/config/moderationRegistry.js +71 -0
  26. package/open-sse/config/musicRegistry.js +99 -0
  27. package/open-sse/config/ocrRegistry.js +61 -0
  28. package/open-sse/config/ollamaModels.js +19 -0
  29. package/open-sse/config/providerErrorRules.js +114 -0
  30. package/open-sse/config/providerFieldStrips.js +35 -0
  31. package/open-sse/config/providerHeaderProfiles.js +191 -0
  32. package/open-sse/config/providerModels.js +106 -0
  33. package/open-sse/config/providerRegistry.js +16 -0
  34. package/open-sse/config/providers.js +19 -0
  35. package/open-sse/config/registryUtils.js +106 -0
  36. package/open-sse/config/rerankRegistry.js +177 -0
  37. package/open-sse/config/runtimeConfig.js +96 -0
  38. package/open-sse/config/runway.js +39 -0
  39. package/open-sse/config/searchRegistry.js +153 -0
  40. package/open-sse/config/toolCloaking.js +199 -0
  41. package/open-sse/config/ttsModels.js +129 -0
  42. package/open-sse/config/videoRegistry.js +207 -0
  43. package/open-sse/executors/adapta-web.js +435 -0
  44. package/open-sse/executors/antigravity.js +534 -0
  45. package/open-sse/executors/antigravityUpstreamError.js +13 -0
  46. package/open-sse/executors/auggie.js +437 -0
  47. package/open-sse/executors/azure-openai.js +32 -0
  48. package/open-sse/executors/azure.js +57 -0
  49. package/open-sse/executors/base.js +181 -0
  50. package/open-sse/executors/bedrock.js +633 -0
  51. package/open-sse/executors/blackbox-web.js +578 -0
  52. package/open-sse/executors/chatgpt-web.js +2042 -0
  53. package/open-sse/executors/chipotle.js +357 -0
  54. package/open-sse/executors/claude-web-with-auto-refresh.js +73 -0
  55. package/open-sse/executors/claude-web.js +586 -0
  56. package/open-sse/executors/cliproxyapi.js +296 -0
  57. package/open-sse/executors/cloudflare-ai.js +46 -0
  58. package/open-sse/executors/codebuddy-cn.js +40 -0
  59. package/open-sse/executors/codex.js +1324 -0
  60. package/open-sse/executors/commandCode.js +558 -0
  61. package/open-sse/executors/commandcode.js +88 -0
  62. package/open-sse/executors/copilot-m365-connection.js +176 -0
  63. package/open-sse/executors/copilot-m365-frames.js +192 -0
  64. package/open-sse/executors/copilot-m365-web.js +299 -0
  65. package/open-sse/executors/copilot-web.js +580 -0
  66. package/open-sse/executors/cursor.js +795 -0
  67. package/open-sse/executors/deepseek-web-with-auto-refresh.js +150 -0
  68. package/open-sse/executors/deepseek-web.js +875 -0
  69. package/open-sse/executors/default.js +430 -0
  70. package/open-sse/executors/devin-cli.js +351 -0
  71. package/open-sse/executors/doubao-web.js +550 -0
  72. package/open-sse/executors/duckduckgo-web.js +629 -0
  73. package/open-sse/executors/forceResponsesUpstream.js +33 -0
  74. package/open-sse/executors/gemini-business.js +320 -0
  75. package/open-sse/executors/gemini-cli.js +89 -0
  76. package/open-sse/executors/gemini-web.js +297 -0
  77. package/open-sse/executors/github.js +379 -0
  78. package/open-sse/executors/gitlab.js +569 -0
  79. package/open-sse/executors/glm.js +361 -0
  80. package/open-sse/executors/grok-cli.js +119 -0
  81. package/open-sse/executors/grok-web.js +345 -0
  82. package/open-sse/executors/huggingchat.js +467 -0
  83. package/open-sse/executors/iflow.js +108 -0
  84. package/open-sse/executors/index.js +78 -0
  85. package/open-sse/executors/inner-ai.js +527 -0
  86. package/open-sse/executors/kimchi.js +123 -0
  87. package/open-sse/executors/kimi-web.js +325 -0
  88. package/open-sse/executors/kimi.js +97 -0
  89. package/open-sse/executors/kiro.js +503 -0
  90. package/open-sse/executors/kiroThinking.js +44 -0
  91. package/open-sse/executors/lmarena.js +395 -0
  92. package/open-sse/executors/mimo-free.js +167 -0
  93. package/open-sse/executors/mimocode.js +392 -0
  94. package/open-sse/executors/ninerouter.js +155 -0
  95. package/open-sse/executors/nlpcloud.js +438 -0
  96. package/open-sse/executors/ollama-local.js +14 -0
  97. package/open-sse/executors/opencode-go.js +41 -0
  98. package/open-sse/executors/opencode.js +32 -0
  99. package/open-sse/executors/perplexity-web.js +581 -0
  100. package/open-sse/executors/poe-web.js +133 -0
  101. package/open-sse/executors/pollinations.js +95 -0
  102. package/open-sse/executors/puter.js +31 -0
  103. package/open-sse/executors/qoder.js +448 -0
  104. package/open-sse/executors/qwen-web.js +372 -0
  105. package/open-sse/executors/qwen.js +129 -0
  106. package/open-sse/executors/t3-chat-web.js +417 -0
  107. package/open-sse/executors/theoldllm.js +290 -0
  108. package/open-sse/executors/trae.js +418 -0
  109. package/open-sse/executors/v0-vercel-web.js +141 -0
  110. package/open-sse/executors/venice-web.js +142 -0
  111. package/open-sse/executors/vertex.js +131 -0
  112. package/open-sse/executors/vertexMedia.js +236 -0
  113. package/open-sse/executors/windsurf.js +560 -0
  114. package/open-sse/executors/xai.js +46 -0
  115. package/open-sse/executors/xiaomi-tokenplan.js +19 -0
  116. package/open-sse/executors/yuanbao-web.js +385 -0
  117. package/open-sse/executors/zed-hosted.js +281 -0
  118. package/open-sse/executors/zenmux-free.js +247 -0
  119. package/open-sse/handlers/audioSpeech.js +759 -0
  120. package/open-sse/handlers/audioTranscription.js +395 -0
  121. package/open-sse/handlers/audioTranslation.js +181 -0
  122. package/open-sse/handlers/chatCore/nonStreamingHandler.js +300 -0
  123. package/open-sse/handlers/chatCore/requestDetail.js +102 -0
  124. package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
  125. package/open-sse/handlers/chatCore/streamingHandler.js +95 -0
  126. package/open-sse/handlers/chatCore.js +404 -0
  127. package/open-sse/handlers/embeddingProviders/_base.js +4 -0
  128. package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
  129. package/open-sse/handlers/embeddingProviders/index.js +23 -0
  130. package/open-sse/handlers/embeddingProviders/openai.js +39 -0
  131. package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
  132. package/open-sse/handlers/embeddingsCore.js +126 -0
  133. package/open-sse/handlers/fetch/index.js +237 -0
  134. package/open-sse/handlers/imageGenerationCore.js +230 -0
  135. package/open-sse/handlers/imageProviders/_base.js +31 -0
  136. package/open-sse/handlers/imageProviders/antigravity.js +73 -0
  137. package/open-sse/handlers/imageProviders/blackForestLabs.js +63 -0
  138. package/open-sse/handlers/imageProviders/cloudflareAi.js +207 -0
  139. package/open-sse/handlers/imageProviders/codex.js +217 -0
  140. package/open-sse/handlers/imageProviders/comfyui.js +25 -0
  141. package/open-sse/handlers/imageProviders/falAi.js +62 -0
  142. package/open-sse/handlers/imageProviders/gemini.js +40 -0
  143. package/open-sse/handlers/imageProviders/huggingface.js +38 -0
  144. package/open-sse/handlers/imageProviders/index.js +44 -0
  145. package/open-sse/handlers/imageProviders/leonardo.js +435 -0
  146. package/open-sse/handlers/imageProviders/nanobanana.js +79 -0
  147. package/open-sse/handlers/imageProviders/openai.js +61 -0
  148. package/open-sse/handlers/imageProviders/runwayml.js +72 -0
  149. package/open-sse/handlers/imageProviders/sdwebui.js +36 -0
  150. package/open-sse/handlers/imageProviders/stabilityAi.js +59 -0
  151. package/open-sse/handlers/imageProviders/weavy.js +222 -0
  152. package/open-sse/handlers/moderations.js +92 -0
  153. package/open-sse/handlers/ocr.js +89 -0
  154. package/open-sse/handlers/rerank.js +223 -0
  155. package/open-sse/handlers/responseSanitizer/reasoning.js +144 -0
  156. package/open-sse/handlers/responsesHandler.js +99 -0
  157. package/open-sse/handlers/search/callers.js +371 -0
  158. package/open-sse/handlers/search/chatSearch.js +409 -0
  159. package/open-sse/handlers/search/index.js +201 -0
  160. package/open-sse/handlers/search/normalizers.js +223 -0
  161. package/open-sse/handlers/search.js +1056 -0
  162. package/open-sse/handlers/sseParser.js +469 -0
  163. package/open-sse/handlers/sttCore.js +193 -0
  164. package/open-sse/handlers/ttsCore.js +74 -0
  165. package/open-sse/handlers/ttsProviders/_base.js +39 -0
  166. package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
  167. package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
  168. package/open-sse/handlers/ttsProviders/gemini.js +117 -0
  169. package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
  170. package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
  171. package/open-sse/handlers/ttsProviders/index.js +50 -0
  172. package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
  173. package/open-sse/handlers/ttsProviders/minimax.js +59 -0
  174. package/open-sse/handlers/ttsProviders/openai.js +30 -0
  175. package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
  176. package/open-sse/handlers/usageExtractor.js +93 -0
  177. package/open-sse/handlers/videoGenerationCore.js +119 -0
  178. package/open-sse/handlers/videoProviders/index.js +22 -0
  179. package/open-sse/handlers/videoProviders/leonardo.js +511 -0
  180. package/open-sse/handlers/videoProviders/weavy.js +198 -0
  181. package/open-sse/handlers/webFetch.js +200 -0
  182. package/open-sse/index.js +79 -0
  183. package/open-sse/mcp-server/audit.js +283 -0
  184. package/open-sse/mcp-server/catalog.js +184 -0
  185. package/open-sse/mcp-server/descriptionCompressor.js +175 -0
  186. package/open-sse/mcp-server/httpAuthContext.js +32 -0
  187. package/open-sse/mcp-server/httpTransport.js +229 -0
  188. package/open-sse/mcp-server/index.js +33 -0
  189. package/open-sse/mcp-server/mcpCallerIdentity.js +21 -0
  190. package/open-sse/mcp-server/runtimeHeartbeat.js +110 -0
  191. package/open-sse/mcp-server/schemas/a2a.js +153 -0
  192. package/open-sse/mcp-server/schemas/audit.js +17 -0
  193. package/open-sse/mcp-server/schemas/index.js +166 -0
  194. package/open-sse/mcp-server/schemas/pickFastestModel.js +90 -0
  195. package/open-sse/mcp-server/schemas/toolDefinition.js +0 -0
  196. package/open-sse/mcp-server/schemas/toolSearch.js +32 -0
  197. package/open-sse/mcp-server/schemas/tools.js +1219 -0
  198. package/open-sse/mcp-server/scopeEnforcement.js +80 -0
  199. package/open-sse/mcp-server/server.js +1168 -0
  200. package/open-sse/mcp-server/toolCardinality.js +78 -0
  201. package/open-sse/mcp-server/toolSearch/catalog.js +66 -0
  202. package/open-sse/mcp-server/toolSearch/handler.js +20 -0
  203. package/open-sse/mcp-server/toolSearch/index.js +10 -0
  204. package/open-sse/mcp-server/toolSearch/register.js +21 -0
  205. package/open-sse/mcp-server/toolSearch/search.js +55 -0
  206. package/open-sse/mcp-server/toolSearch/signature.js +67 -0
  207. package/open-sse/mcp-server/tools/advancedTools.js +893 -0
  208. package/open-sse/mcp-server/tools/agentSkillTools.js +69 -0
  209. package/open-sse/mcp-server/tools/compressionTools.js +309 -0
  210. package/open-sse/mcp-server/tools/gamificationTools.js +134 -0
  211. package/open-sse/mcp-server/tools/githubSkillTools.js +102 -0
  212. package/open-sse/mcp-server/tools/memoryTools.js +118 -0
  213. package/open-sse/mcp-server/tools/notionTools.js +101 -0
  214. package/open-sse/mcp-server/tools/obsidianTools.js +307 -0
  215. package/open-sse/mcp-server/tools/pickFastestModel.js +223 -0
  216. package/open-sse/mcp-server/tools/pluginTools.js +181 -0
  217. package/open-sse/mcp-server/tools/poolTools.js +139 -0
  218. package/open-sse/mcp-server/tools/skillTools.js +113 -0
  219. package/open-sse/package.json +10 -0
  220. package/open-sse/providers/REGISTRY_TEMPLATE.js +98 -0
  221. package/open-sse/providers/capabilities.js +293 -0
  222. package/open-sse/providers/index.js +51 -0
  223. package/open-sse/providers/models/helpers.js +20 -0
  224. package/open-sse/providers/models/namePatterns.js +33 -0
  225. package/open-sse/providers/models/schema.js +40 -0
  226. package/open-sse/providers/pricing.js +308 -0
  227. package/open-sse/providers/registry/alicode-intl.js +30 -0
  228. package/open-sse/providers/registry/alicode.js +31 -0
  229. package/open-sse/providers/registry/anthropic.js +37 -0
  230. package/open-sse/providers/registry/antigravity.js +81 -0
  231. package/open-sse/providers/registry/assemblyai.js +38 -0
  232. package/open-sse/providers/registry/aws-polly.js +45 -0
  233. package/open-sse/providers/registry/azure.js +21 -0
  234. package/open-sse/providers/registry/black-forest-labs.js +32 -0
  235. package/open-sse/providers/registry/blackbox.js +41 -0
  236. package/open-sse/providers/registry/brave-search.js +35 -0
  237. package/open-sse/providers/registry/byteplus.js +35 -0
  238. package/open-sse/providers/registry/cartesia.js +36 -0
  239. package/open-sse/providers/registry/cerebras.js +31 -0
  240. package/open-sse/providers/registry/chutes.js +24 -0
  241. package/open-sse/providers/registry/claude.js +87 -0
  242. package/open-sse/providers/registry/cline.js +51 -0
  243. package/open-sse/providers/registry/clinepass.js +57 -0
  244. package/open-sse/providers/registry/cloudflare-ai.js +55 -0
  245. package/open-sse/providers/registry/codebuddy-cn.js +77 -0
  246. package/open-sse/providers/registry/codex.js +100 -0
  247. package/open-sse/providers/registry/cohere.js +27 -0
  248. package/open-sse/providers/registry/comfyui.js +20 -0
  249. package/open-sse/providers/registry/commandcode.js +43 -0
  250. package/open-sse/providers/registry/coqui.js +30 -0
  251. package/open-sse/providers/registry/cursor.js +58 -0
  252. package/open-sse/providers/registry/deepgram.js +33 -0
  253. package/open-sse/providers/registry/deepseek.js +51 -0
  254. package/open-sse/providers/registry/digitalocean.js +92 -0
  255. package/open-sse/providers/registry/edge-tts.js +24 -0
  256. package/open-sse/providers/registry/elevenlabs.js +35 -0
  257. package/open-sse/providers/registry/exa.js +50 -0
  258. package/open-sse/providers/registry/fal-ai.js +34 -0
  259. package/open-sse/providers/registry/featherless.js +34 -0
  260. package/open-sse/providers/registry/firecrawl.js +34 -0
  261. package/open-sse/providers/registry/fireworks.js +29 -0
  262. package/open-sse/providers/registry/gemini-cli.js +58 -0
  263. package/open-sse/providers/registry/gemini.js +83 -0
  264. package/open-sse/providers/registry/github.js +88 -0
  265. package/open-sse/providers/registry/gitlab.js +32 -0
  266. package/open-sse/providers/registry/glm-cn.js +35 -0
  267. package/open-sse/providers/registry/glm.js +58 -0
  268. package/open-sse/providers/registry/google-pse.js +35 -0
  269. package/open-sse/providers/registry/google-tts.js +24 -0
  270. package/open-sse/providers/registry/grok-cli.js +86 -0
  271. package/open-sse/providers/registry/grok-web.js +39 -0
  272. package/open-sse/providers/registry/groq.js +37 -0
  273. package/open-sse/providers/registry/huggingface.js +34 -0
  274. package/open-sse/providers/registry/hyperbolic.js +35 -0
  275. package/open-sse/providers/registry/ibm-bob.js +27 -0
  276. package/open-sse/providers/registry/iflow.js +52 -0
  277. package/open-sse/providers/registry/index.js +210 -0
  278. package/open-sse/providers/registry/inworld.js +36 -0
  279. package/open-sse/providers/registry/jina-ai.js +42 -0
  280. package/open-sse/providers/registry/jina-reader.js +34 -0
  281. package/open-sse/providers/registry/kilocode.js +51 -0
  282. package/open-sse/providers/registry/kimchi.js +49 -0
  283. package/open-sse/providers/registry/kimi-coding.js +65 -0
  284. package/open-sse/providers/registry/kimi.js +56 -0
  285. package/open-sse/providers/registry/kiro.js +115 -0
  286. package/open-sse/providers/registry/linkup.js +34 -0
  287. package/open-sse/providers/registry/local-device.js +24 -0
  288. package/open-sse/providers/registry/mimo-free.js +24 -0
  289. package/open-sse/providers/registry/minimax-cn.js +76 -0
  290. package/open-sse/providers/registry/minimax.js +83 -0
  291. package/open-sse/providers/registry/mistral.js +34 -0
  292. package/open-sse/providers/registry/mmf.js +19 -0
  293. package/open-sse/providers/registry/nanobanana.js +35 -0
  294. package/open-sse/providers/registry/nebius.js +27 -0
  295. package/open-sse/providers/registry/nvidia.js +43 -0
  296. package/open-sse/providers/registry/ollama-local.js +19 -0
  297. package/open-sse/providers/registry/ollama.js +36 -0
  298. package/open-sse/providers/registry/openai.js +84 -0
  299. package/open-sse/providers/registry/opencode-go.js +41 -0
  300. package/open-sse/providers/registry/opencode.js +25 -0
  301. package/open-sse/providers/registry/openrouter.js +60 -0
  302. package/open-sse/providers/registry/perplexity-agent.js +49 -0
  303. package/open-sse/providers/registry/perplexity-web.js +33 -0
  304. package/open-sse/providers/registry/perplexity.js +37 -0
  305. package/open-sse/providers/registry/playht.js +36 -0
  306. package/open-sse/providers/registry/qoder.js +54 -0
  307. package/open-sse/providers/registry/qwen.js +33 -0
  308. package/open-sse/providers/registry/recraft.js +24 -0
  309. package/open-sse/providers/registry/runwayml.js +30 -0
  310. package/open-sse/providers/registry/sdwebui.js +20 -0
  311. package/open-sse/providers/registry/searchapi.js +35 -0
  312. package/open-sse/providers/registry/searxng.js +35 -0
  313. package/open-sse/providers/registry/serper.js +35 -0
  314. package/open-sse/providers/registry/siliconflow.js +39 -0
  315. package/open-sse/providers/registry/stability-ai.js +31 -0
  316. package/open-sse/providers/registry/tavily.js +50 -0
  317. package/open-sse/providers/registry/together.js +31 -0
  318. package/open-sse/providers/registry/topaz.js +19 -0
  319. package/open-sse/providers/registry/tortoise.js +30 -0
  320. package/open-sse/providers/registry/venice.js +56 -0
  321. package/open-sse/providers/registry/vercel-ai-gateway.js +41 -0
  322. package/open-sse/providers/registry/vertex-partner.js +29 -0
  323. package/open-sse/providers/registry/vertex.js +32 -0
  324. package/open-sse/providers/registry/volcengine-ark.js +35 -0
  325. package/open-sse/providers/registry/voyage-ai.js +30 -0
  326. package/open-sse/providers/registry/xai.js +45 -0
  327. package/open-sse/providers/registry/xiaomi-mimo.js +46 -0
  328. package/open-sse/providers/registry/xiaomi-tokenplan.js +63 -0
  329. package/open-sse/providers/registry/youcom.js +35 -0
  330. package/open-sse/providers/schema.js +76 -0
  331. package/open-sse/providers/shared.js +74 -0
  332. package/open-sse/providers/thinkingLevels.js +48 -0
  333. package/open-sse/rtk/applyFilter.js +15 -0
  334. package/open-sse/rtk/autodetect.js +111 -0
  335. package/open-sse/rtk/caveman.js +100 -0
  336. package/open-sse/rtk/cavemanPrompts.js +78 -0
  337. package/open-sse/rtk/constants.js +55 -0
  338. package/open-sse/rtk/filters/buildOutput.js +127 -0
  339. package/open-sse/rtk/filters/dedupLog.js +44 -0
  340. package/open-sse/rtk/filters/find.js +50 -0
  341. package/open-sse/rtk/filters/gitDiff.js +92 -0
  342. package/open-sse/rtk/filters/gitLog.js +99 -0
  343. package/open-sse/rtk/filters/gitStatus.js +117 -0
  344. package/open-sse/rtk/filters/grep.js +48 -0
  345. package/open-sse/rtk/filters/ls.js +79 -0
  346. package/open-sse/rtk/filters/readNumbered.js +27 -0
  347. package/open-sse/rtk/filters/searchList.js +52 -0
  348. package/open-sse/rtk/filters/smartTruncate.js +15 -0
  349. package/open-sse/rtk/filters/tree.js +32 -0
  350. package/open-sse/rtk/headroom.js +215 -0
  351. package/open-sse/rtk/index.js +155 -0
  352. package/open-sse/rtk/paleo.js +47 -0
  353. package/open-sse/rtk/ponytail.js +9 -0
  354. package/open-sse/rtk/ponytailPrompt.js +52 -0
  355. package/open-sse/rtk/ponytailPrompts.js +76 -0
  356. package/open-sse/rtk/pxpipe.js +104 -0
  357. package/open-sse/rtk/registry.js +38 -0
  358. package/open-sse/rtk/systemInject.js +98 -0
  359. package/open-sse/services/accountFallback.js +323 -0
  360. package/open-sse/services/antigravityHeaders.js +91 -0
  361. package/open-sse/services/antigravityVersion.js +154 -0
  362. package/open-sse/services/clinepassModels.js +63 -0
  363. package/open-sse/services/codexQuotaFetcher.js +312 -0
  364. package/open-sse/services/codexVerbosity.js +30 -0
  365. package/open-sse/services/combo/context.js +12 -0
  366. package/open-sse/services/combo/fingerprintExpansion.js +51 -0
  367. package/open-sse/services/combo/headroomRanking.js +28 -0
  368. package/open-sse/services/combo/quotaExhaustionCutoff.js +87 -0
  369. package/open-sse/services/combo/quotaScoring.js +226 -0
  370. package/open-sse/services/combo/quotaShareConcurrency.js +25 -0
  371. package/open-sse/services/combo/quotaShareInflight.js +50 -0
  372. package/open-sse/services/combo/quotaShareStrategy.js +130 -0
  373. package/open-sse/services/combo/resolveAutoStrategy.js +231 -0
  374. package/open-sse/services/combo/responseValidation.js +140 -0
  375. package/open-sse/services/combo/runtimeUnits.js +195 -0
  376. package/open-sse/services/combo/sessionStickiness.js +128 -0
  377. package/open-sse/services/combo/shadowRouting.js +131 -0
  378. package/open-sse/services/combo/targetExhaustion.js +75 -0
  379. package/open-sse/services/combo/targetSorters.js +126 -0
  380. package/open-sse/services/combo/targetTimeoutRunner.js +65 -0
  381. package/open-sse/services/combo/validateQuality.js +271 -0
  382. package/open-sse/services/combo.js +571 -0
  383. package/open-sse/services/comboAgentMiddleware.js +91 -0
  384. package/open-sse/services/comboConfig.js +153 -0
  385. package/open-sse/services/comboManifestMetrics.js +10 -0
  386. package/open-sse/services/comboMetrics.js +278 -0
  387. package/open-sse/services/compact.js +71 -0
  388. package/open-sse/services/copilotModels.js +155 -0
  389. package/open-sse/services/emergencyFallback.js +109 -0
  390. package/open-sse/services/errorClassifier.js +190 -0
  391. package/open-sse/services/evalRouting.js +201 -0
  392. package/open-sse/services/freeWebSearch.js +4 -0
  393. package/open-sse/services/fusion.js +223 -0
  394. package/open-sse/services/intentClassifier.js +631 -0
  395. package/open-sse/services/kimchiModels.js +176 -0
  396. package/open-sse/services/kiroModels.js +332 -0
  397. package/open-sse/services/model.js +136 -0
  398. package/open-sse/services/modelCapabilities.js +5 -0
  399. package/open-sse/services/modelDeprecation.js +101 -0
  400. package/open-sse/services/modelFamilyFallback.js +166 -0
  401. package/open-sse/services/oauthCredentialManager.js +156 -0
  402. package/open-sse/services/pipeline.js +107 -0
  403. package/open-sse/services/projectId.js +306 -0
  404. package/open-sse/services/provider.js +169 -0
  405. package/open-sse/services/providerCooldownTracker.js +94 -0
  406. package/open-sse/services/providerDefaultRateLimit.js +11 -0
  407. package/open-sse/services/proxyAutoSelector.js +24 -0
  408. package/open-sse/services/qoderModels.js +214 -0
  409. package/open-sse/services/quotaFetchThrottle.js +103 -0
  410. package/open-sse/services/quotaMonitor.js +310 -0
  411. package/open-sse/services/quotaPreflight.js +266 -0
  412. package/open-sse/services/rateLimitManager/headers.js +56 -0
  413. package/open-sse/services/rateLimitManager.js +616 -0
  414. package/open-sse/services/rateLimitSemaphore.js +112 -0
  415. package/open-sse/services/responsesInputSanitizer.js +99 -0
  416. package/open-sse/services/slidingWindowLimiter.js +44 -0
  417. package/open-sse/services/streamRecovery.js +355 -0
  418. package/open-sse/services/taskAwareRouter.js +227 -0
  419. package/open-sse/services/taskAwareRouting.js +332 -0
  420. package/open-sse/services/thinkingBudget.js +247 -0
  421. package/open-sse/services/tokenRefresh/dedup.js +31 -0
  422. package/open-sse/services/tokenRefresh/providers.js +624 -0
  423. package/open-sse/services/tokenRefresh.js +254 -0
  424. package/open-sse/services/usage/claude.js +147 -0
  425. package/open-sse/services/usage/codebuddy-cn.js +138 -0
  426. package/open-sse/services/usage/codex.js +267 -0
  427. package/open-sse/services/usage/github.js +100 -0
  428. package/open-sse/services/usage/google.js +241 -0
  429. package/open-sse/services/usage/grok-cli.js +274 -0
  430. package/open-sse/services/usage/kiro.js +188 -0
  431. package/open-sse/services/usage/minimax.js +234 -0
  432. package/open-sse/services/usage/misc.js +288 -0
  433. package/open-sse/services/usage/shared.js +70 -0
  434. package/open-sse/services/usage.js +62 -0
  435. package/open-sse/services/usageDb.js +2 -0
  436. package/open-sse/services/webSearchRouting.js +25 -0
  437. package/open-sse/services/wildcardRouter.js +59 -0
  438. package/open-sse/shared/clineAuth.js +37 -0
  439. package/open-sse/shared/machineId.js +19 -0
  440. package/open-sse/shared/qoder/constants.js +64 -0
  441. package/open-sse/shared/qoder/cosy.js +175 -0
  442. package/open-sse/shared/qoder/encoding.js +55 -0
  443. package/open-sse/transformer/responsesTransformer.js +439 -0
  444. package/open-sse/transformer/streamToJsonConverter.js +103 -0
  445. package/open-sse/translator/concerns/chunk.js +11 -0
  446. package/open-sse/translator/concerns/finishReason.js +63 -0
  447. package/open-sse/translator/concerns/image.js +124 -0
  448. package/open-sse/translator/concerns/json.js +5 -0
  449. package/open-sse/translator/concerns/message.js +7 -0
  450. package/open-sse/translator/concerns/modality.js +155 -0
  451. package/open-sse/translator/concerns/paramSupport.js +75 -0
  452. package/open-sse/translator/concerns/prefetch.js +96 -0
  453. package/open-sse/translator/concerns/reasoning.js +24 -0
  454. package/open-sse/translator/concerns/thinking.js +53 -0
  455. package/open-sse/translator/concerns/thinkingUnified.js +329 -0
  456. package/open-sse/translator/concerns/toolCall.js +153 -0
  457. package/open-sse/translator/concerns/usage.js +69 -0
  458. package/open-sse/translator/formats/claude.js +368 -0
  459. package/open-sse/translator/formats/gemini.js +380 -0
  460. package/open-sse/translator/formats/maxTokens.js +34 -0
  461. package/open-sse/translator/formats/openai.js +134 -0
  462. package/open-sse/translator/formats/responsesApi.js +141 -0
  463. package/open-sse/translator/formats.js +36 -0
  464. package/open-sse/translator/helpers/claudeHelper.js +216 -0
  465. package/open-sse/translator/helpers/geminiHelper.js +372 -0
  466. package/open-sse/translator/helpers/imageHelper.js +34 -0
  467. package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
  468. package/open-sse/translator/helpers/openaiHelper.js +130 -0
  469. package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
  470. package/open-sse/translator/helpers/toolCallHelper.js +148 -0
  471. package/open-sse/translator/index.js +252 -0
  472. package/open-sse/translator/request/antigravity-to-openai.js +242 -0
  473. package/open-sse/translator/request/claude-to-kiro.js +478 -0
  474. package/open-sse/translator/request/claude-to-openai.js +263 -0
  475. package/open-sse/translator/request/gemini-to-openai.js +152 -0
  476. package/open-sse/translator/request/openai-responses.js +385 -0
  477. package/open-sse/translator/request/openai-to-claude.js +383 -0
  478. package/open-sse/translator/request/openai-to-commandcode.js +176 -0
  479. package/open-sse/translator/request/openai-to-cursor.js +185 -0
  480. package/open-sse/translator/request/openai-to-gemini.js +442 -0
  481. package/open-sse/translator/request/openai-to-kiro.js +607 -0
  482. package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
  483. package/open-sse/translator/request/openai-to-ollama.js +195 -0
  484. package/open-sse/translator/request/openai-to-vertex.js +42 -0
  485. package/open-sse/translator/response/claude-to-openai.js +195 -0
  486. package/open-sse/translator/response/commandcode-to-openai.js +188 -0
  487. package/open-sse/translator/response/cursor-to-openai.js +30 -0
  488. package/open-sse/translator/response/gemini-to-openai.js +147 -0
  489. package/open-sse/translator/response/kiro-to-claude.js +261 -0
  490. package/open-sse/translator/response/kiro-to-openai.js +160 -0
  491. package/open-sse/translator/response/ollama-to-openai.js +134 -0
  492. package/open-sse/translator/response/openai-responses.js +535 -0
  493. package/open-sse/translator/response/openai-to-antigravity.js +123 -0
  494. package/open-sse/translator/response/openai-to-claude.js +265 -0
  495. package/open-sse/translator/schema/blocks.js +43 -0
  496. package/open-sse/translator/schema/defaults.js +7 -0
  497. package/open-sse/translator/schema/finishReasons.js +27 -0
  498. package/open-sse/translator/schema/index.js +8 -0
  499. package/open-sse/translator/schema/roles.js +16 -0
  500. package/open-sse/utils/agentGoalPolicy.js +85 -0
  501. package/open-sse/utils/aiSdkCompat.js +75 -0
  502. package/open-sse/utils/bypassHandler.js +298 -0
  503. package/open-sse/utils/cacheControlPolicy.js +189 -0
  504. package/open-sse/utils/claudeCloaking.js +155 -0
  505. package/open-sse/utils/claudeHeaderCache.js +70 -0
  506. package/open-sse/utils/claudeSignature.js +41 -0
  507. package/open-sse/utils/clientDetector.js +63 -0
  508. package/open-sse/utils/codebuddySanitizer.js +96 -0
  509. package/open-sse/utils/composerToolCalls.js +158 -0
  510. package/open-sse/utils/cors.js +13 -0
  511. package/open-sse/utils/cursorChecksum.js +149 -0
  512. package/open-sse/utils/cursorImages.js +224 -0
  513. package/open-sse/utils/cursorProtobuf.js +904 -0
  514. package/open-sse/utils/cursorVersionDetector.js +55 -0
  515. package/open-sse/utils/debugLog.js +14 -0
  516. package/open-sse/utils/diagnostics.js +143 -0
  517. package/open-sse/utils/error.js +164 -0
  518. package/open-sse/utils/estimateSize.js +33 -0
  519. package/open-sse/utils/finishReason.js +32 -0
  520. package/open-sse/utils/flattenToolHistory.js +68 -0
  521. package/open-sse/utils/headers.js +36 -0
  522. package/open-sse/utils/heapPressure.js +35 -0
  523. package/open-sse/utils/jsonToSse.js +125 -0
  524. package/open-sse/utils/keepaliveThreshold.js +40 -0
  525. package/open-sse/utils/logger.js +104 -0
  526. package/open-sse/utils/noThinkingAlias.js +74 -0
  527. package/open-sse/utils/number.js +7 -0
  528. package/open-sse/utils/ollamaTransform.js +85 -0
  529. package/open-sse/utils/opencodeHeaders.js +47 -0
  530. package/open-sse/utils/progressTracker.js +81 -0
  531. package/open-sse/utils/providerRequestLogging.js +219 -0
  532. package/open-sse/utils/proxyFetch.js +369 -0
  533. package/open-sse/utils/reasoningContentInjector.js +79 -0
  534. package/open-sse/utils/reasoningFields.js +70 -0
  535. package/open-sse/utils/requestLogger.js +260 -0
  536. package/open-sse/utils/responsesInputNormalization.js +92 -0
  537. package/open-sse/utils/responsesStatePolicy.js +52 -0
  538. package/open-sse/utils/responsesStreamHelpers.js +49 -0
  539. package/open-sse/utils/sessionManager.js +240 -0
  540. package/open-sse/utils/sleep.js +6 -0
  541. package/open-sse/utils/sse.js +14 -0
  542. package/open-sse/utils/sseConstants.js +23 -0
  543. package/open-sse/utils/sseHeartbeat.js +104 -0
  544. package/open-sse/utils/stream.js +517 -0
  545. package/open-sse/utils/streamFailureFinalization.js +134 -0
  546. package/open-sse/utils/streamHandler.js +250 -0
  547. package/open-sse/utils/streamHelpers.js +137 -0
  548. package/open-sse/utils/textualToolCall.js +104 -0
  549. package/open-sse/utils/thinkCloseMarker.js +72 -0
  550. package/open-sse/utils/thinkTagParser.js +146 -0
  551. package/open-sse/utils/toolCallArguments.js +12 -0
  552. package/open-sse/utils/toolDeduper.js +49 -0
  553. package/open-sse/utils/toolSources.js +31 -0
  554. package/open-sse/utils/upstreamResponseHeaders.js +25 -0
  555. package/open-sse/utils/urlSanitize.js +15 -0
  556. package/open-sse/utils/usageTracking.js +347 -0
  557. package/package.json +1 -1
@@ -0,0 +1,2042 @@
1
+ import { BaseExecutor } from "./base.ts";
2
+ import { describeChatGptWebHttpError } from "./chatgptWebErrors.ts";
3
+ import { prepareToolMessages } from "../translator/webTools.ts";
4
+ import { buildToolModeResponse } from "./chatgptWebTools.ts";
5
+ import { createHash, randomUUID, randomBytes } from "node:crypto";
6
+ import { sha3_512Hex } from "../utils/sha3-512.ts";
7
+ import {
8
+ tlsFetchChatGpt,
9
+ TlsClientUnavailableError
10
+ } from "../services/chatgptTlsClient.ts";
11
+ import {
12
+ storeChatGptImage,
13
+ getChatGptImageConversationContext,
14
+ __resetChatGptImageCacheForTesting
15
+ } from "../services/chatgptImageCache.ts";
16
+ import { isThinkingCapableModel, resolveChatGptModel } from "./chatgpt-web/models.ts";
17
+ const CHATGPT_BASE = "https://chatgpt.com";
18
+ const SESSION_URL = `${CHATGPT_BASE}/api/auth/session`;
19
+ const SENTINEL_PREPARE_URL = `${CHATGPT_BASE}/backend-api/sentinel/chat-requirements/prepare`;
20
+ const SENTINEL_CR_URL = `${CHATGPT_BASE}/backend-api/sentinel/chat-requirements`;
21
+ const CONV_URL = `${CHATGPT_BASE}/backend-api/f/conversation`;
22
+ const USER_LAST_USED_MODEL_CONFIG_URL = `${CHATGPT_BASE}/backend-api/settings/user_last_used_model_config`;
23
+ const DEFAULT_PRO_POLL_TIMEOUT_MS = 20 * 6e4;
24
+ const DEFAULT_PRO_POLL_INTERVAL_MS = 4e3;
25
+ const CHATGPT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0";
26
+ const OAI_CLIENT_VERSION = "prod-81e0c5cdf6140e8c5db714d613337f4aeab94029";
27
+ const OAI_CLIENT_BUILD_NUMBER = "6128297";
28
+ const deviceIdCache = /* @__PURE__ */ new Map();
29
+ function deviceIdFor(cookie) {
30
+ const key = cookieKey(cookie);
31
+ let id = deviceIdCache.get(key);
32
+ if (!id) {
33
+ const h = createHash("sha256").update(cookie).digest("hex");
34
+ id = `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-${(parseInt(h.slice(16, 17), 16) & 3 | 8).toString(16)}${h.slice(17, 20)}-` + h.slice(20, 32);
35
+ if (deviceIdCache.size >= 200) {
36
+ const first = deviceIdCache.keys().next().value;
37
+ if (first) deviceIdCache.delete(first);
38
+ }
39
+ deviceIdCache.set(key, id);
40
+ }
41
+ return id;
42
+ }
43
+ function browserHeaders() {
44
+ return {
45
+ Accept: "*/*",
46
+ "Accept-Language": "en-US,en;q=0.9",
47
+ "Cache-Control": "no-cache",
48
+ Origin: CHATGPT_BASE,
49
+ Pragma: "no-cache",
50
+ Referer: `${CHATGPT_BASE}/`,
51
+ "Sec-Fetch-Dest": "empty",
52
+ "Sec-Fetch-Mode": "cors",
53
+ "Sec-Fetch-Site": "same-origin",
54
+ "User-Agent": CHATGPT_USER_AGENT
55
+ };
56
+ }
57
+ function oaiHeaders(sessionId, deviceId) {
58
+ return {
59
+ "OAI-Language": "en-US",
60
+ "OAI-Device-Id": deviceId,
61
+ "OAI-Client-Version": OAI_CLIENT_VERSION,
62
+ "OAI-Client-Build-Number": OAI_CLIENT_BUILD_NUMBER,
63
+ "OAI-Session-Id": sessionId
64
+ };
65
+ }
66
+ const TOKEN_TTL_MS = 5 * 60 * 1e3;
67
+ const tokenCache = /* @__PURE__ */ new Map();
68
+ function cookieKey(cookie) {
69
+ return createHash("sha256").update(cookie).digest("hex").slice(0, 16);
70
+ }
71
+ function tokenLookup(cookie) {
72
+ const entry = tokenCache.get(cookieKey(cookie));
73
+ if (!entry) return null;
74
+ if (Date.now() >= entry.expiresAt) {
75
+ tokenCache.delete(cookieKey(cookie));
76
+ return null;
77
+ }
78
+ return entry;
79
+ }
80
+ const TOKEN_CACHE_MAX = 200;
81
+ function tokenStore(cookie, entry) {
82
+ if (tokenCache.size >= TOKEN_CACHE_MAX && !tokenCache.has(cookieKey(cookie))) {
83
+ const firstKey = tokenCache.keys().next().value;
84
+ if (firstKey) tokenCache.delete(firstKey);
85
+ }
86
+ tokenCache.set(cookieKey(cookie), entry);
87
+ }
88
+ const SESSION_TOKEN_FAMILY_RE = /^__Secure-next-auth\.session-token(?:\.\d+)?$/;
89
+ function mergeRefreshedCookie(originalCookie, setCookieHeader) {
90
+ if (!setCookieHeader) return null;
91
+ const matches = Array.from(
92
+ setCookieHeader.matchAll(/(__Secure-next-auth\.session-token(?:\.\d+)?)=([^;,\s]+)/g)
93
+ );
94
+ if (matches.length === 0) return null;
95
+ const refreshed = /* @__PURE__ */ new Map();
96
+ for (const m of matches) refreshed.set(m[1], m[2]);
97
+ let blob = originalCookie.trim();
98
+ if (/^cookie\s*:\s*/i.test(blob)) blob = blob.replace(/^cookie\s*:\s*/i, "");
99
+ if (!/=/.test(blob)) {
100
+ return Array.from(refreshed, ([k, v]) => `${k}=${v}`).join("; ");
101
+ }
102
+ const pairs = blob.split(/;\s*/).filter(Boolean);
103
+ const result = [];
104
+ let mutated = false;
105
+ let droppedStale = false;
106
+ for (const pair of pairs) {
107
+ const eqIdx = pair.indexOf("=");
108
+ if (eqIdx < 0) {
109
+ result.push(pair);
110
+ continue;
111
+ }
112
+ const name = pair.slice(0, eqIdx).trim();
113
+ const value = pair.slice(eqIdx + 1);
114
+ if (SESSION_TOKEN_FAMILY_RE.test(name)) {
115
+ if (!refreshed.has(name) || refreshed.get(name) !== value) mutated = true;
116
+ droppedStale = true;
117
+ continue;
118
+ }
119
+ result.push(`${name}=${value}`);
120
+ }
121
+ for (const [name, value] of refreshed) {
122
+ result.push(`${name}=${value}`);
123
+ }
124
+ if (!droppedStale) mutated = true;
125
+ return mutated ? result.join("; ") : null;
126
+ }
127
+ function buildSessionCookieHeader(rawInput) {
128
+ let s = rawInput.trim();
129
+ if (/^cookie\s*:\s*/i.test(s)) s = s.replace(/^cookie\s*:\s*/i, "");
130
+ if (/__Secure-next-auth\.session-token(?:\.\d+)?\s*=/.test(s)) {
131
+ return s;
132
+ }
133
+ return `__Secure-next-auth.session-token=${s}`;
134
+ }
135
+ async function exchangeSession(cookie, signal) {
136
+ const cached = tokenLookup(cookie);
137
+ if (cached) return cached;
138
+ const headers = {
139
+ ...browserHeaders(),
140
+ Accept: "application/json",
141
+ Cookie: buildSessionCookieHeader(cookie)
142
+ };
143
+ const response = await tlsFetchChatGpt(SESSION_URL, {
144
+ method: "GET",
145
+ headers,
146
+ timeoutMs: 3e4,
147
+ signal
148
+ });
149
+ if (response.status === 401 || response.status === 403) {
150
+ throw new SessionAuthError("Invalid session cookie");
151
+ }
152
+ if (response.status >= 400) {
153
+ throw new Error(`Session exchange failed (HTTP ${response.status})`);
154
+ }
155
+ const refreshed = mergeRefreshedCookie(cookie, response.headers.get("set-cookie"));
156
+ let data = {};
157
+ try {
158
+ data = JSON.parse(response.text || "{}");
159
+ } catch {
160
+ console.warn("[chatgpt-web] session response JSON parse failed");
161
+ }
162
+ if (!data.accessToken) {
163
+ throw new SessionAuthError("Session response missing accessToken \u2014 cookie likely expired");
164
+ }
165
+ const expiresAt = data.expires ? new Date(data.expires).getTime() : Date.now() + TOKEN_TTL_MS;
166
+ const entry = {
167
+ accessToken: data.accessToken,
168
+ accountId: data.user?.id ?? null,
169
+ expiresAt: Math.min(expiresAt, Date.now() + TOKEN_TTL_MS),
170
+ refreshedCookie: refreshed ?? void 0
171
+ };
172
+ tokenStore(cookie, entry);
173
+ return entry;
174
+ }
175
+ class SessionAuthError extends Error {
176
+ constructor(message) {
177
+ super(message);
178
+ this.name = "SessionAuthError";
179
+ }
180
+ }
181
+ const warmupCache = /* @__PURE__ */ new Map();
182
+ const WARMUP_TTL_MS = 6e4;
183
+ const WARMUP_CACHE_MAX = 200;
184
+ async function runSessionWarmup(accessToken, accountId, sessionId, deviceId, cookie, signal, log) {
185
+ const key = cookieKey(cookie) + ":" + accessToken.slice(-8);
186
+ const now = Date.now();
187
+ const last = warmupCache.get(key);
188
+ if (last && now - last < WARMUP_TTL_MS) return;
189
+ if (warmupCache.size >= WARMUP_CACHE_MAX && !warmupCache.has(key)) {
190
+ const first = warmupCache.keys().next().value;
191
+ if (first) warmupCache.delete(first);
192
+ }
193
+ warmupCache.set(key, now);
194
+ const headers = {
195
+ ...browserHeaders(),
196
+ ...oaiHeaders(sessionId, deviceId),
197
+ Accept: "*/*",
198
+ Authorization: `Bearer ${accessToken}`,
199
+ Cookie: buildSessionCookieHeader(cookie),
200
+ Priority: "u=1, i"
201
+ };
202
+ if (accountId) headers["chatgpt-account-id"] = accountId;
203
+ const urls = [
204
+ `${CHATGPT_BASE}/backend-api/me`,
205
+ `${CHATGPT_BASE}/backend-api/conversations?offset=0&limit=28&order=updated`,
206
+ `${CHATGPT_BASE}/backend-api/models?history_and_training_disabled=false`
207
+ ];
208
+ for (const url of urls) {
209
+ try {
210
+ const r = await tlsFetchChatGpt(url, {
211
+ method: "GET",
212
+ headers,
213
+ timeoutMs: 15e3,
214
+ signal
215
+ });
216
+ log?.debug?.("CGPT-WEB", `warmup ${url.split("/backend-api/")[1]} \u2192 ${r.status}`);
217
+ } catch (err) {
218
+ log?.debug?.(
219
+ "CGPT-WEB",
220
+ `warmup ${url} failed: ${err instanceof Error ? err.message : String(err)}`
221
+ );
222
+ }
223
+ }
224
+ }
225
+ const thinkingEffortCache = /* @__PURE__ */ new Map();
226
+ const THINKING_EFFORT_TTL_MS = 5 * 60 * 1e3;
227
+ const THINKING_EFFORT_CACHE_MAX = 400;
228
+ function configuredProPollTimeoutMs() {
229
+ const raw = Number(process.env.OMNIROUTE_CGPT_WEB_PRO_TIMEOUT_MS);
230
+ if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_PRO_POLL_TIMEOUT_MS;
231
+ return Math.floor(raw);
232
+ }
233
+ function configuredProPollIntervalMs() {
234
+ const raw = Number(process.env.OMNIROUTE_CGPT_WEB_PRO_POLL_INTERVAL_MS);
235
+ if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_PRO_POLL_INTERVAL_MS;
236
+ return Math.floor(raw);
237
+ }
238
+ async function setUserThinkingEffort(modelSlug, effort, accessToken, accountId, sessionId, deviceId, cookie, signal, log) {
239
+ const cacheKey = `${cookieKey(cookie)}:${modelSlug}:${effort}`;
240
+ const now = Date.now();
241
+ const last = thinkingEffortCache.get(cacheKey);
242
+ if (last && now - last < THINKING_EFFORT_TTL_MS) {
243
+ log?.debug?.("CGPT-WEB", `thinking_effort cached (${modelSlug}=${effort}) \u2014 skip PATCH`);
244
+ return;
245
+ }
246
+ if (thinkingEffortCache.size >= THINKING_EFFORT_CACHE_MAX && !thinkingEffortCache.has(cacheKey)) {
247
+ const first = thinkingEffortCache.keys().next().value;
248
+ if (first) thinkingEffortCache.delete(first);
249
+ }
250
+ const url = `${USER_LAST_USED_MODEL_CONFIG_URL}?model_slug=${encodeURIComponent(modelSlug)}&thinking_effort=${encodeURIComponent(effort)}`;
251
+ const headers = {
252
+ ...browserHeaders(),
253
+ ...oaiHeaders(sessionId, deviceId),
254
+ Accept: "application/json",
255
+ Authorization: `Bearer ${accessToken}`,
256
+ Cookie: buildSessionCookieHeader(cookie),
257
+ Priority: "u=4"
258
+ };
259
+ if (accountId) headers["chatgpt-account-id"] = accountId;
260
+ try {
261
+ const r = await tlsFetchChatGpt(url, {
262
+ method: "PATCH",
263
+ headers,
264
+ timeoutMs: 15e3,
265
+ signal
266
+ });
267
+ if (r.status >= 400) {
268
+ log?.warn?.(
269
+ "CGPT-WEB",
270
+ `thinking_effort PATCH ${r.status} for ${modelSlug}=${effort} (continuing)`
271
+ );
272
+ return;
273
+ }
274
+ thinkingEffortCache.set(cacheKey, now);
275
+ log?.debug?.("CGPT-WEB", `thinking_effort PATCH OK (${modelSlug}=${effort})`);
276
+ } catch (err) {
277
+ log?.warn?.(
278
+ "CGPT-WEB",
279
+ `thinking_effort PATCH failed: ${err instanceof Error ? err.message : String(err)}`
280
+ );
281
+ }
282
+ }
283
+ async function prepareChatRequirements(accessToken, accountId, sessionId, deviceId, cookie, dplInfo, signal, log) {
284
+ const config = buildPrekeyConfig(CHATGPT_USER_AGENT, dplInfo.dpl, dplInfo.scriptSrc);
285
+ const prekey = await buildPrepareToken(config, log);
286
+ const headers = {
287
+ ...browserHeaders(),
288
+ ...oaiHeaders(sessionId, deviceId),
289
+ "Content-Type": "application/json",
290
+ Authorization: `Bearer ${accessToken}`,
291
+ Cookie: buildSessionCookieHeader(cookie),
292
+ Priority: "u=1, i"
293
+ };
294
+ if (accountId) headers["chatgpt-account-id"] = accountId;
295
+ const prepResp = await tlsFetchChatGpt(SENTINEL_PREPARE_URL, {
296
+ method: "POST",
297
+ headers,
298
+ body: JSON.stringify({ p: prekey }),
299
+ timeoutMs: 3e4,
300
+ signal
301
+ });
302
+ if (prepResp.status === 401 || prepResp.status === 403) {
303
+ throw new SentinelBlockedError(`Sentinel /prepare blocked (HTTP ${prepResp.status})`);
304
+ }
305
+ if (prepResp.status >= 400) {
306
+ throw new Error(`Sentinel /prepare failed (HTTP ${prepResp.status})`);
307
+ }
308
+ let prepData = {};
309
+ try {
310
+ prepData = JSON.parse(prepResp.text || "{}");
311
+ } catch {
312
+ console.warn("[chatgpt-web] chat requirements prep JSON parse failed");
313
+ }
314
+ if (!prepData.prepare_token) {
315
+ return prepData;
316
+ }
317
+ const crBody = { p: prekey, prepare_token: prepData.prepare_token };
318
+ const crResp = await tlsFetchChatGpt(SENTINEL_CR_URL, {
319
+ method: "POST",
320
+ headers,
321
+ body: JSON.stringify(crBody),
322
+ timeoutMs: 3e4,
323
+ signal
324
+ });
325
+ if (crResp.status === 401 || crResp.status === 403) {
326
+ throw new SentinelBlockedError(`Sentinel /chat-requirements blocked (HTTP ${crResp.status})`);
327
+ }
328
+ if (crResp.status >= 400) {
329
+ return prepData;
330
+ }
331
+ try {
332
+ const crData = JSON.parse(crResp.text || "{}");
333
+ return { ...crData, prepare_token: prepData.prepare_token };
334
+ } catch {
335
+ console.warn("[chatgpt-web] chat requirements response JSON parse failed");
336
+ return prepData;
337
+ }
338
+ }
339
+ class SentinelBlockedError extends Error {
340
+ constructor(message) {
341
+ super(message);
342
+ this.name = "SentinelBlockedError";
343
+ }
344
+ }
345
+ let dplCache = null;
346
+ const DPL_TTL_MS = 60 * 60 * 1e3;
347
+ async function fetchDpl(cookie, signal) {
348
+ if (dplCache && Date.now() < dplCache.expiresAt) {
349
+ return { dpl: dplCache.dpl, scriptSrc: dplCache.scriptSrc };
350
+ }
351
+ const headers = {
352
+ ...browserHeaders(),
353
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
354
+ Cookie: buildSessionCookieHeader(cookie)
355
+ };
356
+ const response = await tlsFetchChatGpt(`${CHATGPT_BASE}/`, {
357
+ method: "GET",
358
+ headers,
359
+ timeoutMs: 2e4,
360
+ signal
361
+ });
362
+ const html = response.text || "";
363
+ const dplMatch = html.match(/data-build="([^"]+)"/);
364
+ const dpl = dplMatch ? `dpl=${dplMatch[1]}` : `dpl=${OAI_CLIENT_VERSION.replace(/^prod-/, "")}`;
365
+ const scriptMatch = html.match(/<script[^>]+src="(https?:\/\/[^"]*\.js[^"]*)"/);
366
+ const scriptSrc = scriptMatch?.[1] ?? `${CHATGPT_BASE}/_next/static/chunks/webpack-${randomHex(16)}.js`;
367
+ dplCache = { dpl, scriptSrc, expiresAt: Date.now() + DPL_TTL_MS };
368
+ return { dpl, scriptSrc };
369
+ }
370
+ function randomHex(n) {
371
+ return randomBytes(Math.ceil(n / 2)).toString("hex").slice(0, n);
372
+ }
373
+ const NAVIGATOR_KEYS = [
374
+ "webdriver\u2212false",
375
+ "geolocation",
376
+ "languages",
377
+ "language",
378
+ "platform",
379
+ "userAgent",
380
+ "vendor",
381
+ "hardwareConcurrency",
382
+ "deviceMemory",
383
+ "permissions",
384
+ "plugins",
385
+ "mediaDevices"
386
+ ];
387
+ const DOCUMENT_KEYS = [
388
+ "_reactListeningkfj3eavmks",
389
+ "_reactListeningo743lnnpvdg",
390
+ "location",
391
+ "scrollingElement",
392
+ "documentElement"
393
+ ];
394
+ const WINDOW_KEYS = [
395
+ "webpackChunk_N_E",
396
+ "__NEXT_DATA__",
397
+ "chrome",
398
+ "history",
399
+ "screen",
400
+ "navigation",
401
+ "scrollX",
402
+ "scrollY"
403
+ ];
404
+ function pick(arr) {
405
+ return arr[Math.floor(Math.random() * arr.length)];
406
+ }
407
+ function buildPrekeyConfig(userAgent, dpl, scriptSrc) {
408
+ const screenSizes = [3e3, 4e3, 3120, 4160];
409
+ const cores = [8, 16, 24, 32];
410
+ const dateStr = (/* @__PURE__ */ new Date()).toString();
411
+ const perfNow = performance.now();
412
+ const epochOffset = Date.now() - perfNow;
413
+ return [
414
+ pick(screenSizes),
415
+ dateStr,
416
+ 4294705152,
417
+ 0,
418
+ // mutated by solver
419
+ userAgent,
420
+ scriptSrc,
421
+ dpl,
422
+ "en-US",
423
+ "en-US,en",
424
+ 0,
425
+ // mutated by solver
426
+ pick(NAVIGATOR_KEYS),
427
+ pick(DOCUMENT_KEYS),
428
+ pick(WINDOW_KEYS),
429
+ perfNow,
430
+ randomUUID(),
431
+ "",
432
+ pick(cores),
433
+ epochOffset
434
+ ];
435
+ }
436
+ const POW_YIELD_EVERY = 1e3;
437
+ function yieldToEventLoop() {
438
+ return new Promise((resolve) => setImmediate(resolve));
439
+ }
440
+ async function solvePow(opts) {
441
+ const cfg = [...opts.config];
442
+ for (let i = 0; i < opts.maxIter; i++) {
443
+ if (i > 0 && i % POW_YIELD_EVERY === 0) await yieldToEventLoop();
444
+ cfg[3] = i;
445
+ const json = JSON.stringify(cfg);
446
+ const b642 = Buffer.from(json).toString("base64");
447
+ const hash = sha3_512Hex(opts.seed + b642);
448
+ if (opts.target && hash.slice(0, opts.target.length) <= opts.target) {
449
+ return `${opts.prefix}${b642}`;
450
+ }
451
+ }
452
+ opts.log?.warn?.(
453
+ "CGPT-WEB",
454
+ `PoW (${opts.label}) exhausted ${opts.maxIter} iterations against target=${opts.target || "<empty>"}; submitting unsolved token (Sentinel may reject)`
455
+ );
456
+ const b64 = Buffer.from(JSON.stringify(cfg)).toString("base64");
457
+ return `${opts.prefix}${b64}`;
458
+ }
459
+ async function buildPrepareToken(config, log) {
460
+ return solvePow({
461
+ config,
462
+ seed: "",
463
+ target: "0fffff",
464
+ prefix: "gAAAAAC",
465
+ maxIter: 1e5,
466
+ label: "prepare",
467
+ log
468
+ });
469
+ }
470
+ async function solveProofOfWork(seed, difficulty, config, log) {
471
+ return solvePow({
472
+ config,
473
+ seed,
474
+ target: (difficulty || "").toLowerCase(),
475
+ prefix: "gAAAAAB",
476
+ maxIter: 5e5,
477
+ label: "conversation",
478
+ log
479
+ });
480
+ }
481
+ const DATA_URI_IMAGE_RE = /!\[([^\]]*)\]\(data:image\/[^)]+\)/g;
482
+ const CACHED_IMAGE_URL_RE = /\/v1\/chatgpt-web\/image\/([a-f0-9]{16,64})(?=[)\s"'<>]|$)/gi;
483
+ function stripInlinedImages(content) {
484
+ return content.replace(
485
+ DATA_URI_IMAGE_RE,
486
+ (_, alt) => alt ? `[${alt}: generated image]` : "[generated image]"
487
+ );
488
+ }
489
+ function findCachedImageContext(content) {
490
+ let latest = null;
491
+ for (const match of content.matchAll(CACHED_IMAGE_URL_RE)) {
492
+ const id = match[1];
493
+ const context = getChatGptImageConversationContext(id);
494
+ if (context) latest = context;
495
+ }
496
+ return latest;
497
+ }
498
+ function parseOpenAIMessages(messages) {
499
+ let systemMsg = "";
500
+ const history = [];
501
+ let latestImageContext = null;
502
+ for (const msg of messages) {
503
+ let role = String(msg.role || "user");
504
+ if (role === "developer") role = "system";
505
+ let content = "";
506
+ if (typeof msg.content === "string") {
507
+ content = msg.content;
508
+ } else if (Array.isArray(msg.content)) {
509
+ content = msg.content.filter((c) => c.type === "text").map((c) => String(c.text || "")).join(" ");
510
+ }
511
+ content = stripInlinedImages(content);
512
+ const imageContext = findCachedImageContext(content);
513
+ if (imageContext) latestImageContext = imageContext;
514
+ if (!content.trim()) continue;
515
+ if (role === "system") {
516
+ systemMsg += (systemMsg ? "\n" : "") + content;
517
+ } else if (role === "user" || role === "assistant") {
518
+ history.push({ role, content });
519
+ }
520
+ }
521
+ let currentMsg = "";
522
+ if (history.length > 0 && history[history.length - 1].role === "user") {
523
+ currentMsg = history.pop().content;
524
+ }
525
+ return { systemMsg, history, currentMsg, latestImageContext };
526
+ }
527
+ const IMAGE_GEN_REGEXES = [
528
+ // verb + (anything within 40 chars) + image-noun
529
+ /\b(?:generate|create|make|draw|paint|render|produce|design|sketch|illustrate|show me)\b[\s\S]{0,40}\b(?:image|picture|photo|photograph|drawing|illustration|sketch|painting|portrait|logo|icon|art|artwork|wallpaper|render|graphic)\b/i,
530
+ // image-noun + "of" — "image of a kitten", "picture of mountains"
531
+ /\b(?:image|picture|photo|photograph|illustration|drawing|painting|render)\s+of\b/i,
532
+ // direct verb + a/an article — "draw a kitten", "paint an apple"
533
+ /\b(?:draw|paint|sketch|render|illustrate)\s+(?:me\s+)?(?:a|an|some|the)\s+\w+/i,
534
+ // explicit slash command users sometimes type — "/imagine ..."
535
+ /^\s*\/(?:image|imagine|img|draw|paint)\b/im
536
+ ];
537
+ const OPENWEBUI_TOOL_PROMPT_MARKERS = [
538
+ /<chat_history>/i,
539
+ /^### Task:/im,
540
+ /\bJSON format:\s*\{/i,
541
+ /\bfollow_?ups\b.*\barray of strings\b/i
542
+ ];
543
+ const OPENWEBUI_IMAGE_CONTEXT_MARKERS = [
544
+ /<context>\s*The requested image has been (?:created|edited and created) by the system successfully/i,
545
+ /<context>\s*The requested image has been edited and created and is now being shown to the user/i,
546
+ /<context>\s*Image generation was attempted but failed/i
547
+ ];
548
+ function hasOpenWebUIImageContext(parsed) {
549
+ return OPENWEBUI_IMAGE_CONTEXT_MARKERS.some((re) => re.test(parsed.systemMsg));
550
+ }
551
+ function looksLikeImageGenRequest(parsed) {
552
+ const text = parsed.currentMsg.trim();
553
+ if (!text) return false;
554
+ if (OPENWEBUI_TOOL_PROMPT_MARKERS.some((re) => re.test(text))) return false;
555
+ if (hasOpenWebUIImageContext(parsed)) return false;
556
+ return IMAGE_GEN_REGEXES.some((re) => re.test(text));
557
+ }
558
+ const IMAGE_EDIT_REGEXES = [
559
+ /\b(?:edit|adjust|modify|change|update|alter|revise|retouch|fix)\b[\s\S]{0,120}\b(?:it|image|picture|photo|lighting|background|style|color|colour|composition|scene|time of day)\b/i,
560
+ /\b(?:make|turn|set|switch)\s+(?:it|the\s+(?:image|picture|photo|scene))\b[\s\S]{0,120}\b/i,
561
+ /\b(?:add|remove|replace)\b[\s\S]{0,120}\b(?:it|image|picture|photo|background|sky|person|object|text|logo)\b/i,
562
+ /\b(?:brighter|darker|night|daytime|time of day|sunset|sunrise|morning|evening|lighting|relight|background|style)\b/i,
563
+ /^\s*(?:now|then|also)\b[\s\S]{0,120}\b(?:make|turn|change|adjust|add|remove|replace|edit)\b/i
564
+ ];
565
+ function looksLikeImageEditRequest(parsed) {
566
+ if (!parsed.latestImageContext) return false;
567
+ const text = parsed.currentMsg.trim();
568
+ if (!text) return false;
569
+ if (OPENWEBUI_TOOL_PROMPT_MARKERS.some((re) => re.test(text))) return false;
570
+ if (hasOpenWebUIImageContext(parsed)) return false;
571
+ return IMAGE_EDIT_REGEXES.some((re) => re.test(text));
572
+ }
573
+ function buildConversationBody(parsed, modelSlug, parentMessageId, options) {
574
+ const systemParts = [];
575
+ if (parsed.systemMsg.trim()) {
576
+ systemParts.push(parsed.systemMsg.trim());
577
+ }
578
+ const continuation = options.continuation ?? null;
579
+ if (!continuation && parsed.history.length > 0) {
580
+ const formatted = parsed.history.map((h) => `${h.role === "assistant" ? "Assistant" : "User"}: ${h.content}`).join("\n\n");
581
+ systemParts.push(
582
+ `Prior conversation (for context \u2014 answer only the new user message below):
583
+
584
+ ${formatted}`
585
+ );
586
+ }
587
+ const messages = [];
588
+ if (systemParts.length > 0) {
589
+ messages.push({
590
+ id: randomUUID(),
591
+ author: { role: "system" },
592
+ content: { content_type: "text", parts: [systemParts.join("\n\n")] }
593
+ });
594
+ }
595
+ const currentUserContent = hasOpenWebUIImageContext(parsed) ? "Briefly acknowledge the image result described in the system context. Do not generate, edit, or request another image." : parsed.currentMsg || "";
596
+ messages.push({
597
+ id: randomUUID(),
598
+ author: { role: "user" },
599
+ content: { content_type: "text", parts: [currentUserContent] }
600
+ });
601
+ return {
602
+ action: "next",
603
+ messages,
604
+ model: modelSlug,
605
+ // Text-only API-style requests start fresh because clients replay full
606
+ // history. Generated-image edits are the exception: ChatGPT needs the
607
+ // original conversation node to adjust the actual image, not just a
608
+ // markdown URL echoed back in a synthetic history block.
609
+ conversation_id: continuation?.conversationId ?? null,
610
+ parent_message_id: continuation?.parentMessageId ?? parentMessageId,
611
+ timezone_offset_min: -(/* @__PURE__ */ new Date()).getTimezoneOffset(),
612
+ // Temporary Chat is the default. Disable it only for image generation /
613
+ // image edits, where ChatGPT needs durable conversation state for tools.
614
+ history_and_training_disabled: !options.persistConversation,
615
+ suggestions: [],
616
+ websocket_request_id: randomUUID(),
617
+ conversation_mode: { kind: "primary_assistant" },
618
+ supports_buffering: true,
619
+ force_parallel_switch: "auto",
620
+ paragen_cot_summary_display_override: "allow",
621
+ ...options.thinkingEffort ? { thinking_effort: options.thinkingEffort } : {}
622
+ };
623
+ }
624
+ async function* readChatGptSseEvents(body, signal) {
625
+ const reader = body.getReader();
626
+ const decoder = new TextDecoder();
627
+ let buffer = "";
628
+ let dataLines = [];
629
+ let eventName = null;
630
+ function flush() {
631
+ if (dataLines.length === 0) {
632
+ eventName = null;
633
+ return null;
634
+ }
635
+ const payload = dataLines.join("\n");
636
+ dataLines = [];
637
+ const sseEventName = eventName;
638
+ eventName = null;
639
+ const trimmed = payload.trim();
640
+ if (!trimmed || trimmed === "[DONE]") return "done";
641
+ try {
642
+ const parsed = JSON.parse(trimmed);
643
+ if (sseEventName && !parsed.type) parsed.type = sseEventName;
644
+ return parsed;
645
+ } catch {
646
+ console.warn("[chatgpt-web] stream event JSON parse failed");
647
+ return null;
648
+ }
649
+ }
650
+ try {
651
+ while (true) {
652
+ if (signal?.aborted) return;
653
+ const { value, done } = await reader.read();
654
+ if (done) break;
655
+ buffer += decoder.decode(value, { stream: true });
656
+ while (true) {
657
+ const idx = buffer.indexOf("\n");
658
+ if (idx < 0) break;
659
+ const rawLine = buffer.slice(0, idx);
660
+ buffer = buffer.slice(idx + 1);
661
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
662
+ if (line === "") {
663
+ const parsed = flush();
664
+ if (parsed === "done") return;
665
+ if (parsed) yield parsed;
666
+ continue;
667
+ }
668
+ if (line.startsWith("event:")) {
669
+ eventName = line.slice(6).trim();
670
+ } else if (line.startsWith("data:")) {
671
+ dataLines.push(line.slice(5).trimStart());
672
+ }
673
+ }
674
+ }
675
+ buffer += decoder.decode();
676
+ if (buffer.trim().startsWith("data:")) {
677
+ dataLines.push(buffer.trim().slice(5).trimStart());
678
+ }
679
+ const tail = flush();
680
+ if (tail && tail !== "done") yield tail;
681
+ } finally {
682
+ reader.releaseLock();
683
+ }
684
+ }
685
+ function extractImagePointers(parts) {
686
+ const out = [];
687
+ for (const p of parts) {
688
+ if (!p || typeof p !== "object") continue;
689
+ const obj = p;
690
+ if (obj.content_type === "image_asset_pointer" && typeof obj.asset_pointer === "string") {
691
+ out.push(obj.asset_pointer);
692
+ }
693
+ }
694
+ return out;
695
+ }
696
+ async function* extractContent(eventStream, signal) {
697
+ let conversationId = null;
698
+ let currentId = null;
699
+ let currentParts = "";
700
+ let emittedLen = 0;
701
+ let isLive = false;
702
+ const imagePointers = /* @__PURE__ */ new Map();
703
+ let imageGenAsync = false;
704
+ let handoff = false;
705
+ for await (const event of readChatGptSseEvents(eventStream, signal)) {
706
+ if (event.error) {
707
+ const msg = typeof event.error === "string" ? event.error : event.error.message || "ChatGPT stream error";
708
+ yield { error: msg, done: true };
709
+ return;
710
+ }
711
+ if (event.conversation_id) conversationId = event.conversation_id;
712
+ if (event.type === "stream_handoff") {
713
+ handoff = true;
714
+ yield {
715
+ conversationId: conversationId ?? void 0,
716
+ handoff: true
717
+ };
718
+ continue;
719
+ }
720
+ if (event.type === "server_ste_metadata") {
721
+ const meta = event.metadata;
722
+ if (meta && meta.turn_use_case === "image gen") {
723
+ imageGenAsync = true;
724
+ }
725
+ }
726
+ const m = event.message;
727
+ if (!m) continue;
728
+ if (m.metadata && typeof m.metadata.image_gen_task_id === "string") {
729
+ imageGenAsync = true;
730
+ }
731
+ if (m.author?.role !== "assistant") continue;
732
+ const id = m.id ?? null;
733
+ const status = m.status ?? "";
734
+ if (id && id !== currentId) {
735
+ currentId = id;
736
+ currentParts = "";
737
+ emittedLen = 0;
738
+ isLive = false;
739
+ }
740
+ if (status === "in_progress") {
741
+ isLive = true;
742
+ }
743
+ const parts = m.content?.parts ?? [];
744
+ if (parts.length === 0) continue;
745
+ if (status === "finished_successfully" || status === "" || isLive) {
746
+ for (const ptr of extractImagePointers(parts)) {
747
+ const existing = imagePointers.get(ptr);
748
+ imagePointers.set(
749
+ ptr,
750
+ existing?.messageId ? existing : { pointer: ptr, ...id ? { messageId: id } : {} }
751
+ );
752
+ }
753
+ }
754
+ const cumulative = parts.map((p) => typeof p === "string" ? p : "").join("");
755
+ if (cumulative.length > currentParts.length) {
756
+ currentParts = cumulative;
757
+ }
758
+ if (isLive && currentParts.length > emittedLen) {
759
+ const delta = currentParts.slice(emittedLen);
760
+ emittedLen = currentParts.length;
761
+ yield {
762
+ delta,
763
+ answer: currentParts,
764
+ conversationId: conversationId ?? void 0,
765
+ messageId: currentId ?? void 0
766
+ };
767
+ }
768
+ }
769
+ if (!isLive && currentParts.length > emittedLen) {
770
+ yield {
771
+ delta: currentParts.slice(emittedLen),
772
+ answer: currentParts,
773
+ conversationId: conversationId ?? void 0,
774
+ messageId: currentId ?? void 0
775
+ };
776
+ }
777
+ yield {
778
+ delta: "",
779
+ answer: currentParts,
780
+ conversationId: conversationId ?? void 0,
781
+ messageId: currentId ?? void 0,
782
+ imagePointers: imagePointers.size > 0 ? Array.from(imagePointers.values()) : void 0,
783
+ imageGenAsync,
784
+ handoff,
785
+ done: true
786
+ };
787
+ }
788
+ function textFromContentPart(part) {
789
+ if (typeof part === "string") return part;
790
+ if (!part || typeof part !== "object") return "";
791
+ const obj = part;
792
+ for (const key of ["text", "content", "summary"]) {
793
+ const value = obj[key];
794
+ if (typeof value === "string") return value;
795
+ }
796
+ return "";
797
+ }
798
+ function detailMessageText(message) {
799
+ const content = message.content;
800
+ if (!content) return "";
801
+ if (typeof content.text === "string") return content.text;
802
+ const parts = content.parts ?? [];
803
+ return parts.map(textFromContentPart).join("");
804
+ }
805
+ function extractFinalAssistantAnswer(detail) {
806
+ const nodes = Object.values(detail.mapping ?? {});
807
+ let best = null;
808
+ for (const node of nodes) {
809
+ const message = node.message;
810
+ if (!message || message.author?.role !== "assistant") continue;
811
+ if (message.metadata?.is_visually_hidden === true) continue;
812
+ const contentType = message.content?.content_type ?? "";
813
+ if (contentType.includes("thought") || contentType.includes("reasoning")) continue;
814
+ const text = detailMessageText(message).trim();
815
+ if (!text) continue;
816
+ const finished = message.status === "finished_successfully" && message.end_turn !== false;
817
+ const sort = message.update_time ?? message.create_time ?? 0;
818
+ if (!best || finished && (!best.finished || sort >= best.sort) || !finished && !best.finished && sort >= best.sort) {
819
+ best = { text, messageId: message.id, finished, sort };
820
+ }
821
+ }
822
+ if (!best) return null;
823
+ return { text: best.text, messageId: best.messageId, finished: best.finished };
824
+ }
825
+ function delayWithAbort(ms, signal) {
826
+ if (ms <= 0) return Promise.resolve();
827
+ if (signal?.aborted) return Promise.resolve();
828
+ return new Promise((resolve) => {
829
+ const timer = setTimeout(() => {
830
+ signal?.removeEventListener("abort", onAbort);
831
+ resolve();
832
+ }, ms);
833
+ const onAbort = () => {
834
+ clearTimeout(timer);
835
+ signal?.removeEventListener("abort", onAbort);
836
+ resolve();
837
+ };
838
+ signal?.addEventListener("abort", onAbort, { once: true });
839
+ });
840
+ }
841
+ function decodeUtf8DataUrl(text) {
842
+ const marker = ";base64,";
843
+ if (!text.startsWith("data:") || !text.includes(marker)) return text;
844
+ const base64 = text.slice(text.indexOf(marker) + marker.length);
845
+ return new TextDecoder().decode(Buffer.from(base64, "base64"));
846
+ }
847
+ async function fetchConversationDetail(conversationId, ctx) {
848
+ const url = `${CHATGPT_BASE}/backend-api/conversation/${encodeURIComponent(conversationId)}`;
849
+ const headers = {
850
+ ...browserHeaders(),
851
+ ...oaiHeaders(ctx.sessionId, ctx.deviceId),
852
+ Accept: "application/json",
853
+ Authorization: `Bearer ${ctx.accessToken}`,
854
+ Cookie: buildSessionCookieHeader(ctx.cookie)
855
+ };
856
+ if (ctx.accountId) headers["chatgpt-account-id"] = ctx.accountId;
857
+ try {
858
+ const response = await tlsFetchChatGpt(url, {
859
+ method: "GET",
860
+ headers,
861
+ timeoutMs: 3e4,
862
+ signal: ctx.signal,
863
+ // The native tls-client text path can surface UTF-8 JSON as mojibake
864
+ // (e.g. 👉 becomes 👉). Ask for raw bytes and decode as UTF-8 here so
865
+ // the final answer appended after Pro stream_handoff preserves Unicode.
866
+ byteResponse: true
867
+ });
868
+ if (response.status >= 400) {
869
+ ctx.log?.warn?.(
870
+ "CGPT-WEB",
871
+ `conversation poll ${response.status}: ${(response.text || "").slice(0, 300)}`
872
+ );
873
+ return { detail: null, terminal: [401, 403, 404].includes(response.status) };
874
+ }
875
+ if (!response.text) return { detail: null, terminal: false };
876
+ return {
877
+ detail: JSON.parse(decodeUtf8DataUrl(response.text)),
878
+ terminal: false
879
+ };
880
+ } catch (err) {
881
+ ctx.log?.warn?.(
882
+ "CGPT-WEB",
883
+ `conversation poll failed: ${err instanceof Error ? err.message : String(err)}`
884
+ );
885
+ return { detail: null, terminal: false };
886
+ }
887
+ }
888
+ async function pollForFinalAssistantAnswer(conversationId, ctx) {
889
+ const started = Date.now();
890
+ const timeoutMs = configuredProPollTimeoutMs();
891
+ const intervalMs = configuredProPollIntervalMs();
892
+ let last = null;
893
+ let terminalPollFailure = false;
894
+ while (!ctx.signal?.aborted && Date.now() - started < timeoutMs) {
895
+ const { detail, terminal } = await fetchConversationDetail(conversationId, ctx);
896
+ if (detail) {
897
+ const answer = extractFinalAssistantAnswer(detail);
898
+ if (answer) {
899
+ last = answer;
900
+ if (answer.finished) return answer;
901
+ }
902
+ }
903
+ if (terminal) {
904
+ terminalPollFailure = true;
905
+ break;
906
+ }
907
+ const remaining = timeoutMs - (Date.now() - started);
908
+ if (remaining <= 0) break;
909
+ await delayWithAbort(Math.min(intervalMs, remaining), ctx.signal);
910
+ }
911
+ if (last) {
912
+ ctx.log?.warn?.(
913
+ "CGPT-WEB",
914
+ terminalPollFailure ? `conversation poll stopped before finished_successfully; returning latest assistant text for ${conversationId}` : `conversation poll timed out before finished_successfully; returning latest assistant text for ${conversationId}`
915
+ );
916
+ } else {
917
+ ctx.log?.warn?.(
918
+ "CGPT-WEB",
919
+ terminalPollFailure ? `conversation poll stopped without assistant text for ${conversationId}` : `conversation poll timed out without assistant text for ${conversationId}`
920
+ );
921
+ }
922
+ return last;
923
+ }
924
+ function sseChunk(data) {
925
+ return `data: ${JSON.stringify(data)}
926
+
927
+ `;
928
+ }
929
+ function detectImageResolutionFailure(pointerCount, resolvedCount) {
930
+ return pointerCount > 0 && resolvedCount === 0;
931
+ }
932
+ function imageMarkdown(urls) {
933
+ if (urls.length === 0) return "";
934
+ return "\n\n" + urls.map((u) => `![image](${u})`).join("\n\n");
935
+ }
936
+ async function resolveImagePointers(pointers, conversationId, resolver, log, fallbackParentMessageId) {
937
+ if (!pointers || pointers.length === 0 || !resolver) return [];
938
+ const urls = [];
939
+ for (const ref of pointers) {
940
+ try {
941
+ const url = await resolver(
942
+ ref.pointer,
943
+ conversationId,
944
+ ref.messageId ?? fallbackParentMessageId
945
+ );
946
+ if (url) urls.push(url);
947
+ } catch (err) {
948
+ log?.warn?.(
949
+ "CGPT-WEB",
950
+ `Image resolve failed (${ref.pointer}): ${err instanceof Error ? err.message : String(err)}`
951
+ );
952
+ }
953
+ }
954
+ return urls;
955
+ }
956
+ function buildStreamingResponse(eventStream, model, cid, created, resolver, pollAsyncImage, pollFinalAnswer, log, signal) {
957
+ const encoder = new TextEncoder();
958
+ return new ReadableStream(
959
+ {
960
+ async start(controller) {
961
+ try {
962
+ controller.enqueue(
963
+ encoder.encode(
964
+ sseChunk({
965
+ id: cid,
966
+ object: "chat.completion.chunk",
967
+ created,
968
+ model,
969
+ system_fingerprint: null,
970
+ choices: [
971
+ { index: 0, delta: { role: "assistant" }, finish_reason: null, logprobs: null }
972
+ ]
973
+ })
974
+ )
975
+ );
976
+ let conversationId = null;
977
+ let imagePointers;
978
+ let imageGenAsync = false;
979
+ let handoff = false;
980
+ let emittedText = "";
981
+ let polledFinalAnswer = "";
982
+ let parentCandidateMessageId = null;
983
+ const emitTextDelta = (content) => {
984
+ const cleaned = cleanChatGptText(content);
985
+ if (!cleaned) return;
986
+ emittedText += cleaned;
987
+ controller.enqueue(
988
+ encoder.encode(
989
+ sseChunk({
990
+ id: cid,
991
+ object: "chat.completion.chunk",
992
+ created,
993
+ model,
994
+ system_fingerprint: null,
995
+ choices: [
996
+ {
997
+ index: 0,
998
+ delta: { content: cleaned },
999
+ finish_reason: null,
1000
+ logprobs: null
1001
+ }
1002
+ ]
1003
+ })
1004
+ )
1005
+ );
1006
+ };
1007
+ const appendFinalAnswer = (text) => {
1008
+ const cleaned = cleanChatGptText(text);
1009
+ const finalTrimmed = cleaned.trim();
1010
+ if (!finalTrimmed) return;
1011
+ const emittedTrimmed = emittedText.trim();
1012
+ if (emittedTrimmed === finalTrimmed || emittedTrimmed.endsWith(finalTrimmed)) return;
1013
+ const prefix = emittedTrimmed && !emittedText.endsWith("\n") ? "\n\n" : "";
1014
+ emitTextDelta(`${prefix}${cleaned}`);
1015
+ };
1016
+ const startHeartbeat = (intervalMs = 5e3) => {
1017
+ const heartbeatChunk = sseChunk({
1018
+ id: cid,
1019
+ object: "chat.completion.chunk",
1020
+ created,
1021
+ model,
1022
+ system_fingerprint: null,
1023
+ choices: [{ index: 0, delta: { content: "\u200B" }, finish_reason: null, logprobs: null }]
1024
+ });
1025
+ const timer = setInterval(() => {
1026
+ try {
1027
+ controller.enqueue(encoder.encode(heartbeatChunk));
1028
+ } catch {
1029
+ console.warn("[chatgpt-web] heartbeat enqueue failed - controller closed");
1030
+ clearInterval(timer);
1031
+ }
1032
+ }, intervalMs);
1033
+ return () => clearInterval(timer);
1034
+ };
1035
+ for await (const chunk of extractContent(eventStream, signal)) {
1036
+ if (chunk.conversationId) conversationId = chunk.conversationId;
1037
+ if (chunk.messageId) parentCandidateMessageId = chunk.messageId;
1038
+ if (chunk.handoff) handoff = true;
1039
+ if (chunk.error) {
1040
+ controller.enqueue(
1041
+ encoder.encode(
1042
+ sseChunk({
1043
+ id: cid,
1044
+ object: "chat.completion.chunk",
1045
+ created,
1046
+ model,
1047
+ system_fingerprint: null,
1048
+ choices: [
1049
+ {
1050
+ index: 0,
1051
+ delta: { content: `[Error: ${chunk.error}]` },
1052
+ finish_reason: null,
1053
+ logprobs: null
1054
+ }
1055
+ ]
1056
+ })
1057
+ )
1058
+ );
1059
+ break;
1060
+ }
1061
+ if (chunk.done) {
1062
+ imagePointers = chunk.imagePointers;
1063
+ imageGenAsync = chunk.imageGenAsync ?? false;
1064
+ handoff = handoff || (chunk.handoff ?? false);
1065
+ if (chunk.messageId) parentCandidateMessageId = chunk.messageId;
1066
+ break;
1067
+ }
1068
+ if (chunk.delta) {
1069
+ emitTextDelta(chunk.delta);
1070
+ }
1071
+ }
1072
+ if (pollFinalAnswer && conversationId && handoff) {
1073
+ const stopHb = startHeartbeat();
1074
+ try {
1075
+ const polled = await pollFinalAnswer(conversationId);
1076
+ if (polled?.text) {
1077
+ polledFinalAnswer = polled.text;
1078
+ if (polled.messageId) parentCandidateMessageId = polled.messageId;
1079
+ }
1080
+ } finally {
1081
+ stopHb();
1082
+ }
1083
+ }
1084
+ if (polledFinalAnswer) {
1085
+ appendFinalAnswer(polledFinalAnswer);
1086
+ }
1087
+ if (imageGenAsync && conversationId && (!imagePointers || imagePointers.length === 0) && pollAsyncImage) {
1088
+ controller.enqueue(
1089
+ encoder.encode(
1090
+ sseChunk({
1091
+ id: cid,
1092
+ object: "chat.completion.chunk",
1093
+ created,
1094
+ model,
1095
+ system_fingerprint: null,
1096
+ choices: [
1097
+ {
1098
+ index: 0,
1099
+ delta: { content: "_Generating image\u2026_\n\n" },
1100
+ finish_reason: null,
1101
+ logprobs: null
1102
+ }
1103
+ ]
1104
+ })
1105
+ )
1106
+ );
1107
+ const stopHb = startHeartbeat();
1108
+ try {
1109
+ const polled = await pollAsyncImage(conversationId);
1110
+ if (polled.length > 0) imagePointers = polled;
1111
+ } catch (err) {
1112
+ log?.warn?.(
1113
+ "CGPT-WEB",
1114
+ `Async image poll failed: ${err instanceof Error ? err.message : String(err)}`
1115
+ );
1116
+ } finally {
1117
+ stopHb();
1118
+ }
1119
+ }
1120
+ const stopHb2 = startHeartbeat();
1121
+ let urls = [];
1122
+ try {
1123
+ urls = await resolveImagePointers(
1124
+ imagePointers,
1125
+ conversationId,
1126
+ resolver,
1127
+ log,
1128
+ parentCandidateMessageId
1129
+ );
1130
+ } finally {
1131
+ stopHb2();
1132
+ }
1133
+ if (signal?.aborted) return;
1134
+ const mdBlock = imageMarkdown(urls);
1135
+ const safeEnqueue = (bytes) => {
1136
+ try {
1137
+ controller.enqueue(bytes);
1138
+ return true;
1139
+ } catch {
1140
+ console.warn("[chatgpt-web] controller enqueue failed");
1141
+ return false;
1142
+ }
1143
+ };
1144
+ if (mdBlock) {
1145
+ if (!safeEnqueue(
1146
+ encoder.encode(
1147
+ sseChunk({
1148
+ id: cid,
1149
+ object: "chat.completion.chunk",
1150
+ created,
1151
+ model,
1152
+ system_fingerprint: null,
1153
+ choices: [
1154
+ {
1155
+ index: 0,
1156
+ delta: { content: mdBlock },
1157
+ finish_reason: null,
1158
+ logprobs: null
1159
+ }
1160
+ ]
1161
+ })
1162
+ )
1163
+ ))
1164
+ return;
1165
+ }
1166
+ if (!safeEnqueue(
1167
+ encoder.encode(
1168
+ sseChunk({
1169
+ id: cid,
1170
+ object: "chat.completion.chunk",
1171
+ created,
1172
+ model,
1173
+ system_fingerprint: null,
1174
+ choices: [{ index: 0, delta: {}, finish_reason: "stop", logprobs: null }]
1175
+ })
1176
+ )
1177
+ ))
1178
+ return;
1179
+ safeEnqueue(encoder.encode("data: [DONE]\n\n"));
1180
+ } catch (err) {
1181
+ controller.enqueue(
1182
+ encoder.encode(
1183
+ sseChunk({
1184
+ id: cid,
1185
+ object: "chat.completion.chunk",
1186
+ created,
1187
+ model,
1188
+ system_fingerprint: null,
1189
+ choices: [
1190
+ {
1191
+ index: 0,
1192
+ delta: {
1193
+ content: `[Stream error: ${err instanceof Error ? err.message : String(err)}]`
1194
+ },
1195
+ finish_reason: "stop",
1196
+ logprobs: null
1197
+ }
1198
+ ]
1199
+ })
1200
+ )
1201
+ );
1202
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
1203
+ } finally {
1204
+ try {
1205
+ controller.close();
1206
+ } catch {
1207
+ }
1208
+ }
1209
+ }
1210
+ },
1211
+ { highWaterMark: 16384 }
1212
+ );
1213
+ }
1214
+ async function buildNonStreamingResponse(eventStream, model, cid, created, currentMsg, resolver, pollAsyncImage, pollFinalAnswer, log, signal) {
1215
+ let fullAnswer = "";
1216
+ let conversationId = null;
1217
+ let imagePointers;
1218
+ let imageGenAsync = false;
1219
+ let handoff = false;
1220
+ let parentCandidateMessageId = null;
1221
+ for await (const chunk of extractContent(eventStream, signal)) {
1222
+ if (chunk.conversationId) conversationId = chunk.conversationId;
1223
+ if (chunk.messageId) parentCandidateMessageId = chunk.messageId;
1224
+ if (chunk.handoff) handoff = true;
1225
+ if (chunk.error) {
1226
+ return new Response(
1227
+ JSON.stringify({
1228
+ error: { message: chunk.error, type: "upstream_error", code: "CHATGPT_ERROR" }
1229
+ }),
1230
+ { status: 502, headers: { "Content-Type": "application/json" } }
1231
+ );
1232
+ }
1233
+ if (chunk.done) {
1234
+ fullAnswer = chunk.answer || fullAnswer;
1235
+ imagePointers = chunk.imagePointers;
1236
+ imageGenAsync = chunk.imageGenAsync ?? false;
1237
+ handoff = handoff || (chunk.handoff ?? false);
1238
+ if (chunk.messageId) parentCandidateMessageId = chunk.messageId;
1239
+ break;
1240
+ }
1241
+ if (chunk.answer) fullAnswer = chunk.answer;
1242
+ }
1243
+ if (pollFinalAnswer && conversationId && (handoff || !fullAnswer.trim())) {
1244
+ const polled = await pollFinalAnswer(conversationId);
1245
+ if (polled?.text) {
1246
+ fullAnswer = polled.text;
1247
+ if (polled.messageId) parentCandidateMessageId = polled.messageId;
1248
+ }
1249
+ }
1250
+ fullAnswer = cleanChatGptText(fullAnswer);
1251
+ if (imageGenAsync && conversationId && (!imagePointers || imagePointers.length === 0) && pollAsyncImage) {
1252
+ try {
1253
+ const polled = await pollAsyncImage(conversationId);
1254
+ if (polled.length > 0) imagePointers = polled;
1255
+ } catch (err) {
1256
+ log?.warn?.(
1257
+ "CGPT-WEB",
1258
+ `Async image poll failed: ${err instanceof Error ? err.message : String(err)}`
1259
+ );
1260
+ }
1261
+ }
1262
+ const urls = await resolveImagePointers(
1263
+ imagePointers,
1264
+ conversationId,
1265
+ resolver,
1266
+ log,
1267
+ parentCandidateMessageId
1268
+ );
1269
+ const imageResolutionFailed = detectImageResolutionFailure(
1270
+ imagePointers?.length ?? 0,
1271
+ urls.length
1272
+ );
1273
+ if (imageResolutionFailed && log?.warn) {
1274
+ const schemes = (imagePointers ?? []).map((p) => p.pointer.split("://")[0] || p.pointer.slice(0, 24)).join(", ");
1275
+ log.warn(
1276
+ "CGPT-WEB",
1277
+ `Image generated upstream but no asset pointer resolved (schemes: ${schemes}) \u2014 surfacing as unretrievable`
1278
+ );
1279
+ }
1280
+ fullAnswer += imageMarkdown(urls);
1281
+ const promptTokens = Math.ceil(currentMsg.length / 4);
1282
+ const completionTokens = Math.ceil(fullAnswer.length / 4);
1283
+ return new Response(
1284
+ JSON.stringify({
1285
+ id: cid,
1286
+ object: "chat.completion",
1287
+ created,
1288
+ model,
1289
+ system_fingerprint: null,
1290
+ ...imageResolutionFailed ? { x_image_resolution_failed: true } : {},
1291
+ choices: [
1292
+ {
1293
+ index: 0,
1294
+ message: { role: "assistant", content: fullAnswer },
1295
+ finish_reason: "stop",
1296
+ logprobs: null
1297
+ }
1298
+ ],
1299
+ usage: {
1300
+ prompt_tokens: promptTokens,
1301
+ completion_tokens: completionTokens,
1302
+ total_tokens: promptTokens + completionTokens
1303
+ }
1304
+ }),
1305
+ { status: 200, headers: { "Content-Type": "application/json" } }
1306
+ );
1307
+ }
1308
+ function errorResponse(status, message, code) {
1309
+ return new Response(
1310
+ JSON.stringify({ error: { message, type: "upstream_error", ...code ? { code } : {} } }),
1311
+ { status, headers: { "Content-Type": "application/json" } }
1312
+ );
1313
+ }
1314
+ function normalizePublicBaseUrl(value) {
1315
+ const trimmed = value?.trim();
1316
+ if (!trimmed) return null;
1317
+ return trimmed.replace(/\/+$/, "").replace(/\/v1$/i, "");
1318
+ }
1319
+ function firstForwardedValue(value) {
1320
+ const first = value?.split(",")[0]?.trim();
1321
+ return first || null;
1322
+ }
1323
+ function isLocalBaseUrl(baseUrl) {
1324
+ try {
1325
+ const host = new URL(baseUrl).hostname.toLowerCase();
1326
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0";
1327
+ } catch {
1328
+ console.warn("[chatgpt-web] URL parse failed, falling back to regex");
1329
+ return /\b(?:localhost|127\.0\.0\.1|0\.0\.0\.0)\b/i.test(baseUrl);
1330
+ }
1331
+ }
1332
+ function deriveHeaderBaseUrl(clientHeaders) {
1333
+ const headers = clientHeaders ?? {};
1334
+ const lower = {};
1335
+ for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;
1336
+ const forwardedHost = firstForwardedValue(lower["x-forwarded-host"]);
1337
+ const forwardedProto = firstForwardedValue(lower["x-forwarded-proto"]);
1338
+ const host = forwardedHost || firstForwardedValue(lower["host"]);
1339
+ if (!host) return null;
1340
+ const isPlain = host.includes("localhost") || /^\d+\.\d+\.\d+\.\d+(:\d+)?$/.test(host) || host.endsWith(".local") || host.includes(":");
1341
+ const proto = forwardedProto || (isPlain ? "http" : "https");
1342
+ return `${proto}://${host}`;
1343
+ }
1344
+ function derivePublicBaseUrl(clientHeaders, log) {
1345
+ const explicitPublicBase = normalizePublicBaseUrl(process.env.OMNIROUTE_PUBLIC_BASE_URL);
1346
+ if (explicitPublicBase) {
1347
+ log?.debug?.("CGPT-WEB", `derivePublicBaseUrl: using OMNIROUTE_PUBLIC_BASE_URL`);
1348
+ return explicitPublicBase;
1349
+ }
1350
+ const headerBase = deriveHeaderBaseUrl(clientHeaders);
1351
+ const configuredBase = normalizePublicBaseUrl(process.env.OMNIROUTE_BASE_URL) || normalizePublicBaseUrl(process.env.NEXT_PUBLIC_BASE_URL);
1352
+ log?.debug?.(
1353
+ "CGPT-WEB",
1354
+ `derivePublicBaseUrl: configured=${configuredBase ?? "-"} header=${headerBase ?? "-"}`
1355
+ );
1356
+ if (configuredBase && (!headerBase || !isLocalBaseUrl(configuredBase))) return configuredBase;
1357
+ if (headerBase) return headerBase;
1358
+ if (configuredBase) return configuredBase;
1359
+ return `http://localhost:${process.env.PORT || 20128}`;
1360
+ }
1361
+ const FILE_SERVICE_PREFIX = "file-service://";
1362
+ const SEDIMENT_PREFIX = "sediment://";
1363
+ async function fetchDownloadUrl(endpoint, ctx) {
1364
+ const headers = {
1365
+ ...browserHeaders(),
1366
+ ...oaiHeaders(ctx.sessionId, ctx.deviceId),
1367
+ Accept: "application/json",
1368
+ Authorization: `Bearer ${ctx.accessToken}`,
1369
+ Cookie: buildSessionCookieHeader(ctx.cookie)
1370
+ };
1371
+ if (ctx.accountId) headers["chatgpt-account-id"] = ctx.accountId;
1372
+ const response = await tlsFetchChatGpt(endpoint, {
1373
+ method: "GET",
1374
+ headers,
1375
+ timeoutMs: 3e4,
1376
+ signal: ctx.signal
1377
+ });
1378
+ if (response.status !== 200) {
1379
+ ctx.log?.warn?.(
1380
+ "CGPT-WEB",
1381
+ `Image download URL fetch failed (${response.status}) for ${endpoint}`
1382
+ );
1383
+ return null;
1384
+ }
1385
+ let parsed = {};
1386
+ try {
1387
+ parsed = JSON.parse(response.text || "{}");
1388
+ } catch {
1389
+ console.warn("[chatgpt-web] image download URL parse failed");
1390
+ return null;
1391
+ }
1392
+ return parsed.download_url ?? null;
1393
+ }
1394
+ const IMAGE_DOWNLOAD_MAX_BYTES = 8 * 1024 * 1024;
1395
+ async function imageUrlToCachedImageUrl(signedUrl, ctx, imageContext) {
1396
+ const headers = {
1397
+ ...browserHeaders(),
1398
+ Accept: "image/*,*/*;q=0.8",
1399
+ Authorization: `Bearer ${ctx.accessToken}`,
1400
+ Cookie: buildSessionCookieHeader(ctx.cookie)
1401
+ };
1402
+ if (ctx.accountId) headers["chatgpt-account-id"] = ctx.accountId;
1403
+ let response;
1404
+ try {
1405
+ response = await tlsFetchChatGpt(signedUrl, {
1406
+ method: "GET",
1407
+ headers,
1408
+ timeoutMs: 6e4,
1409
+ signal: ctx.signal,
1410
+ // Required for binary payloads — the underlying tls-client returns
1411
+ // bytes as a `data:<mime>;base64,...` string when this is true.
1412
+ // Without it, raw image bytes get mangled by UTF-8 decoding.
1413
+ byteResponse: true
1414
+ });
1415
+ } catch (err) {
1416
+ ctx.log?.warn?.(
1417
+ "CGPT-WEB",
1418
+ `Image fetch failed: ${err instanceof Error ? err.message : String(err)}`
1419
+ );
1420
+ return null;
1421
+ }
1422
+ if (response.status !== 200) {
1423
+ ctx.log?.warn?.(
1424
+ "CGPT-WEB",
1425
+ `Image fetch returned HTTP ${response.status} (${(response.text || "").slice(0, 120)})`
1426
+ );
1427
+ return null;
1428
+ }
1429
+ if (response.text == null || response.text.length === 0) return null;
1430
+ let bytes;
1431
+ let mime;
1432
+ if (/^data:[^;]{1,256};base64,/.test(response.text)) {
1433
+ const commaIdx = response.text.indexOf(",");
1434
+ const header = response.text.slice(5, commaIdx);
1435
+ mime = header.split(";")[0] || "image/png";
1436
+ bytes = Buffer.from(response.text.slice(commaIdx + 1), "base64");
1437
+ } else {
1438
+ bytes = Buffer.from(response.text, "binary");
1439
+ mime = response.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
1440
+ }
1441
+ if (bytes.length === 0 || bytes.length > IMAGE_DOWNLOAD_MAX_BYTES) {
1442
+ if (bytes.length > IMAGE_DOWNLOAD_MAX_BYTES) {
1443
+ ctx.log?.warn?.(
1444
+ "CGPT-WEB",
1445
+ `Image too large to cache (${bytes.length} bytes > ${IMAGE_DOWNLOAD_MAX_BYTES}); skipping`
1446
+ );
1447
+ }
1448
+ return null;
1449
+ }
1450
+ const id = storeChatGptImage(bytes, mime, void 0, imageContext);
1451
+ return `${ctx.publicBaseUrl}/v1/chatgpt-web/image/${id}`;
1452
+ }
1453
+ async function registerWebSocket(ctx) {
1454
+ const candidates = [
1455
+ { url: `${CHATGPT_BASE}/backend-api/celsius/ws/user`, method: "GET" },
1456
+ { url: `${CHATGPT_BASE}/backend-api/register-websocket`, method: "POST" }
1457
+ ];
1458
+ const headers = {
1459
+ ...browserHeaders(),
1460
+ ...oaiHeaders(ctx.sessionId, ctx.deviceId),
1461
+ Accept: "application/json",
1462
+ Authorization: `Bearer ${ctx.accessToken}`,
1463
+ Cookie: buildSessionCookieHeader(ctx.cookie)
1464
+ };
1465
+ if (ctx.accountId) headers["chatgpt-account-id"] = ctx.accountId;
1466
+ for (const { url, method } of candidates) {
1467
+ let r;
1468
+ try {
1469
+ r = await tlsFetchChatGpt(url, {
1470
+ method,
1471
+ headers,
1472
+ body: method === "POST" ? "" : void 0,
1473
+ timeoutMs: 3e4,
1474
+ signal: ctx.signal
1475
+ });
1476
+ } catch (err) {
1477
+ ctx.log?.warn?.(
1478
+ "CGPT-WEB",
1479
+ `register-websocket fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`
1480
+ );
1481
+ continue;
1482
+ }
1483
+ if (r.status === 200) {
1484
+ try {
1485
+ const data = JSON.parse(r.text || "{}");
1486
+ const ws = data.websocket_url ?? data.wss_url;
1487
+ if (ws) {
1488
+ ctx.log?.debug?.("CGPT-WEB", `Got WebSocket URL via ${url}`);
1489
+ return ws;
1490
+ }
1491
+ } catch {
1492
+ console.warn("[chatgpt-web] WebSocket URL parse failed, falling through");
1493
+ }
1494
+ }
1495
+ ctx.log?.warn?.(
1496
+ "CGPT-WEB",
1497
+ `register-websocket via ${url} \u2192 ${r.status}: ${(r.text || "").slice(0, 200)}`
1498
+ );
1499
+ }
1500
+ return null;
1501
+ }
1502
+ async function waitForImageViaWebSocket(wssUrl, conversationId, timeoutMs, ctx) {
1503
+ return new Promise((resolve) => {
1504
+ const found = /* @__PURE__ */ new Map();
1505
+ let resolved = false;
1506
+ let errored = false;
1507
+ let gotAnyMessage = false;
1508
+ const finish = () => {
1509
+ if (resolved) return;
1510
+ resolved = true;
1511
+ try {
1512
+ ws.close();
1513
+ } catch {
1514
+ console.warn("[chatgpt-web] ws.close failed");
1515
+ }
1516
+ resolve({
1517
+ pointers: Array.from(found.values()),
1518
+ errored,
1519
+ gotAnyMessage
1520
+ });
1521
+ };
1522
+ const ws = new WebSocket(wssUrl);
1523
+ const timer = setTimeout(() => {
1524
+ ctx.log?.warn?.("CGPT-WEB", `WebSocket image wait timed out after ${timeoutMs}ms`);
1525
+ finish();
1526
+ }, timeoutMs);
1527
+ const onAbort = () => {
1528
+ ctx.log?.debug?.("CGPT-WEB", "WebSocket aborted by client");
1529
+ finish();
1530
+ };
1531
+ ctx.signal?.addEventListener?.("abort", onAbort);
1532
+ ws.onopen = () => {
1533
+ gotAnyMessage = true;
1534
+ ctx.log?.debug?.("CGPT-WEB", "WebSocket open \u2014 waiting for image events");
1535
+ };
1536
+ ws.onerror = (e) => {
1537
+ errored = true;
1538
+ ctx.log?.warn?.("CGPT-WEB", `WebSocket error: ${e.message ?? "unknown"}`);
1539
+ };
1540
+ ws.onclose = () => {
1541
+ clearTimeout(timer);
1542
+ ctx.signal?.removeEventListener?.("abort", onAbort);
1543
+ finish();
1544
+ };
1545
+ ws.onmessage = (event) => {
1546
+ gotAnyMessage = true;
1547
+ let payload;
1548
+ const raw = typeof event.data === "string" ? event.data : event.data.toString();
1549
+ try {
1550
+ payload = JSON.parse(raw);
1551
+ } catch {
1552
+ console.warn("[chatgpt-web] WebSocket event JSON parse failed");
1553
+ return;
1554
+ }
1555
+ const obj = payload;
1556
+ const candidates = [];
1557
+ const innerPayload = obj.payload;
1558
+ const updateContent = innerPayload?.update_content;
1559
+ if (updateContent?.message) {
1560
+ candidates.push({
1561
+ message: updateContent.message,
1562
+ conversation_id: innerPayload?.conversation_id
1563
+ });
1564
+ }
1565
+ if (innerPayload?.message) {
1566
+ candidates.push({
1567
+ message: innerPayload.message,
1568
+ conversation_id: innerPayload.conversation_id
1569
+ });
1570
+ }
1571
+ if (obj.data?.message) {
1572
+ candidates.push(obj.data);
1573
+ }
1574
+ for (const data of candidates) {
1575
+ if (data?.conversation_id && data.conversation_id !== conversationId) continue;
1576
+ const m = data?.message;
1577
+ if (Array.isArray(m?.content?.parts)) {
1578
+ for (const ptr of extractImagePointers(m.content?.parts ?? [])) {
1579
+ const existing = found.get(ptr);
1580
+ found.set(
1581
+ ptr,
1582
+ existing?.messageId ? existing : { pointer: ptr, ...m?.id ? { messageId: m.id } : {} }
1583
+ );
1584
+ }
1585
+ }
1586
+ if (m?.metadata && typeof m.metadata === "object") {
1587
+ const md = m.metadata;
1588
+ const ptr = md.asset_pointer ?? md.image_asset_pointer;
1589
+ if (typeof ptr === "string") {
1590
+ const existing = found.get(ptr);
1591
+ found.set(
1592
+ ptr,
1593
+ existing?.messageId ? existing : { pointer: ptr, ...m?.id ? { messageId: m.id } : {} }
1594
+ );
1595
+ }
1596
+ }
1597
+ }
1598
+ if (found.size > 0) finish();
1599
+ };
1600
+ });
1601
+ }
1602
+ const DEFAULT_ASYNC_IMAGE_TIMEOUT_MS = 18e4;
1603
+ function configuredAsyncImageTimeoutMs() {
1604
+ const raw = Number(process.env.OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS);
1605
+ if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_ASYNC_IMAGE_TIMEOUT_MS;
1606
+ return Math.floor(raw);
1607
+ }
1608
+ async function pollForAsyncImage(conversationId, ctx, opts = {}) {
1609
+ const totalTimeoutMs = opts.timeoutMs ?? configuredAsyncImageTimeoutMs();
1610
+ const deadline = Date.now() + totalTimeoutMs;
1611
+ for (let attempt = 0; attempt < 2; attempt++) {
1612
+ const remaining = deadline - Date.now();
1613
+ if (remaining <= 0) break;
1614
+ const wssUrl = await registerWebSocket(ctx);
1615
+ if (!wssUrl) {
1616
+ ctx.log?.warn?.(
1617
+ "CGPT-WEB",
1618
+ attempt === 0 ? "Could not register WebSocket \u2014 async image gen not retrievable" : `WebSocket re-registration failed on retry attempt ${attempt + 1}`
1619
+ );
1620
+ if (attempt === 0) continue;
1621
+ return [];
1622
+ }
1623
+ ctx.log?.debug?.(
1624
+ "CGPT-WEB",
1625
+ `Registered WebSocket for async image (attempt ${attempt + 1}, ${remaining}ms remaining)`
1626
+ );
1627
+ const outcome = await waitForImageViaWebSocket(wssUrl, conversationId, remaining, ctx);
1628
+ if (outcome.pointers.length > 0) return outcome.pointers;
1629
+ if (ctx.signal?.aborted) return [];
1630
+ if (!outcome.errored || outcome.gotAnyMessage) return [];
1631
+ ctx.log?.warn?.(
1632
+ "CGPT-WEB",
1633
+ `WebSocket attempt ${attempt + 1} ended in transport error before any frame; retrying`
1634
+ );
1635
+ }
1636
+ return [];
1637
+ }
1638
+ function makeImageResolver(ctx) {
1639
+ const cache = /* @__PURE__ */ new Map();
1640
+ return async (assetPointer, conversationId, parentMessageId) => {
1641
+ if (cache.has(assetPointer)) return cache.get(assetPointer) ?? null;
1642
+ let fileId = null;
1643
+ if (assetPointer.startsWith(FILE_SERVICE_PREFIX)) {
1644
+ fileId = assetPointer.slice(FILE_SERVICE_PREFIX.length);
1645
+ } else if (assetPointer.startsWith(SEDIMENT_PREFIX)) {
1646
+ fileId = assetPointer.slice(SEDIMENT_PREFIX.length);
1647
+ } else {
1648
+ ctx.log?.warn?.("CGPT-WEB", `Unknown asset_pointer scheme: ${assetPointer}`);
1649
+ }
1650
+ let signedUrl = null;
1651
+ if (fileId) {
1652
+ signedUrl = await fetchDownloadUrl(
1653
+ `${CHATGPT_BASE}/backend-api/files/${encodeURIComponent(fileId)}/download`,
1654
+ ctx
1655
+ );
1656
+ if (!signedUrl && conversationId) {
1657
+ signedUrl = await fetchDownloadUrl(
1658
+ `${CHATGPT_BASE}/backend-api/conversation/${encodeURIComponent(conversationId)}/attachment/${encodeURIComponent(fileId)}/download`,
1659
+ ctx
1660
+ );
1661
+ }
1662
+ }
1663
+ let finalUrl = null;
1664
+ if (signedUrl) {
1665
+ finalUrl = await imageUrlToCachedImageUrl(
1666
+ signedUrl,
1667
+ ctx,
1668
+ conversationId && parentMessageId ? { conversationId, parentMessageId } : void 0
1669
+ );
1670
+ }
1671
+ cache.set(assetPointer, finalUrl);
1672
+ if (finalUrl) {
1673
+ const preview = finalUrl.startsWith("data:") ? `data:... (${finalUrl.length} chars)` : finalUrl.slice(0, 80) + "...";
1674
+ ctx.log?.debug?.("CGPT-WEB", `Resolved ${assetPointer} \u2192 ${preview}`);
1675
+ }
1676
+ return finalUrl;
1677
+ };
1678
+ }
1679
+ class ChatGptWebExecutor extends BaseExecutor {
1680
+ constructor() {
1681
+ super("chatgpt-web", { id: "chatgpt-web", baseUrl: CONV_URL });
1682
+ }
1683
+ async execute({
1684
+ model,
1685
+ body,
1686
+ stream,
1687
+ credentials,
1688
+ signal,
1689
+ log,
1690
+ onCredentialsRefreshed,
1691
+ clientHeaders
1692
+ }) {
1693
+ const messages = body?.messages;
1694
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
1695
+ return {
1696
+ response: errorResponse(400, "Missing or empty messages array"),
1697
+ url: CONV_URL,
1698
+ headers: {},
1699
+ transformedBody: body
1700
+ };
1701
+ }
1702
+ const { hasTools, requestedTools, effectiveMessages } = prepareToolMessages(
1703
+ body || {},
1704
+ messages
1705
+ );
1706
+ if (!credentials.apiKey) {
1707
+ return {
1708
+ response: errorResponse(
1709
+ 401,
1710
+ "ChatGPT auth failed \u2014 paste your __Secure-next-auth.session-token cookie value."
1711
+ ),
1712
+ url: CONV_URL,
1713
+ headers: {},
1714
+ transformedBody: body
1715
+ };
1716
+ }
1717
+ const cookie = credentials.apiKey;
1718
+ let tokenEntry;
1719
+ try {
1720
+ tokenEntry = await exchangeSession(cookie, signal);
1721
+ } catch (err) {
1722
+ if (err instanceof SessionAuthError) {
1723
+ log?.warn?.("CGPT-WEB", err.message);
1724
+ return {
1725
+ response: errorResponse(
1726
+ 401,
1727
+ "ChatGPT auth failed \u2014 re-paste your __Secure-next-auth.session-token cookie from chatgpt.com.",
1728
+ "HTTP_401"
1729
+ ),
1730
+ url: SESSION_URL,
1731
+ headers: {},
1732
+ transformedBody: body
1733
+ };
1734
+ }
1735
+ log?.error?.(
1736
+ "CGPT-WEB",
1737
+ `Session exchange failed: ${err instanceof Error ? err.message : String(err)}`
1738
+ );
1739
+ return {
1740
+ response: errorResponse(
1741
+ 502,
1742
+ `ChatGPT session exchange failed: ${err instanceof Error ? err.message : String(err)}`
1743
+ ),
1744
+ url: SESSION_URL,
1745
+ headers: {},
1746
+ transformedBody: body
1747
+ };
1748
+ }
1749
+ if (tokenEntry.refreshedCookie && tokenEntry.refreshedCookie !== cookie) {
1750
+ const updated = { ...credentials, apiKey: tokenEntry.refreshedCookie };
1751
+ try {
1752
+ await onCredentialsRefreshed?.(updated);
1753
+ } catch (err) {
1754
+ log?.warn?.(
1755
+ "CGPT-WEB",
1756
+ `Failed to persist refreshed cookie: ${err instanceof Error ? err.message : String(err)}`
1757
+ );
1758
+ }
1759
+ }
1760
+ let dplInfo;
1761
+ try {
1762
+ dplInfo = await fetchDpl(cookie, signal);
1763
+ } catch (err) {
1764
+ log?.warn?.(
1765
+ "CGPT-WEB",
1766
+ `DPL warmup failed (continuing with fallback): ${err instanceof Error ? err.message : String(err)}`
1767
+ );
1768
+ dplInfo = {
1769
+ dpl: `dpl=${OAI_CLIENT_VERSION.replace(/^prod-/, "")}`,
1770
+ scriptSrc: `${CHATGPT_BASE}/_next/static/chunks/webpack-${randomHex(16)}.js`
1771
+ };
1772
+ }
1773
+ const sessionId = randomUUID();
1774
+ const deviceId = deviceIdFor(cookie);
1775
+ await runSessionWarmup(
1776
+ tokenEntry.accessToken,
1777
+ tokenEntry.accountId,
1778
+ sessionId,
1779
+ deviceId,
1780
+ cookie,
1781
+ signal,
1782
+ log
1783
+ );
1784
+ const resolvedModel = resolveChatGptModel(model, body, credentials.providerSpecificData);
1785
+ const modelSlug = resolvedModel.slug;
1786
+ const requestedEffort = resolvedModel.effort;
1787
+ if (requestedEffort && isThinkingCapableModel(model, modelSlug)) {
1788
+ await setUserThinkingEffort(
1789
+ modelSlug,
1790
+ requestedEffort,
1791
+ tokenEntry.accessToken,
1792
+ tokenEntry.accountId,
1793
+ sessionId,
1794
+ deviceId,
1795
+ cookie,
1796
+ signal,
1797
+ log
1798
+ );
1799
+ }
1800
+ let reqs;
1801
+ try {
1802
+ reqs = await prepareChatRequirements(
1803
+ tokenEntry.accessToken,
1804
+ tokenEntry.accountId,
1805
+ sessionId,
1806
+ deviceId,
1807
+ cookie,
1808
+ dplInfo,
1809
+ signal,
1810
+ log
1811
+ );
1812
+ } catch (err) {
1813
+ if (err instanceof SentinelBlockedError) {
1814
+ log?.warn?.("CGPT-WEB", err.message);
1815
+ return {
1816
+ response: errorResponse(
1817
+ 403,
1818
+ "ChatGPT blocked the request (Sentinel/Turnstile required). Try again later or open chatgpt.com in a browser to refresh state.",
1819
+ "SENTINEL_BLOCKED"
1820
+ ),
1821
+ url: SENTINEL_PREPARE_URL,
1822
+ headers: {},
1823
+ transformedBody: body
1824
+ };
1825
+ }
1826
+ log?.error?.(
1827
+ "CGPT-WEB",
1828
+ `Sentinel failed: ${err instanceof Error ? err.message : String(err)}`
1829
+ );
1830
+ return {
1831
+ response: errorResponse(
1832
+ 502,
1833
+ `ChatGPT sentinel failed: ${err instanceof Error ? err.message : String(err)}`
1834
+ ),
1835
+ url: SENTINEL_PREPARE_URL,
1836
+ headers: {},
1837
+ transformedBody: body
1838
+ };
1839
+ }
1840
+ log?.debug?.(
1841
+ "CGPT-WEB",
1842
+ `sentinel: token=${reqs.token ? "y" : "n"} pow=${reqs.proofofwork?.required ? "y" : "n"} turnstile=${reqs.turnstile?.required ? "y" : "n"}`
1843
+ );
1844
+ const turnstileToken = typeof credentials.providerSpecificData?.turnstileToken === "string" ? credentials.providerSpecificData.turnstileToken : null;
1845
+ let proofToken = null;
1846
+ if (reqs.proofofwork?.required && reqs.proofofwork.seed && reqs.proofofwork.difficulty) {
1847
+ const powConfig = buildPrekeyConfig(CHATGPT_USER_AGENT, dplInfo.dpl, dplInfo.scriptSrc);
1848
+ proofToken = await solveProofOfWork(
1849
+ reqs.proofofwork.seed,
1850
+ reqs.proofofwork.difficulty,
1851
+ powConfig,
1852
+ log
1853
+ );
1854
+ }
1855
+ const parsed = parseOpenAIMessages(effectiveMessages);
1856
+ if (!parsed.currentMsg.trim() && parsed.history.length === 0) {
1857
+ return {
1858
+ response: errorResponse(400, "Empty user message"),
1859
+ url: CONV_URL,
1860
+ headers: {},
1861
+ transformedBody: body
1862
+ };
1863
+ }
1864
+ const imageEdit = looksLikeImageEditRequest(parsed);
1865
+ const continuation = imageEdit ? parsed.latestImageContext : null;
1866
+ const forImageGen = looksLikeImageGenRequest(parsed) || imageEdit;
1867
+ const persistConversation = forImageGen || !!continuation;
1868
+ if (forImageGen) {
1869
+ log?.debug?.(
1870
+ "CGPT-WEB",
1871
+ continuation ? "Image edit intent detected \u2014 continuing saved image conversation" : "Image-gen intent detected \u2014 disabling Temporary Chat for this turn"
1872
+ );
1873
+ } else if (resolvedModel.isPro) {
1874
+ log?.debug?.("CGPT-WEB", "GPT-5.5 Pro text request \u2014 keeping Temporary Chat enabled");
1875
+ }
1876
+ const parentMessageId = continuation?.parentMessageId ?? randomUUID();
1877
+ const cgptBody = buildConversationBody(parsed, modelSlug, parentMessageId, {
1878
+ persistConversation,
1879
+ thinkingEffort: requestedEffort,
1880
+ continuation
1881
+ });
1882
+ const headers = {
1883
+ ...browserHeaders(),
1884
+ ...oaiHeaders(sessionId, deviceId),
1885
+ "Content-Type": "application/json",
1886
+ Accept: "text/event-stream",
1887
+ Authorization: `Bearer ${tokenEntry.accessToken}`,
1888
+ Cookie: buildSessionCookieHeader(cookie)
1889
+ };
1890
+ if (tokenEntry.accountId) headers["chatgpt-account-id"] = tokenEntry.accountId;
1891
+ if (reqs.token) headers["openai-sentinel-chat-requirements-token"] = reqs.token;
1892
+ if (reqs.prepare_token)
1893
+ headers["openai-sentinel-chat-requirements-prepare-token"] = reqs.prepare_token;
1894
+ if (proofToken) headers["openai-sentinel-proof-token"] = proofToken;
1895
+ if (turnstileToken) headers["openai-sentinel-turnstile-token"] = turnstileToken;
1896
+ log?.info?.("CGPT-WEB", `Conversation request \u2192 ${modelSlug} (pow=${!!proofToken})`);
1897
+ let response;
1898
+ try {
1899
+ response = await tlsFetchChatGpt(CONV_URL, {
1900
+ method: "POST",
1901
+ headers,
1902
+ body: JSON.stringify(cgptBody),
1903
+ timeoutMs: 12e4,
1904
+ // generations can take a while
1905
+ signal,
1906
+ // For real-time streaming, ask the TLS client to write the body to
1907
+ // a temp file and surface it as a ReadableStream as it arrives —
1908
+ // otherwise long generations buffer entirely before the client sees
1909
+ // anything (and the downstream HTTP request can time out).
1910
+ stream
1911
+ });
1912
+ } catch (err) {
1913
+ log?.error?.("CGPT-WEB", `Fetch failed: ${err instanceof Error ? err.message : String(err)}`);
1914
+ const code = err instanceof TlsClientUnavailableError ? "TLS_UNAVAILABLE" : void 0;
1915
+ return {
1916
+ response: errorResponse(
1917
+ 502,
1918
+ `ChatGPT connection failed: ${err instanceof Error ? err.message : String(err)}`,
1919
+ code
1920
+ ),
1921
+ url: CONV_URL,
1922
+ headers,
1923
+ transformedBody: cgptBody
1924
+ };
1925
+ }
1926
+ if (response.status >= 400) {
1927
+ const status = response.status;
1928
+ log?.warn?.("CGPT-WEB", `conv ${status}: ${(response.text || "").slice(0, 400)}`);
1929
+ const errMsg = describeChatGptWebHttpError(status);
1930
+ if (status === 401 || status === 403) {
1931
+ tokenCache.delete(cookieKey(cookie));
1932
+ }
1933
+ log?.warn?.("CGPT-WEB", errMsg);
1934
+ return {
1935
+ response: errorResponse(status, errMsg, `HTTP_${status}`),
1936
+ url: CONV_URL,
1937
+ headers,
1938
+ transformedBody: cgptBody
1939
+ };
1940
+ }
1941
+ let bodyStream;
1942
+ if (response.body) {
1943
+ bodyStream = response.body;
1944
+ } else if (response.text) {
1945
+ bodyStream = stringToStream(response.text);
1946
+ } else {
1947
+ return {
1948
+ response: errorResponse(502, "ChatGPT returned empty response body"),
1949
+ url: CONV_URL,
1950
+ headers,
1951
+ transformedBody: cgptBody
1952
+ };
1953
+ }
1954
+ const cid = `chatcmpl-cgpt-${crypto.randomUUID().slice(0, 12)}`;
1955
+ const created = Math.floor(Date.now() / 1e3);
1956
+ const resolverCtx = {
1957
+ accessToken: tokenEntry.accessToken,
1958
+ accountId: tokenEntry.accountId,
1959
+ sessionId,
1960
+ deviceId,
1961
+ cookie,
1962
+ signal,
1963
+ log,
1964
+ publicBaseUrl: derivePublicBaseUrl(clientHeaders, log)
1965
+ };
1966
+ const imageResolver = makeImageResolver(resolverCtx);
1967
+ const pollAsyncImage = (conversationId) => pollForAsyncImage(conversationId, resolverCtx);
1968
+ const pollFinalAnswer = resolvedModel.isPro ? (conversationId) => pollForFinalAssistantAnswer(conversationId, resolverCtx) : null;
1969
+ const toolMode = hasTools && !forImageGen;
1970
+ let finalResponse;
1971
+ if (stream && !toolMode) {
1972
+ const sseStream = buildStreamingResponse(
1973
+ bodyStream,
1974
+ model,
1975
+ cid,
1976
+ created,
1977
+ imageResolver,
1978
+ pollAsyncImage,
1979
+ pollFinalAnswer,
1980
+ log,
1981
+ signal
1982
+ );
1983
+ finalResponse = new Response(sseStream, {
1984
+ status: 200,
1985
+ headers: {
1986
+ "Content-Type": "text/event-stream",
1987
+ "Cache-Control": "no-cache",
1988
+ "X-Accel-Buffering": "no"
1989
+ }
1990
+ });
1991
+ } else {
1992
+ finalResponse = await buildNonStreamingResponse(
1993
+ bodyStream,
1994
+ model,
1995
+ cid,
1996
+ created,
1997
+ parsed.currentMsg,
1998
+ imageResolver,
1999
+ pollAsyncImage,
2000
+ pollFinalAnswer,
2001
+ log,
2002
+ signal
2003
+ );
2004
+ if (toolMode) {
2005
+ finalResponse = await buildToolModeResponse(finalResponse, requestedTools, stream, {
2006
+ cid,
2007
+ created,
2008
+ model
2009
+ });
2010
+ }
2011
+ }
2012
+ return { response: finalResponse, url: CONV_URL, headers, transformedBody: cgptBody };
2013
+ }
2014
+ }
2015
+ const ENTITY_RE = /entity\["[^"]*","([^"]*)"[^\]]*\]/g;
2016
+ function cleanChatGptText(text) {
2017
+ return text.replace(ENTITY_RE, "$1");
2018
+ }
2019
+ function stringToStream(text) {
2020
+ const encoder = new TextEncoder();
2021
+ return new ReadableStream({
2022
+ start(controller) {
2023
+ controller.enqueue(encoder.encode(text));
2024
+ controller.close();
2025
+ }
2026
+ });
2027
+ }
2028
+ function __resetChatGptWebCachesForTesting() {
2029
+ tokenCache.clear();
2030
+ warmupCache.clear();
2031
+ thinkingEffortCache.clear();
2032
+ deviceIdCache.clear();
2033
+ __resetChatGptImageCacheForTesting();
2034
+ dplCache = null;
2035
+ }
2036
+ const __derivePublicBaseUrlForTesting = derivePublicBaseUrl;
2037
+ export {
2038
+ ChatGptWebExecutor,
2039
+ __derivePublicBaseUrlForTesting,
2040
+ __resetChatGptWebCachesForTesting,
2041
+ detectImageResolutionFailure
2042
+ };