wyxrouter 0.4.71

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 (763) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSE +21 -0
  3. package/README.md +96 -0
  4. package/README.zh-CN.md +1311 -0
  5. package/assets/pixel-router-2d.png +0 -0
  6. package/cli/LICENSE +42 -0
  7. package/cli/README.md +125 -0
  8. package/cli/app/package.json +58 -0
  9. package/cli/cli.js +806 -0
  10. package/cli/package.json +48 -0
  11. package/i18n/README.ja-JP.md +1209 -0
  12. package/i18n/README.ru.md +1311 -0
  13. package/i18n/README.vi.md +1310 -0
  14. package/i18n/README.zh-CN.md +1306 -0
  15. package/images/9router.png +0 -0
  16. package/jsconfig.json +12 -0
  17. package/next.config.mjs +71 -0
  18. package/open-sse/config/appConstants.js +203 -0
  19. package/open-sse/config/codexInstructions.js +119 -0
  20. package/open-sse/config/constants.js +4 -0
  21. package/open-sse/config/defaultThinkingSignature.js +12 -0
  22. package/open-sse/config/errorConfig.js +85 -0
  23. package/open-sse/config/googleTtsLanguages.js +62 -0
  24. package/open-sse/config/kiroConstants.js +322 -0
  25. package/open-sse/config/models.js +13 -0
  26. package/open-sse/config/ollamaModels.js +19 -0
  27. package/open-sse/config/providerModels.js +944 -0
  28. package/open-sse/config/providers.js +458 -0
  29. package/open-sse/config/runtimeConfig.js +72 -0
  30. package/open-sse/config/ttsModels.js +129 -0
  31. package/open-sse/executors/antigravity.js +504 -0
  32. package/open-sse/executors/azure.js +57 -0
  33. package/open-sse/executors/base.js +185 -0
  34. package/open-sse/executors/codex.js +469 -0
  35. package/open-sse/executors/commandcode.js +88 -0
  36. package/open-sse/executors/cursor.js +795 -0
  37. package/open-sse/executors/default.js +497 -0
  38. package/open-sse/executors/gemini-cli.js +89 -0
  39. package/open-sse/executors/github.js +379 -0
  40. package/open-sse/executors/grok-web.js +345 -0
  41. package/open-sse/executors/iflow.js +108 -0
  42. package/open-sse/executors/index.js +75 -0
  43. package/open-sse/executors/kiro.js +508 -0
  44. package/open-sse/executors/ollama-local.js +14 -0
  45. package/open-sse/executors/opencode-go.js +41 -0
  46. package/open-sse/executors/opencode.js +32 -0
  47. package/open-sse/executors/perplexity-web.js +507 -0
  48. package/open-sse/executors/qoder.js +450 -0
  49. package/open-sse/executors/qwen.js +129 -0
  50. package/open-sse/executors/vertex.js +131 -0
  51. package/open-sse/executors/xiaomi-tokenplan.js +19 -0
  52. package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
  53. package/open-sse/handlers/chatCore/requestDetail.js +102 -0
  54. package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
  55. package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
  56. package/open-sse/handlers/chatCore.js +287 -0
  57. package/open-sse/handlers/embeddingProviders/_base.js +4 -0
  58. package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
  59. package/open-sse/handlers/embeddingProviders/index.js +23 -0
  60. package/open-sse/handlers/embeddingProviders/openai.js +39 -0
  61. package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
  62. package/open-sse/handlers/embeddingsCore.js +126 -0
  63. package/open-sse/handlers/fetch/index.js +237 -0
  64. package/open-sse/handlers/imageGenerationCore.js +189 -0
  65. package/open-sse/handlers/imageProviders/_base.js +31 -0
  66. package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
  67. package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
  68. package/open-sse/handlers/imageProviders/codex.js +198 -0
  69. package/open-sse/handlers/imageProviders/comfyui.js +8 -0
  70. package/open-sse/handlers/imageProviders/falAi.js +41 -0
  71. package/open-sse/handlers/imageProviders/gemini.js +25 -0
  72. package/open-sse/handlers/imageProviders/huggingface.js +22 -0
  73. package/open-sse/handlers/imageProviders/index.js +40 -0
  74. package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
  75. package/open-sse/handlers/imageProviders/openai.js +40 -0
  76. package/open-sse/handlers/imageProviders/runwayml.js +47 -0
  77. package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
  78. package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
  79. package/open-sse/handlers/responsesHandler.js +103 -0
  80. package/open-sse/handlers/search/callers.js +371 -0
  81. package/open-sse/handlers/search/chatSearch.js +409 -0
  82. package/open-sse/handlers/search/index.js +201 -0
  83. package/open-sse/handlers/search/normalizers.js +223 -0
  84. package/open-sse/handlers/sttCore.js +194 -0
  85. package/open-sse/handlers/ttsCore.js +74 -0
  86. package/open-sse/handlers/ttsProviders/_base.js +39 -0
  87. package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
  88. package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
  89. package/open-sse/handlers/ttsProviders/gemini.js +117 -0
  90. package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
  91. package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
  92. package/open-sse/handlers/ttsProviders/index.js +50 -0
  93. package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
  94. package/open-sse/handlers/ttsProviders/minimax.js +59 -0
  95. package/open-sse/handlers/ttsProviders/openai.js +30 -0
  96. package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
  97. package/open-sse/index.js +82 -0
  98. package/open-sse/rtk/applyFilter.js +15 -0
  99. package/open-sse/rtk/autodetect.js +111 -0
  100. package/open-sse/rtk/caveman.js +100 -0
  101. package/open-sse/rtk/cavemanPrompts.js +78 -0
  102. package/open-sse/rtk/constants.js +55 -0
  103. package/open-sse/rtk/filters/buildOutput.js +127 -0
  104. package/open-sse/rtk/filters/dedupLog.js +44 -0
  105. package/open-sse/rtk/filters/find.js +49 -0
  106. package/open-sse/rtk/filters/gitDiff.js +92 -0
  107. package/open-sse/rtk/filters/gitStatus.js +117 -0
  108. package/open-sse/rtk/filters/grep.js +48 -0
  109. package/open-sse/rtk/filters/ls.js +79 -0
  110. package/open-sse/rtk/filters/readNumbered.js +27 -0
  111. package/open-sse/rtk/filters/searchList.js +52 -0
  112. package/open-sse/rtk/filters/smartTruncate.js +15 -0
  113. package/open-sse/rtk/filters/tree.js +32 -0
  114. package/open-sse/rtk/index.js +155 -0
  115. package/open-sse/rtk/registry.js +38 -0
  116. package/open-sse/services/accountFallback.js +238 -0
  117. package/open-sse/services/combo.js +198 -0
  118. package/open-sse/services/compact.js +71 -0
  119. package/open-sse/services/kiroModels.js +332 -0
  120. package/open-sse/services/model.js +261 -0
  121. package/open-sse/services/oauthCredentialManager.js +151 -0
  122. package/open-sse/services/projectId.js +306 -0
  123. package/open-sse/services/provider.js +356 -0
  124. package/open-sse/services/qoderModels.js +214 -0
  125. package/open-sse/services/tokenRefresh.js +939 -0
  126. package/open-sse/services/usage.js +1496 -0
  127. package/open-sse/transformer/responsesTransformer.js +439 -0
  128. package/open-sse/transformer/streamToJsonConverter.js +103 -0
  129. package/open-sse/translator/formats.js +36 -0
  130. package/open-sse/translator/helpers/claudeHelper.js +216 -0
  131. package/open-sse/translator/helpers/geminiHelper.js +372 -0
  132. package/open-sse/translator/helpers/imageHelper.js +34 -0
  133. package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
  134. package/open-sse/translator/helpers/openaiHelper.js +130 -0
  135. package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
  136. package/open-sse/translator/helpers/toolCallHelper.js +148 -0
  137. package/open-sse/translator/index.js +251 -0
  138. package/open-sse/translator/request/antigravity-to-openai.js +229 -0
  139. package/open-sse/translator/request/claude-to-openai.js +232 -0
  140. package/open-sse/translator/request/gemini-to-openai.js +147 -0
  141. package/open-sse/translator/request/openai-responses.js +318 -0
  142. package/open-sse/translator/request/openai-to-claude.js +401 -0
  143. package/open-sse/translator/request/openai-to-commandcode.js +170 -0
  144. package/open-sse/translator/request/openai-to-cursor.js +183 -0
  145. package/open-sse/translator/request/openai-to-gemini.js +470 -0
  146. package/open-sse/translator/request/openai-to-kiro.js +629 -0
  147. package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
  148. package/open-sse/translator/request/openai-to-ollama.js +192 -0
  149. package/open-sse/translator/request/openai-to-vertex.js +42 -0
  150. package/open-sse/translator/response/claude-to-openai.js +206 -0
  151. package/open-sse/translator/response/commandcode-to-openai.js +197 -0
  152. package/open-sse/translator/response/cursor-to-openai.js +30 -0
  153. package/open-sse/translator/response/gemini-to-openai.js +245 -0
  154. package/open-sse/translator/response/kiro-to-openai.js +195 -0
  155. package/open-sse/translator/response/ollama-to-openai.js +152 -0
  156. package/open-sse/translator/response/openai-responses.js +590 -0
  157. package/open-sse/translator/response/openai-to-antigravity.js +122 -0
  158. package/open-sse/translator/response/openai-to-claude.js +266 -0
  159. package/open-sse/utils/bypassHandler.js +298 -0
  160. package/open-sse/utils/claudeCloaking.js +155 -0
  161. package/open-sse/utils/claudeHeaderCache.js +70 -0
  162. package/open-sse/utils/clientDetector.js +63 -0
  163. package/open-sse/utils/cursorChecksum.js +149 -0
  164. package/open-sse/utils/cursorProtobuf.js +904 -0
  165. package/open-sse/utils/debugLog.js +14 -0
  166. package/open-sse/utils/error.js +147 -0
  167. package/open-sse/utils/ollamaTransform.js +85 -0
  168. package/open-sse/utils/proxyFetch.js +368 -0
  169. package/open-sse/utils/reasoningContentInjector.js +79 -0
  170. package/open-sse/utils/requestLogger.js +260 -0
  171. package/open-sse/utils/responsesStreamHelpers.js +49 -0
  172. package/open-sse/utils/sessionManager.js +82 -0
  173. package/open-sse/utils/stream.js +462 -0
  174. package/open-sse/utils/streamHandler.js +250 -0
  175. package/open-sse/utils/streamHelpers.js +122 -0
  176. package/open-sse/utils/toolDeduper.js +49 -0
  177. package/open-sse/utils/usageTracking.js +347 -0
  178. package/package.json +100 -0
  179. package/postcss.config.mjs +12 -0
  180. package/public/favicon.svg +11 -0
  181. package/public/file.svg +1 -0
  182. package/public/globe.svg +1 -0
  183. package/public/i18n/literals/ar.json +195 -0
  184. package/public/i18n/literals/bn.json +195 -0
  185. package/public/i18n/literals/cs.json +195 -0
  186. package/public/i18n/literals/da.json +195 -0
  187. package/public/i18n/literals/de.json +195 -0
  188. package/public/i18n/literals/el.json +195 -0
  189. package/public/i18n/literals/es.json +195 -0
  190. package/public/i18n/literals/fi.json +195 -0
  191. package/public/i18n/literals/fr.json +195 -0
  192. package/public/i18n/literals/he.json +195 -0
  193. package/public/i18n/literals/hi.json +195 -0
  194. package/public/i18n/literals/hu.json +195 -0
  195. package/public/i18n/literals/id.json +195 -0
  196. package/public/i18n/literals/it.json +195 -0
  197. package/public/i18n/literals/ja.json +195 -0
  198. package/public/i18n/literals/ko.json +195 -0
  199. package/public/i18n/literals/nl.json +195 -0
  200. package/public/i18n/literals/no.json +195 -0
  201. package/public/i18n/literals/pl.json +195 -0
  202. package/public/i18n/literals/pt-BR.json +195 -0
  203. package/public/i18n/literals/pt-PT.json +195 -0
  204. package/public/i18n/literals/ro.json +195 -0
  205. package/public/i18n/literals/ru.json +195 -0
  206. package/public/i18n/literals/sv.json +195 -0
  207. package/public/i18n/literals/th.json +195 -0
  208. package/public/i18n/literals/tl.json +195 -0
  209. package/public/i18n/literals/tr.json +195 -0
  210. package/public/i18n/literals/uk.json +195 -0
  211. package/public/i18n/literals/ur.json +195 -0
  212. package/public/i18n/literals/vi.json +195 -0
  213. package/public/i18n/literals/zh-CN.json +772 -0
  214. package/public/i18n/literals/zh-TW.json +195 -0
  215. package/public/icons/discord.svg +4 -0
  216. package/public/icons/icon-192.svg +4 -0
  217. package/public/icons/icon-512.svg +4 -0
  218. package/public/next.svg +1 -0
  219. package/public/providers/alicode-intl.png +0 -0
  220. package/public/providers/alicode.png +0 -0
  221. package/public/providers/amp.png +0 -0
  222. package/public/providers/anthropic-m.png +0 -0
  223. package/public/providers/anthropic.png +0 -0
  224. package/public/providers/antigravity.png +0 -0
  225. package/public/providers/assemblyai.png +0 -0
  226. package/public/providers/aws-polly.png +0 -0
  227. package/public/providers/azure.png +0 -0
  228. package/public/providers/black-forest-labs.png +0 -0
  229. package/public/providers/blackbox.png +0 -0
  230. package/public/providers/brave-search.png +0 -0
  231. package/public/providers/byteplus.png +0 -0
  232. package/public/providers/cartesia.png +0 -0
  233. package/public/providers/cerebras.png +0 -0
  234. package/public/providers/chutes.png +0 -0
  235. package/public/providers/claude.png +0 -0
  236. package/public/providers/cline.png +0 -0
  237. package/public/providers/cloudflare-ai.png +0 -0
  238. package/public/providers/codebuddy.svg +37 -0
  239. package/public/providers/codex.png +0 -0
  240. package/public/providers/cohere.png +0 -0
  241. package/public/providers/comfyui.png +0 -0
  242. package/public/providers/commandcode.png +0 -0
  243. package/public/providers/continue.png +0 -0
  244. package/public/providers/copilot.png +0 -0
  245. package/public/providers/coqui.png +0 -0
  246. package/public/providers/cursor.png +0 -0
  247. package/public/providers/deepgram.png +0 -0
  248. package/public/providers/deepseek-tui.png +0 -0
  249. package/public/providers/deepseek.png +0 -0
  250. package/public/providers/droid.png +0 -0
  251. package/public/providers/edge-tts.png +0 -0
  252. package/public/providers/elevenlabs.png +0 -0
  253. package/public/providers/exa.png +0 -0
  254. package/public/providers/fal-ai.png +0 -0
  255. package/public/providers/firecrawl.png +0 -0
  256. package/public/providers/fireworks.png +0 -0
  257. package/public/providers/gemini-cli.png +0 -0
  258. package/public/providers/gemini.png +0 -0
  259. package/public/providers/github.png +0 -0
  260. package/public/providers/glm-cn.png +0 -0
  261. package/public/providers/glm.png +0 -0
  262. package/public/providers/google-pse.png +0 -0
  263. package/public/providers/google-tts.png +0 -0
  264. package/public/providers/grok-web.png +0 -0
  265. package/public/providers/groq.png +0 -0
  266. package/public/providers/hermes.png +0 -0
  267. package/public/providers/huggingface.png +0 -0
  268. package/public/providers/hyperbolic.png +0 -0
  269. package/public/providers/iflow.png +0 -0
  270. package/public/providers/inworld.png +0 -0
  271. package/public/providers/jcode.png +0 -0
  272. package/public/providers/jina-ai.png +0 -0
  273. package/public/providers/jina-reader.png +0 -0
  274. package/public/providers/kilocode.png +0 -0
  275. package/public/providers/kimi-coding.png +0 -0
  276. package/public/providers/kimi.png +0 -0
  277. package/public/providers/kiro.png +0 -0
  278. package/public/providers/linkup.png +0 -0
  279. package/public/providers/local-device.png +0 -0
  280. package/public/providers/minimax-cn.png +0 -0
  281. package/public/providers/minimax.png +0 -0
  282. package/public/providers/mistral.png +0 -0
  283. package/public/providers/nanobanana.png +0 -0
  284. package/public/providers/nebius.png +0 -0
  285. package/public/providers/nvidia.png +0 -0
  286. package/public/providers/oai-cc.png +0 -0
  287. package/public/providers/oai-r.png +0 -0
  288. package/public/providers/ollama-local.png +0 -0
  289. package/public/providers/ollama.png +0 -0
  290. package/public/providers/openai.png +0 -0
  291. package/public/providers/openclaw.png +0 -0
  292. package/public/providers/opencode-go.png +0 -0
  293. package/public/providers/opencode.png +0 -0
  294. package/public/providers/openrouter.png +0 -0
  295. package/public/providers/perplexity-web.png +0 -0
  296. package/public/providers/perplexity.png +0 -0
  297. package/public/providers/playht.png +0 -0
  298. package/public/providers/qoder.png +0 -0
  299. package/public/providers/qwen.png +0 -0
  300. package/public/providers/recraft.png +0 -0
  301. package/public/providers/roo.png +0 -0
  302. package/public/providers/runwayml.png +0 -0
  303. package/public/providers/sdwebui.png +0 -0
  304. package/public/providers/searchapi.png +0 -0
  305. package/public/providers/searxng.png +0 -0
  306. package/public/providers/serper.png +0 -0
  307. package/public/providers/siliconflow.png +0 -0
  308. package/public/providers/stability-ai.png +0 -0
  309. package/public/providers/tavily.png +0 -0
  310. package/public/providers/together.png +0 -0
  311. package/public/providers/topaz.png +0 -0
  312. package/public/providers/tortoise.png +0 -0
  313. package/public/providers/vertex-partner.png +0 -0
  314. package/public/providers/vertex.png +0 -0
  315. package/public/providers/volcengine-ark.png +0 -0
  316. package/public/providers/voyage-ai.png +0 -0
  317. package/public/providers/xai.png +0 -0
  318. package/public/providers/xiaomi-mimo.png +0 -0
  319. package/public/providers/xiaomi-tokenplan.png +0 -0
  320. package/public/providers/youcom.png +0 -0
  321. package/public/sw.js +22 -0
  322. package/public/vercel.svg +1 -0
  323. package/public/window.svg +1 -0
  324. package/scripts/compact-request-details.mjs +71 -0
  325. package/scripts/import-codex-gptjson.mjs +342 -0
  326. package/scripts/start-standalone.mjs +25 -0
  327. package/scripts/translate-readme.js +201 -0
  328. package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
  329. package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
  330. package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
  331. package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
  332. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
  333. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
  334. package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
  335. package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
  336. package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
  337. package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
  338. package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
  339. package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
  340. package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
  341. package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
  342. package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
  343. package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
  344. package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
  345. package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
  346. package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
  347. package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
  348. package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
  349. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
  350. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
  351. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
  352. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
  353. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
  354. package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
  355. package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
  356. package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
  357. package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
  358. package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
  359. package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
  360. package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
  361. package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
  362. package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
  363. package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
  364. package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
  365. package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
  366. package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
  367. package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
  368. package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
  369. package/src/app/(dashboard)/dashboard/page.js +7 -0
  370. package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
  371. package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
  372. package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
  373. package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
  374. package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
  375. package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
  376. package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
  377. package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
  378. package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
  379. package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
  380. package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
  381. package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
  382. package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
  383. package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
  384. package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
  385. package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
  386. package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
  387. package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
  388. package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
  389. package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
  390. package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
  391. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
  392. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
  393. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
  394. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
  395. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
  396. package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
  397. package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
  398. package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
  399. package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
  400. package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
  401. package/src/app/(dashboard)/layout.js +6 -0
  402. package/src/app/api/auth/login/route.js +76 -0
  403. package/src/app/api/auth/logout/route.js +12 -0
  404. package/src/app/api/auth/oidc/callback/route.js +87 -0
  405. package/src/app/api/auth/oidc/start/route.js +52 -0
  406. package/src/app/api/auth/oidc/test/route.js +84 -0
  407. package/src/app/api/auth/status/route.js +45 -0
  408. package/src/app/api/cli-tools/all-statuses/route.js +46 -0
  409. package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
  410. package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
  411. package/src/app/api/cli-tools/claude-settings/route.js +203 -0
  412. package/src/app/api/cli-tools/cline-settings/route.js +133 -0
  413. package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
  414. package/src/app/api/cli-tools/codex-settings/route.js +239 -0
  415. package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
  416. package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
  417. package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
  418. package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
  419. package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
  420. package/src/app/api/cli-tools/droid-settings/route.js +213 -0
  421. package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
  422. package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
  423. package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
  424. package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
  425. package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
  426. package/src/app/api/combos/[id]/route.js +81 -0
  427. package/src/app/api/combos/route.js +48 -0
  428. package/src/app/api/health/route.js +15 -0
  429. package/src/app/api/init/route.js +4 -0
  430. package/src/app/api/keys/[id]/route.js +58 -0
  431. package/src/app/api/keys/route.js +42 -0
  432. package/src/app/api/locale/route.js +30 -0
  433. package/src/app/api/mcp/[plugin]/message/route.js +21 -0
  434. package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
  435. package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
  436. package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
  437. package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
  438. package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
  439. package/src/app/api/media-providers/tts/voices/route.js +99 -0
  440. package/src/app/api/models/alias/route.js +53 -0
  441. package/src/app/api/models/availability/route.js +103 -0
  442. package/src/app/api/models/custom/route.js +48 -0
  443. package/src/app/api/models/disabled/route.js +50 -0
  444. package/src/app/api/models/route.js +64 -0
  445. package/src/app/api/models/test/ping.js +191 -0
  446. package/src/app/api/models/test/route.js +14 -0
  447. package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
  448. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
  449. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
  450. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
  451. package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
  452. package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
  453. package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
  454. package/src/app/api/oauth/codex/import-token/route.js +96 -0
  455. package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
  456. package/src/app/api/oauth/cursor/import/route.js +100 -0
  457. package/src/app/api/oauth/gitlab/pat/route.js +62 -0
  458. package/src/app/api/oauth/iflow/cookie/route.js +137 -0
  459. package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
  460. package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
  461. package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
  462. package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
  463. package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
  464. package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
  465. package/src/app/api/oauth/kiro/import/route.js +110 -0
  466. package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
  467. package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
  468. package/src/app/api/pricing/route.js +134 -0
  469. package/src/app/api/provider-nodes/[id]/route.js +101 -0
  470. package/src/app/api/provider-nodes/route.js +104 -0
  471. package/src/app/api/provider-nodes/validate/route.js +201 -0
  472. package/src/app/api/providers/[id]/models/route.js +526 -0
  473. package/src/app/api/providers/[id]/route.js +189 -0
  474. package/src/app/api/providers/[id]/test/route.js +23 -0
  475. package/src/app/api/providers/[id]/test/testUtils.js +714 -0
  476. package/src/app/api/providers/[id]/test-models/route.js +66 -0
  477. package/src/app/api/providers/client/route.js +126 -0
  478. package/src/app/api/providers/kilo/free-models/route.js +55 -0
  479. package/src/app/api/providers/route.js +206 -0
  480. package/src/app/api/providers/suggested-models/filters.js +20 -0
  481. package/src/app/api/providers/suggested-models/route.js +32 -0
  482. package/src/app/api/providers/test-batch/route.js +131 -0
  483. package/src/app/api/providers/validate/route.js +637 -0
  484. package/src/app/api/proxy-pools/[id]/route.js +123 -0
  485. package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
  486. package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
  487. package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
  488. package/src/app/api/proxy-pools/route.js +93 -0
  489. package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
  490. package/src/app/api/settings/database/route.js +36 -0
  491. package/src/app/api/settings/proxy-test/route.js +23 -0
  492. package/src/app/api/settings/require-login/route.js +15 -0
  493. package/src/app/api/settings/route.js +100 -0
  494. package/src/app/api/shutdown/route.js +24 -0
  495. package/src/app/api/tags/route.js +18 -0
  496. package/src/app/api/translator/console-logs/route.js +24 -0
  497. package/src/app/api/translator/console-logs/stream/route.js +79 -0
  498. package/src/app/api/translator/load/route.js +45 -0
  499. package/src/app/api/translator/save/route.js +44 -0
  500. package/src/app/api/translator/send/route.js +94 -0
  501. package/src/app/api/translator/translate/route.js +90 -0
  502. package/src/app/api/tunnel/disable/route.js +12 -0
  503. package/src/app/api/tunnel/enable/route.js +16 -0
  504. package/src/app/api/tunnel/status/route.js +13 -0
  505. package/src/app/api/tunnel/tailscale-check/route.js +50 -0
  506. package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
  507. package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
  508. package/src/app/api/tunnel/tailscale-install/route.js +72 -0
  509. package/src/app/api/usage/[connectionId]/route.js +188 -0
  510. package/src/app/api/usage/chart/route.js +21 -0
  511. package/src/app/api/usage/history/route.js +12 -0
  512. package/src/app/api/usage/logs/route.js +12 -0
  513. package/src/app/api/usage/providers/route.js +42 -0
  514. package/src/app/api/usage/request-details/route.js +57 -0
  515. package/src/app/api/usage/request-logs/route.js +13 -0
  516. package/src/app/api/usage/stats/route.js +23 -0
  517. package/src/app/api/usage/stream/route.js +79 -0
  518. package/src/app/api/v1/api/chat/route.js +37 -0
  519. package/src/app/api/v1/audio/speech/route.js +16 -0
  520. package/src/app/api/v1/audio/transcriptions/route.js +19 -0
  521. package/src/app/api/v1/audio/voices/route.js +68 -0
  522. package/src/app/api/v1/chat/completions/route.js +35 -0
  523. package/src/app/api/v1/embeddings/route.js +21 -0
  524. package/src/app/api/v1/images/generations/route.js +16 -0
  525. package/src/app/api/v1/messages/count_tokens/route.js +52 -0
  526. package/src/app/api/v1/messages/route.js +36 -0
  527. package/src/app/api/v1/models/[kind]/route.js +55 -0
  528. package/src/app/api/v1/models/info/route.js +110 -0
  529. package/src/app/api/v1/models/route.js +451 -0
  530. package/src/app/api/v1/responses/compact/route.js +37 -0
  531. package/src/app/api/v1/responses/route.js +30 -0
  532. package/src/app/api/v1/route.js +1 -0
  533. package/src/app/api/v1/search/route.js +21 -0
  534. package/src/app/api/v1/web/fetch/route.js +21 -0
  535. package/src/app/api/v1beta/models/[...path]/route.js +328 -0
  536. package/src/app/api/v1beta/models/route.js +44 -0
  537. package/src/app/api/version/route.js +45 -0
  538. package/src/app/api/version/shutdown/route.js +15 -0
  539. package/src/app/api/version/update/route.js +21 -0
  540. package/src/app/callback/page.js +148 -0
  541. package/src/app/dashboard/settings/pricing/page.js +173 -0
  542. package/src/app/favicon.ico +0 -0
  543. package/src/app/globals.css +496 -0
  544. package/src/app/landing/components/AnimatedBackground.js +57 -0
  545. package/src/app/landing/components/Features.js +133 -0
  546. package/src/app/landing/components/FlowAnimation.js +175 -0
  547. package/src/app/landing/components/Footer.js +61 -0
  548. package/src/app/landing/components/GetStarted.js +97 -0
  549. package/src/app/landing/components/HeroSection.js +47 -0
  550. package/src/app/landing/components/HowItWorks.js +66 -0
  551. package/src/app/landing/components/Navigation.js +72 -0
  552. package/src/app/landing/page.js +106 -0
  553. package/src/app/layout.js +49 -0
  554. package/src/app/login/page.js +197 -0
  555. package/src/app/manifest.js +30 -0
  556. package/src/app/page.js +5 -0
  557. package/src/dashboardGuard.js +242 -0
  558. package/src/i18n/RuntimeI18nProvider.js +27 -0
  559. package/src/i18n/config.js +146 -0
  560. package/src/i18n/runtime.js +162 -0
  561. package/src/lib/appUpdater.js +200 -0
  562. package/src/lib/auth/dashboardSession.js +68 -0
  563. package/src/lib/auth/loginLimiter.js +52 -0
  564. package/src/lib/auth/oidc.js +234 -0
  565. package/src/lib/consoleLogBuffer.js +79 -0
  566. package/src/lib/dataDir.js +29 -0
  567. package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
  568. package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
  569. package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
  570. package/src/lib/db/adapters/sqljsAdapter.js +115 -0
  571. package/src/lib/db/backup.js +35 -0
  572. package/src/lib/db/driver.js +85 -0
  573. package/src/lib/db/helpers/jsonCol.js +9 -0
  574. package/src/lib/db/helpers/kvStore.js +39 -0
  575. package/src/lib/db/helpers/metaStore.js +22 -0
  576. package/src/lib/db/index.js +171 -0
  577. package/src/lib/db/migrate.js +286 -0
  578. package/src/lib/db/migrations/001-initial.js +14 -0
  579. package/src/lib/db/migrations/index.js +10 -0
  580. package/src/lib/db/paths.js +18 -0
  581. package/src/lib/db/repos/aliasRepo.js +62 -0
  582. package/src/lib/db/repos/apiKeysRepo.js +75 -0
  583. package/src/lib/db/repos/combosRepo.js +73 -0
  584. package/src/lib/db/repos/connectionsRepo.js +226 -0
  585. package/src/lib/db/repos/disabledModelsRepo.js +56 -0
  586. package/src/lib/db/repos/nodesRepo.js +95 -0
  587. package/src/lib/db/repos/pricingRepo.js +108 -0
  588. package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
  589. package/src/lib/db/repos/requestDetailsRepo.js +259 -0
  590. package/src/lib/db/repos/settingsRepo.js +104 -0
  591. package/src/lib/db/repos/usageRepo.js +731 -0
  592. package/src/lib/db/schema.js +157 -0
  593. package/src/lib/db/version.js +21 -0
  594. package/src/lib/disabledModelsDb.js +4 -0
  595. package/src/lib/localDb.js +21 -0
  596. package/src/lib/mcp/stdioSseBridge.js +198 -0
  597. package/src/lib/mitmAliasCache.js +46 -0
  598. package/src/lib/network/connectionProxy.js +160 -0
  599. package/src/lib/network/initOutboundProxy.js +25 -0
  600. package/src/lib/network/outboundProxy.js +68 -0
  601. package/src/lib/network/proxyTest.js +91 -0
  602. package/src/lib/oauth/constants/oauth.js +284 -0
  603. package/src/lib/oauth/constants/xai.js +61 -0
  604. package/src/lib/oauth/providers.js +1506 -0
  605. package/src/lib/oauth/services/antigravity.js +321 -0
  606. package/src/lib/oauth/services/claude.js +136 -0
  607. package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
  608. package/src/lib/oauth/services/codex.js +144 -0
  609. package/src/lib/oauth/services/cursor.js +179 -0
  610. package/src/lib/oauth/services/gemini.js +240 -0
  611. package/src/lib/oauth/services/github.js +225 -0
  612. package/src/lib/oauth/services/iflow.js +202 -0
  613. package/src/lib/oauth/services/index.js +17 -0
  614. package/src/lib/oauth/services/kiro.js +334 -0
  615. package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
  616. package/src/lib/oauth/services/kiroConnections.js +93 -0
  617. package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
  618. package/src/lib/oauth/services/oauth.js +157 -0
  619. package/src/lib/oauth/services/openai.js +123 -0
  620. package/src/lib/oauth/services/qoder.js +216 -0
  621. package/src/lib/oauth/services/qwen.js +170 -0
  622. package/src/lib/oauth/services/xai.js +238 -0
  623. package/src/lib/oauth/utils/banner.js +63 -0
  624. package/src/lib/oauth/utils/pkce.js +39 -0
  625. package/src/lib/oauth/utils/server.js +415 -0
  626. package/src/lib/oauth/utils/ui.js +48 -0
  627. package/src/lib/providerNormalization.js +45 -0
  628. package/src/lib/qoder/constants.js +64 -0
  629. package/src/lib/qoder/cosy.js +175 -0
  630. package/src/lib/qoder/encoding.js +55 -0
  631. package/src/lib/requestDetailsDb.js +4 -0
  632. package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
  633. package/src/lib/tunnel/cloudflare/config.js +9 -0
  634. package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
  635. package/src/lib/tunnel/cloudflare/manager.js +151 -0
  636. package/src/lib/tunnel/cloudflare/pid.js +23 -0
  637. package/src/lib/tunnel/index.js +48 -0
  638. package/src/lib/tunnel/shared/dnsResolver.js +17 -0
  639. package/src/lib/tunnel/shared/internetCheck.js +26 -0
  640. package/src/lib/tunnel/shared/state.js +41 -0
  641. package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
  642. package/src/lib/tunnel/tailscale/config.js +7 -0
  643. package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
  644. package/src/lib/tunnel/tailscale/manager.js +129 -0
  645. package/src/lib/tunnel/tailscale/tailscale.js +790 -0
  646. package/src/lib/updater/updater.js +235 -0
  647. package/src/lib/usage/fetcher.js +208 -0
  648. package/src/lib/usageDb.js +7 -0
  649. package/src/mitm/antigravityIdeVersion.js +50 -0
  650. package/src/mitm/cert/generate.js +32 -0
  651. package/src/mitm/cert/install.js +269 -0
  652. package/src/mitm/cert/rootCA.js +173 -0
  653. package/src/mitm/config.js +87 -0
  654. package/src/mitm/dbReader.js +22 -0
  655. package/src/mitm/dns/dnsConfig.js +266 -0
  656. package/src/mitm/handlers/antigravity.js +33 -0
  657. package/src/mitm/handlers/base.js +226 -0
  658. package/src/mitm/handlers/copilot.js +35 -0
  659. package/src/mitm/handlers/cursor.js +15 -0
  660. package/src/mitm/handlers/kiro.js +526 -0
  661. package/src/mitm/logger.js +106 -0
  662. package/src/mitm/manager.js +851 -0
  663. package/src/mitm/paths.js +32 -0
  664. package/src/mitm/server.js +435 -0
  665. package/src/mitm/winElevated.js +81 -0
  666. package/src/models/index.js +38 -0
  667. package/src/proxy.js +5 -0
  668. package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
  669. package/src/shared/components/Avatar.js +88 -0
  670. package/src/shared/components/Badge.js +54 -0
  671. package/src/shared/components/BulkAccountAutomationModal.js +508 -0
  672. package/src/shared/components/Button.js +56 -0
  673. package/src/shared/components/Card.js +116 -0
  674. package/src/shared/components/ChangelogModal.js +97 -0
  675. package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
  676. package/src/shared/components/ComboFormModal.js +176 -0
  677. package/src/shared/components/CursorAuthModal.js +212 -0
  678. package/src/shared/components/DonateModal.js +136 -0
  679. package/src/shared/components/Drawer.js +82 -0
  680. package/src/shared/components/EditConnectionModal.js +286 -0
  681. package/src/shared/components/Footer.js +132 -0
  682. package/src/shared/components/GitLabAuthModal.js +194 -0
  683. package/src/shared/components/Header.js +380 -0
  684. package/src/shared/components/HeaderLanguage.js +46 -0
  685. package/src/shared/components/HeaderMenu.js +126 -0
  686. package/src/shared/components/IFlowCookieModal.js +132 -0
  687. package/src/shared/components/Input.js +65 -0
  688. package/src/shared/components/KiroAuthModal.js +1171 -0
  689. package/src/shared/components/KiroOAuthWrapper.js +149 -0
  690. package/src/shared/components/KiroSocialOAuthModal.js +205 -0
  691. package/src/shared/components/LanguageSwitcher.js +190 -0
  692. package/src/shared/components/Loading.js +75 -0
  693. package/src/shared/components/ManualConfigModal.js +44 -0
  694. package/src/shared/components/McpMarketplaceModal.js +255 -0
  695. package/src/shared/components/Modal.js +146 -0
  696. package/src/shared/components/ModelSelectModal.js +537 -0
  697. package/src/shared/components/NineRemoteButton.js +23 -0
  698. package/src/shared/components/NineRemotePromoModal.js +99 -0
  699. package/src/shared/components/NoAuthProxyCard.js +86 -0
  700. package/src/shared/components/OAuthModal.js +682 -0
  701. package/src/shared/components/Pagination.js +150 -0
  702. package/src/shared/components/PricingModal.js +208 -0
  703. package/src/shared/components/ProviderIcon.js +63 -0
  704. package/src/shared/components/ProviderInfoCard.js +82 -0
  705. package/src/shared/components/RequestLogger.js +121 -0
  706. package/src/shared/components/SegmentedControl.js +48 -0
  707. package/src/shared/components/Select.js +67 -0
  708. package/src/shared/components/Sidebar.js +441 -0
  709. package/src/shared/components/ThemeProvider.js +15 -0
  710. package/src/shared/components/ThemeToggle.js +42 -0
  711. package/src/shared/components/Toggle.js +69 -0
  712. package/src/shared/components/Tooltip.js +25 -0
  713. package/src/shared/components/UsageStats.js +505 -0
  714. package/src/shared/components/index.js +46 -0
  715. package/src/shared/components/layouts/AuthLayout.js +29 -0
  716. package/src/shared/components/layouts/DashboardLayout.js +104 -0
  717. package/src/shared/components/layouts/index.js +4 -0
  718. package/src/shared/constants/cliTools.js +397 -0
  719. package/src/shared/constants/colors.js +77 -0
  720. package/src/shared/constants/config.js +99 -0
  721. package/src/shared/constants/coworkPlugins.js +75 -0
  722. package/src/shared/constants/index.js +4 -0
  723. package/src/shared/constants/locales.js +36 -0
  724. package/src/shared/constants/mitmToolHosts.js +12 -0
  725. package/src/shared/constants/models.js +38 -0
  726. package/src/shared/constants/pricing.js +303 -0
  727. package/src/shared/constants/providers.js +289 -0
  728. package/src/shared/constants/skills.js +78 -0
  729. package/src/shared/constants/ttsProviders.js +138 -0
  730. package/src/shared/hooks/index.js +2 -0
  731. package/src/shared/hooks/useCopyToClipboard.js +43 -0
  732. package/src/shared/hooks/useTheme.js +60 -0
  733. package/src/shared/services/bootstrap.js +12 -0
  734. package/src/shared/services/initializeApp.js +268 -0
  735. package/src/shared/utils/api.js +93 -0
  736. package/src/shared/utils/apiKey.js +98 -0
  737. package/src/shared/utils/clineAuth.js +37 -0
  738. package/src/shared/utils/cn.js +11 -0
  739. package/src/shared/utils/connectionStatus.js +162 -0
  740. package/src/shared/utils/index.js +40 -0
  741. package/src/shared/utils/machine.js +6 -0
  742. package/src/shared/utils/machineId.js +66 -0
  743. package/src/shared/utils/providerModelsFetcher.js +30 -0
  744. package/src/sse/handlers/chat.js +261 -0
  745. package/src/sse/handlers/embeddings.js +141 -0
  746. package/src/sse/handlers/fetch.js +213 -0
  747. package/src/sse/handlers/imageGeneration.js +142 -0
  748. package/src/sse/handlers/search.js +206 -0
  749. package/src/sse/handlers/stt.js +88 -0
  750. package/src/sse/handlers/tts.js +114 -0
  751. package/src/sse/services/auth.js +346 -0
  752. package/src/sse/services/codexGateway.js +215 -0
  753. package/src/sse/services/model.js +99 -0
  754. package/src/sse/services/tokenRefresh.js +319 -0
  755. package/src/sse/utils/logger.js +75 -0
  756. package/src/store/headerSearchStore.js +19 -0
  757. package/src/store/index.js +6 -0
  758. package/src/store/notificationStore.js +45 -0
  759. package/src/store/providerStore.js +55 -0
  760. package/src/store/settingsStore.js +51 -0
  761. package/src/store/themeStore.js +54 -0
  762. package/src/store/userStore.js +20 -0
  763. package/start.sh +4 -0
@@ -0,0 +1,1506 @@
1
+ /**
2
+ * OAuth Provider Configurations and Handlers
3
+ * Centralized DRY approach for all OAuth providers
4
+ */
5
+
6
+ // Ensure outbound fetch respects HTTP(S)_PROXY/ALL_PROXY in Node runtime
7
+ import "open-sse/index.js";
8
+ import crypto from "crypto";
9
+
10
+ import { generatePKCE, generateState } from "./utils/pkce";
11
+ import {
12
+ CLAUDE_CONFIG,
13
+ CODEX_CONFIG,
14
+ GEMINI_CONFIG,
15
+ QWEN_CONFIG,
16
+ QODER_CONFIG,
17
+ IFLOW_CONFIG,
18
+ ANTIGRAVITY_CONFIG,
19
+ GITHUB_CONFIG,
20
+ KIRO_CONFIG,
21
+ CURSOR_CONFIG,
22
+ KIMI_CODING_CONFIG,
23
+ KILOCODE_CONFIG,
24
+ CLINE_CONFIG,
25
+ GITLAB_CONFIG,
26
+ CODEBUDDY_CONFIG,
27
+ getOAuthClientMetadata,
28
+ } from "./constants/oauth";
29
+ import { XAI_CONFIG, XAI_PKCE_VERIFIER_BYTES } from "./constants/xai";
30
+
31
+ // Inlined from services/xai.js to keep web route bundle free of `open` (CLI-only) package
32
+ let cachedXaiDiscovery = null;
33
+
34
+ function validateXaiOAuthEndpoint(rawUrl, field) {
35
+ const value = String(rawUrl || "").trim();
36
+ if (!value) throw new Error(`xai discovery ${field} is empty`);
37
+ let parsed;
38
+ try { parsed = new URL(value); } catch (err) {
39
+ throw new Error(`xai discovery ${field} is invalid: ${err.message}`);
40
+ }
41
+ if (parsed.protocol !== "https:") throw new Error(`xai discovery ${field} must use https: ${value}`);
42
+ const host = parsed.hostname.toLowerCase().trim();
43
+ if (host !== "x.ai" && !host.endsWith(".x.ai")) {
44
+ throw new Error(`xai discovery ${field} host ${host} is not on x.ai`);
45
+ }
46
+ return value;
47
+ }
48
+
49
+ async function discoverXaiEndpoints() {
50
+ if (cachedXaiDiscovery) return cachedXaiDiscovery;
51
+ try {
52
+ const res = await fetch(XAI_CONFIG.discoveryUrl, { headers: { Accept: "application/json" } });
53
+ if (res.ok) {
54
+ const data = await res.json();
55
+ cachedXaiDiscovery = {
56
+ authorizeUrl: validateXaiOAuthEndpoint(data.authorization_endpoint, "authorization_endpoint"),
57
+ tokenUrl: validateXaiOAuthEndpoint(data.token_endpoint, "token_endpoint"),
58
+ };
59
+ return cachedXaiDiscovery;
60
+ }
61
+ } catch { /* fall through to static fallback */ }
62
+ cachedXaiDiscovery = { authorizeUrl: XAI_CONFIG.authorizeUrl, tokenUrl: XAI_CONFIG.tokenUrl };
63
+ return cachedXaiDiscovery;
64
+ }
65
+
66
+ function decodeXaiIdTokenEmail(idToken) {
67
+ if (!idToken || typeof idToken !== "string") return undefined;
68
+ const parts = idToken.split(".");
69
+ if (parts.length !== 3) return undefined;
70
+ try {
71
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
72
+ const padding = (BASE64_BLOCK_SIZE - (base64.length % BASE64_BLOCK_SIZE)) % BASE64_BLOCK_SIZE;
73
+ const json = Buffer.from(base64 + "=".repeat(padding), "base64").toString("utf8");
74
+ const payload = JSON.parse(json);
75
+ return payload.email || payload.preferred_username || payload.sub || undefined;
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ const BASE64_BLOCK_SIZE = 4;
82
+
83
+ /**
84
+ * Decode JWT access token and extract a stable account identifier for display/upsert.
85
+ * @param {string} accessToken
86
+ * @returns {string|undefined}
87
+ */
88
+ function decodeJwtPayload(jwt) {
89
+ try {
90
+ if (!jwt || typeof jwt !== "string") return null;
91
+ const parts = jwt.split(".");
92
+ if (parts.length !== 3) return null;
93
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
94
+ const missingPadding = (BASE64_BLOCK_SIZE - (base64.length % BASE64_BLOCK_SIZE)) % BASE64_BLOCK_SIZE;
95
+ const padded = base64 + "=".repeat(missingPadding);
96
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ function extractEmailFromAccessToken(accessToken) {
103
+ const payload = decodeJwtPayload(accessToken);
104
+ if (!payload) return undefined;
105
+ return payload.email || payload.preferred_username || payload.sub || undefined;
106
+ }
107
+
108
+ // Extract codex account info from id_token or access token
109
+ export function extractCodexAccountInfo(idToken) {
110
+ const payload = decodeJwtPayload(idToken);
111
+ if (!payload) return {};
112
+ const chatgpt = payload["https://api.openai.com/auth"] || {};
113
+ return {
114
+ email: payload.email,
115
+ chatgptAccountId: chatgpt.chatgpt_account_id || payload.account_id,
116
+ chatgptPlanType: chatgpt.chatgpt_plan_type || payload.plan_type,
117
+ };
118
+ }
119
+
120
+ // Provider configurations
121
+ const PROVIDERS = {
122
+ claude: {
123
+ config: CLAUDE_CONFIG,
124
+ flowType: "authorization_code_pkce",
125
+ buildAuthUrl: (config, redirectUri, state, codeChallenge) => {
126
+ const params = new URLSearchParams({
127
+ code: "true",
128
+ client_id: config.clientId,
129
+ response_type: "code",
130
+ redirect_uri: redirectUri,
131
+ scope: config.scopes.join(" "),
132
+ code_challenge: codeChallenge,
133
+ code_challenge_method: config.codeChallengeMethod,
134
+ state: state,
135
+ });
136
+ return `${config.authorizeUrl}?${params.toString()}`;
137
+ },
138
+ exchangeToken: async (config, code, redirectUri, codeVerifier, state) => {
139
+ // Parse code - may contain state after #
140
+ let authCode = code;
141
+ let codeState = "";
142
+ if (authCode.includes("#")) {
143
+ const parts = authCode.split("#");
144
+ authCode = parts[0];
145
+ codeState = parts[1] || "";
146
+ }
147
+
148
+ const response = await fetch(config.tokenUrl, {
149
+ method: "POST",
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ Accept: "application/json",
153
+ },
154
+ body: JSON.stringify({
155
+ code: authCode,
156
+ state: codeState || state,
157
+ grant_type: "authorization_code",
158
+ client_id: config.clientId,
159
+ redirect_uri: redirectUri,
160
+ code_verifier: codeVerifier,
161
+ }),
162
+ });
163
+
164
+ if (!response.ok) {
165
+ const error = await response.text();
166
+ throw new Error(`Token exchange failed: ${error}`);
167
+ }
168
+
169
+ return await response.json();
170
+ },
171
+ mapTokens: (tokens) => ({
172
+ accessToken: tokens.access_token,
173
+ refreshToken: tokens.refresh_token,
174
+ expiresIn: tokens.expires_in,
175
+ scope: tokens.scope,
176
+ }),
177
+ },
178
+
179
+ codex: {
180
+ config: CODEX_CONFIG,
181
+ flowType: "authorization_code_pkce",
182
+ fixedPort: 1455,
183
+ callbackPath: "/auth/callback",
184
+ buildAuthUrl: (config, redirectUri, state, codeChallenge) => {
185
+ const params = {
186
+ response_type: "code",
187
+ client_id: config.clientId,
188
+ redirect_uri: redirectUri,
189
+ scope: config.scope,
190
+ code_challenge: codeChallenge,
191
+ code_challenge_method: config.codeChallengeMethod,
192
+ ...config.extraParams,
193
+ state: state,
194
+ };
195
+ const queryString = Object.entries(params)
196
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
197
+ .join("&");
198
+ return `${config.authorizeUrl}?${queryString}`;
199
+ },
200
+ exchangeToken: async (config, code, redirectUri, codeVerifier) => {
201
+ const response = await fetch(config.tokenUrl, {
202
+ method: "POST",
203
+ headers: {
204
+ "Content-Type": "application/x-www-form-urlencoded",
205
+ Accept: "application/json",
206
+ },
207
+ body: new URLSearchParams({
208
+ grant_type: "authorization_code",
209
+ client_id: config.clientId,
210
+ code: code,
211
+ redirect_uri: redirectUri,
212
+ code_verifier: codeVerifier,
213
+ }),
214
+ });
215
+
216
+ if (!response.ok) {
217
+ const error = await response.text();
218
+ throw new Error(`Token exchange failed: ${error}`);
219
+ }
220
+
221
+ return await response.json();
222
+ },
223
+ mapTokens: (tokens) => {
224
+ const info = extractCodexAccountInfo(tokens.id_token);
225
+ const mapped = {
226
+ accessToken: tokens.access_token,
227
+ refreshToken: tokens.refresh_token,
228
+ idToken: tokens.id_token,
229
+ expiresIn: tokens.expires_in,
230
+ lastRefreshAt: new Date().toISOString(),
231
+ };
232
+ const email = info.email || extractEmailFromAccessToken(tokens.access_token);
233
+ if (email) mapped.email = email;
234
+ if (info.chatgptAccountId || info.chatgptPlanType) {
235
+ mapped.providerSpecificData = {
236
+ chatgptAccountId: info.chatgptAccountId,
237
+ chatgptPlanType: info.chatgptPlanType,
238
+ };
239
+ }
240
+ return mapped;
241
+ },
242
+ },
243
+
244
+ xai: {
245
+ config: XAI_CONFIG,
246
+ flowType: "authorization_code_pkce",
247
+ fixedPort: XAI_CONFIG.loopbackPort,
248
+ callbackPath: XAI_CONFIG.callbackPath,
249
+ pkceVerifierBytes: XAI_PKCE_VERIFIER_BYTES,
250
+ prepareConfig: async (config) => {
251
+ const endpoints = await discoverXaiEndpoints();
252
+ return {
253
+ ...config,
254
+ authorizeUrl: endpoints.authorizeUrl,
255
+ tokenUrl: endpoints.tokenUrl,
256
+ };
257
+ },
258
+ buildAuthUrl: (config, redirectUri, state, codeChallenge) => {
259
+ // Mirror CLIProxyAPI BuildAuthorizeURL: includes nonce, plan, referrer
260
+ const nonce = crypto.randomBytes(16).toString("hex");
261
+ const params = {
262
+ response_type: "code",
263
+ client_id: config.clientId,
264
+ redirect_uri: redirectUri,
265
+ scope: config.scope,
266
+ code_challenge: codeChallenge,
267
+ code_challenge_method: config.codeChallengeMethod,
268
+ state,
269
+ nonce,
270
+ plan: "generic",
271
+ referrer: "cli-proxy-api",
272
+ };
273
+ const qs = Object.entries(params)
274
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
275
+ .join("&");
276
+ return `${config.authorizeUrl}?${qs}`;
277
+ },
278
+ exchangeToken: async (config, code, redirectUri, codeVerifier) => {
279
+ const response = await fetch(config.tokenUrl, {
280
+ method: "POST",
281
+ headers: {
282
+ "Content-Type": "application/x-www-form-urlencoded",
283
+ Accept: "application/json",
284
+ },
285
+ body: new URLSearchParams({
286
+ grant_type: "authorization_code",
287
+ client_id: config.clientId,
288
+ code,
289
+ redirect_uri: redirectUri,
290
+ code_verifier: codeVerifier,
291
+ }),
292
+ });
293
+ if (!response.ok) {
294
+ const error = await response.text();
295
+ throw new Error(`xAI token exchange failed: ${error}`);
296
+ }
297
+ return await response.json();
298
+ },
299
+ mapTokens: (tokens) => {
300
+ const mapped = {
301
+ accessToken: tokens.access_token,
302
+ refreshToken: tokens.refresh_token,
303
+ expiresIn: tokens.expires_in,
304
+ scope: tokens.scope,
305
+ };
306
+ const email = decodeXaiIdTokenEmail(tokens.id_token);
307
+ if (email) mapped.email = email;
308
+ if (tokens.id_token) {
309
+ mapped.providerSpecificData = { idToken: tokens.id_token };
310
+ }
311
+ return mapped;
312
+ },
313
+ },
314
+
315
+ "gemini-cli": {
316
+ config: GEMINI_CONFIG,
317
+ flowType: "authorization_code",
318
+ buildAuthUrl: (config, redirectUri, state) => {
319
+ const params = new URLSearchParams({
320
+ client_id: config.clientId,
321
+ response_type: "code",
322
+ redirect_uri: redirectUri,
323
+ scope: config.scopes.join(" "),
324
+ state: state,
325
+ access_type: "offline",
326
+ prompt: "consent",
327
+ });
328
+ return `${config.authorizeUrl}?${params.toString()}`;
329
+ },
330
+ exchangeToken: async (config, code, redirectUri) => {
331
+ const response = await fetch(config.tokenUrl, {
332
+ method: "POST",
333
+ headers: {
334
+ "Content-Type": "application/x-www-form-urlencoded",
335
+ Accept: "application/json",
336
+ },
337
+ body: new URLSearchParams({
338
+ grant_type: "authorization_code",
339
+ client_id: config.clientId,
340
+ client_secret: config.clientSecret,
341
+ code: code,
342
+ redirect_uri: redirectUri,
343
+ }),
344
+ });
345
+
346
+ if (!response.ok) {
347
+ const error = await response.text();
348
+ throw new Error(`Token exchange failed: ${error}`);
349
+ }
350
+
351
+ return await response.json();
352
+ },
353
+ postExchange: async (tokens) => {
354
+ // Fetch user info
355
+ const userInfoRes = await fetch(`${GEMINI_CONFIG.userInfoUrl}?alt=json`, {
356
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
357
+ });
358
+ const userInfo = userInfoRes.ok ? await userInfoRes.json() : {};
359
+
360
+ // Fetch project ID
361
+ let projectId = "";
362
+ try {
363
+ const projectRes = await fetch(
364
+ "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
365
+ {
366
+ method: "POST",
367
+ headers: {
368
+ Authorization: `Bearer ${tokens.access_token}`,
369
+ "Content-Type": "application/json",
370
+ },
371
+ body: JSON.stringify({
372
+ metadata: getOAuthClientMetadata(),
373
+ mode: 1,
374
+ }),
375
+ }
376
+ );
377
+ if (projectRes.ok) {
378
+ const data = await projectRes.json();
379
+ projectId = data.cloudaicompanionProject?.id || data.cloudaicompanionProject || "";
380
+ }
381
+ } catch (e) {
382
+ console.log("Failed to fetch project ID:", e);
383
+ }
384
+
385
+ return { userInfo, projectId };
386
+ },
387
+ mapTokens: (tokens, extra) => ({
388
+ accessToken: tokens.access_token,
389
+ refreshToken: tokens.refresh_token,
390
+ expiresIn: tokens.expires_in,
391
+ scope: tokens.scope,
392
+ email: extra?.userInfo?.email,
393
+ projectId: extra?.projectId,
394
+ }),
395
+ },
396
+
397
+ antigravity: {
398
+ config: ANTIGRAVITY_CONFIG,
399
+ flowType: "authorization_code",
400
+ buildAuthUrl: (config, redirectUri, state) => {
401
+ const params = new URLSearchParams({
402
+ client_id: config.clientId,
403
+ response_type: "code",
404
+ redirect_uri: redirectUri,
405
+ scope: config.scopes.join(" "),
406
+ state: state,
407
+ access_type: "offline",
408
+ prompt: "consent",
409
+ });
410
+ return `${config.authorizeUrl}?${params.toString()}`;
411
+ },
412
+ exchangeToken: async (config, code, redirectUri) => {
413
+ const response = await fetch(config.tokenUrl, {
414
+ method: "POST",
415
+ headers: {
416
+ "Content-Type": "application/x-www-form-urlencoded",
417
+ Accept: "application/json",
418
+ },
419
+ body: new URLSearchParams({
420
+ grant_type: "authorization_code",
421
+ client_id: config.clientId,
422
+ client_secret: config.clientSecret,
423
+ code: code,
424
+ redirect_uri: redirectUri,
425
+ }),
426
+ });
427
+
428
+ if (!response.ok) {
429
+ const error = await response.text();
430
+ throw new Error(`Token exchange failed: ${error}`);
431
+ }
432
+
433
+ return await response.json();
434
+ },
435
+ postExchange: async (tokens) => {
436
+ // Numeric enums matching Antigravity binary ClientMetadata
437
+ const loadHeaders = {
438
+ "Authorization": `Bearer ${tokens.access_token}`,
439
+ "Content-Type": "application/json",
440
+ "User-Agent": ANTIGRAVITY_CONFIG.loadCodeAssistUserAgent,
441
+ "X-Goog-Api-Client": ANTIGRAVITY_CONFIG.loadCodeAssistApiClient,
442
+ "Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata,
443
+ "x-request-source": "local",
444
+ };
445
+ const metadata = getOAuthClientMetadata();
446
+
447
+ // Fetch user info
448
+ const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
449
+ headers: {
450
+ Authorization: `Bearer ${tokens.access_token}`,
451
+ "x-request-source": "local",
452
+ },
453
+ });
454
+ const userInfo = userInfoRes.ok ? await userInfoRes.json() : {};
455
+
456
+ // Load Code Assist to get project ID and tier
457
+ let projectId = "";
458
+ let tierId = "legacy-tier";
459
+ try {
460
+ const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, {
461
+ method: "POST",
462
+ headers: loadHeaders,
463
+ body: JSON.stringify({ metadata }),
464
+ });
465
+ if (loadRes.ok) {
466
+ const data = await loadRes.json();
467
+ projectId = data.cloudaicompanionProject?.id || data.cloudaicompanionProject || "";
468
+ if (Array.isArray(data.allowedTiers)) {
469
+ for (const tier of data.allowedTiers) {
470
+ if (tier.isDefault && tier.id) {
471
+ tierId = tier.id.trim();
472
+ break;
473
+ }
474
+ }
475
+ }
476
+ }
477
+ } catch (e) {
478
+ console.log("Failed to load code assist:", e);
479
+ }
480
+
481
+ // Fire-and-forget onboarding — does not block DB save
482
+ if (projectId) {
483
+ const doOnboard = async () => {
484
+ for (let i = 0; i < 10; i++) {
485
+ try {
486
+ const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, {
487
+ method: "POST",
488
+ headers: loadHeaders,
489
+ body: JSON.stringify({ tierId, metadata }),
490
+ });
491
+ if (onboardRes.ok) {
492
+ const result = await onboardRes.json();
493
+ if (result.done === true) break;
494
+ }
495
+ } catch (e) {
496
+ break;
497
+ }
498
+ await new Promise(resolve => setTimeout(resolve, 5000));
499
+ }
500
+ };
501
+ doOnboard().catch(() => {});
502
+ }
503
+
504
+ return { userInfo, projectId };
505
+ },
506
+ mapTokens: (tokens, extra) => ({
507
+ accessToken: tokens.access_token,
508
+ refreshToken: tokens.refresh_token,
509
+ expiresIn: tokens.expires_in,
510
+ scope: tokens.scope,
511
+ email: extra?.userInfo?.email,
512
+ projectId: extra?.projectId,
513
+ }),
514
+ },
515
+
516
+ iflow: {
517
+ config: IFLOW_CONFIG,
518
+ flowType: "authorization_code",
519
+ buildAuthUrl: (config, redirectUri, state) => {
520
+ const params = new URLSearchParams({
521
+ loginMethod: config.extraParams.loginMethod,
522
+ type: config.extraParams.type,
523
+ redirect: redirectUri,
524
+ state: state,
525
+ client_id: config.clientId,
526
+ });
527
+ return `${config.authorizeUrl}?${params.toString()}`;
528
+ },
529
+ exchangeToken: async (config, code, redirectUri) => {
530
+ // Create Basic Auth header
531
+ const basicAuth = Buffer.from(
532
+ `${config.clientId}:${config.clientSecret}`
533
+ ).toString("base64");
534
+
535
+ const response = await fetch(config.tokenUrl, {
536
+ method: "POST",
537
+ headers: {
538
+ "Content-Type": "application/x-www-form-urlencoded",
539
+ Accept: "application/json",
540
+ Authorization: `Basic ${basicAuth}`,
541
+ },
542
+ body: new URLSearchParams({
543
+ grant_type: "authorization_code",
544
+ code: code,
545
+ redirect_uri: redirectUri,
546
+ client_id: config.clientId,
547
+ client_secret: config.clientSecret,
548
+ }),
549
+ });
550
+
551
+ if (!response.ok) {
552
+ const error = await response.text();
553
+ throw new Error(`Token exchange failed: ${error}`);
554
+ }
555
+
556
+ return await response.json();
557
+ },
558
+ postExchange: async (tokens) => {
559
+ // Fetch user info (MUST succeed to get API key)
560
+ const userInfoRes = await fetch(
561
+ `${IFLOW_CONFIG.userInfoUrl}?accessToken=${encodeURIComponent(tokens.access_token)}`,
562
+ {
563
+ headers: {
564
+ Accept: "application/json",
565
+ },
566
+ }
567
+ );
568
+
569
+ if (!userInfoRes.ok) {
570
+ const errorText = await userInfoRes.text();
571
+ throw new Error(`Failed to fetch user info: ${errorText}`);
572
+ }
573
+
574
+ const result = await userInfoRes.json();
575
+ if (!result.success) {
576
+ throw new Error(`User info request failed: ${result.message || 'Unknown error'}`);
577
+ }
578
+
579
+ const userInfo = result.data || {};
580
+
581
+ // Validate API key (critical for iFlow)
582
+ if (!userInfo.apiKey || userInfo.apiKey.trim() === "") {
583
+ throw new Error("Empty API key returned from iFlow");
584
+ }
585
+
586
+ // Validate email/phone
587
+ const email = userInfo.email?.trim() || userInfo.phone?.trim();
588
+ if (!email) {
589
+ throw new Error("Missing account email/phone in user info");
590
+ }
591
+
592
+ return { userInfo };
593
+ },
594
+ mapTokens: (tokens, extra) => ({
595
+ accessToken: tokens.access_token,
596
+ refreshToken: tokens.refresh_token,
597
+ expiresIn: tokens.expires_in,
598
+ apiKey: extra?.userInfo?.apiKey,
599
+ email: extra?.userInfo?.email || extra?.userInfo?.phone,
600
+ displayName: extra?.userInfo?.nickname || extra?.userInfo?.name,
601
+ }),
602
+ },
603
+
604
+ qoder: {
605
+ config: QODER_CONFIG,
606
+ flowType: "device_code",
607
+ // Qoder uses a custom device flow: PKCE + nonce + machine_id are generated
608
+ // locally, the user lands on qoder.com/device/selectAccounts in the
609
+ // browser, and we poll openapi.qoder.sh until a `dt-...` token appears.
610
+ requestDeviceCode: async (config) => {
611
+ const { QoderService } = await import("@/lib/oauth/services/qoder");
612
+ const flow = new QoderService().initiateDeviceFlow();
613
+ // Match the device_code shape the rest of the OAuthModal expects
614
+ // (device_code, user_code, verification_uri[_complete], interval).
615
+ // The poll endpoint identifies us by nonce+verifier, not by a
616
+ // server-issued device_code, so we plumb our own values through:
617
+ // device_code = nonce (modal forwards as deviceCode on poll)
618
+ // codeVerifier = our PKCE verifier (route forwards as codeVerifier)
619
+ return {
620
+ device_code: flow.nonce,
621
+ user_code: flow.nonce.slice(0, 8).toUpperCase(),
622
+ verification_uri: config.loginUrl,
623
+ verification_uri_complete: flow.verificationUriComplete,
624
+ expires_in: 300,
625
+ interval: 2,
626
+ codeVerifier: flow.codeVerifier,
627
+ _qoderNonce: flow.nonce,
628
+ _qoderMachineId: flow.machineId,
629
+ };
630
+ },
631
+ pollToken: async (config, deviceCode, codeVerifier, extraData) => {
632
+ const { QoderService } = await import("@/lib/oauth/services/qoder");
633
+ const svc = new QoderService();
634
+ const nonce = deviceCode || extraData?._qoderNonce;
635
+ const verifier = codeVerifier || extraData?._qoderVerifier;
636
+ if (!nonce || !verifier) {
637
+ return {
638
+ ok: false,
639
+ data: { error: "invalid_request", error_description: "Missing nonce/verifier" },
640
+ };
641
+ }
642
+ let result;
643
+ try {
644
+ result = await svc.pollDeviceToken({ nonce, codeVerifier: verifier });
645
+ } catch (err) {
646
+ return {
647
+ ok: false,
648
+ data: { error: "poll_failed", error_description: err.message },
649
+ };
650
+ }
651
+ if (result.status === "pending") {
652
+ return { ok: false, data: { error: "authorization_pending" } };
653
+ }
654
+ // Best-effort profile lookup so we have a name/email to display.
655
+ const userInfo = await svc.fetchUserInfo(result.accessToken);
656
+ // expireTime is a Unix-ms timestamp from QoderService.parseExpiry,
657
+ // which already falls back to "now + 30 days" when the upstream
658
+ // omits expiry. Floor to a sane minimum (1 day) so a stale or
659
+ // skewed upstream timestamp doesn't truncate the stored token below
660
+ // something useful.
661
+ const minSeconds = 24 * 60 * 60;
662
+ const remainingSeconds = Math.floor((result.expireTime - Date.now()) / 1000);
663
+ const expiresIn = Math.max(minSeconds, remainingSeconds);
664
+ return {
665
+ ok: true,
666
+ data: {
667
+ access_token: result.accessToken,
668
+ refresh_token: result.refreshToken,
669
+ expires_in: expiresIn,
670
+ _qoderUserId: result.userId,
671
+ _qoderMachineId: extraData?._qoderMachineId || "",
672
+ _qoderName: userInfo.name,
673
+ _qoderEmail: userInfo.email,
674
+ _qoderOrganizationId: userInfo.organizationId,
675
+ },
676
+ };
677
+ },
678
+ mapTokens: (tokens) => {
679
+ const rawEmail = (tokens._qoderEmail || "").trim();
680
+ const displayName = (tokens._qoderName || "").trim() || null;
681
+ const userId = tokens._qoderUserId || "";
682
+ // Dedup in createProviderConnection requires a non-empty email. When
683
+ // fetchUserInfo silently fails (returns ""), fall back to a stable
684
+ // synthetic identifier derived from userId so re-logins update the
685
+ // existing row instead of accumulating "Account N" duplicates.
686
+ const email = rawEmail || (userId ? `qoder-user-${userId}` : null);
687
+ return {
688
+ accessToken: tokens.access_token,
689
+ refreshToken: tokens.refresh_token || null,
690
+ expiresIn: tokens.expires_in,
691
+ email,
692
+ displayName,
693
+ providerSpecificData: {
694
+ authMethod: "device",
695
+ userId,
696
+ machineId: tokens._qoderMachineId || "",
697
+ organizationId: tokens._qoderOrganizationId || "",
698
+ },
699
+ };
700
+ },
701
+ },
702
+
703
+ qwen: {
704
+ config: QWEN_CONFIG,
705
+ flowType: "device_code",
706
+ requestDeviceCode: async (config, codeChallenge) => {
707
+ const response = await fetch(config.deviceCodeUrl, {
708
+ method: "POST",
709
+ headers: {
710
+ "Content-Type": "application/x-www-form-urlencoded",
711
+ Accept: "application/json",
712
+ },
713
+ body: new URLSearchParams({
714
+ client_id: config.clientId,
715
+ scope: config.scope,
716
+ code_challenge: codeChallenge,
717
+ code_challenge_method: config.codeChallengeMethod,
718
+ }),
719
+ });
720
+
721
+ if (!response.ok) {
722
+ const error = await response.text();
723
+ throw new Error(`Device code request failed: ${error}`);
724
+ }
725
+
726
+ return await response.json();
727
+ },
728
+ pollToken: async (config, deviceCode, codeVerifier) => {
729
+ const response = await fetch(config.tokenUrl, {
730
+ method: "POST",
731
+ headers: {
732
+ "Content-Type": "application/x-www-form-urlencoded",
733
+ Accept: "application/json",
734
+ },
735
+ body: new URLSearchParams({
736
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
737
+ client_id: config.clientId,
738
+ device_code: deviceCode,
739
+ code_verifier: codeVerifier,
740
+ }),
741
+ });
742
+
743
+ return {
744
+ ok: response.ok,
745
+ data: await response.json(),
746
+ };
747
+ },
748
+ mapTokens: (tokens) => ({
749
+ accessToken: tokens.access_token,
750
+ refreshToken: tokens.refresh_token,
751
+ expiresIn: tokens.expires_in,
752
+ providerSpecificData: { resourceUrl: tokens.resource_url },
753
+ }),
754
+ },
755
+
756
+ github: {
757
+ config: GITHUB_CONFIG,
758
+ flowType: "device_code",
759
+ requestDeviceCode: async (config) => {
760
+ const response = await fetch(config.deviceCodeUrl, {
761
+ method: "POST",
762
+ headers: {
763
+ "Content-Type": "application/x-www-form-urlencoded",
764
+ Accept: "application/json",
765
+ },
766
+ body: new URLSearchParams({
767
+ client_id: config.clientId,
768
+ scope: config.scopes,
769
+ }),
770
+ });
771
+
772
+ if (!response.ok) {
773
+ const error = await response.text();
774
+ throw new Error(`Device code request failed: ${error}`);
775
+ }
776
+
777
+ return await response.json();
778
+ },
779
+ pollToken: async (config, deviceCode) => {
780
+ const response = await fetch(config.tokenUrl, {
781
+ method: "POST",
782
+ headers: {
783
+ "Content-Type": "application/x-www-form-urlencoded",
784
+ Accept: "application/json",
785
+ },
786
+ body: new URLSearchParams({
787
+ client_id: config.clientId,
788
+ device_code: deviceCode,
789
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
790
+ }),
791
+ });
792
+
793
+ // Handle response properly - if not ok, try to get error as text first
794
+ let data;
795
+ try {
796
+ data = await response.json();
797
+ } catch (e) {
798
+ // If response is not JSON, get as text
799
+ const text = await response.text();
800
+ data = { error: "invalid_response", error_description: text };
801
+ }
802
+
803
+ return {
804
+ ok: response.ok,
805
+ data: data,
806
+ };
807
+ },
808
+ postExchange: async (tokens) => {
809
+ // Get Copilot token using GitHub access token
810
+ const copilotRes = await fetch(GITHUB_CONFIG.copilotTokenUrl, {
811
+ headers: {
812
+ Authorization: `Bearer ${tokens.access_token}`,
813
+ Accept: "application/json",
814
+ "X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
815
+ "User-Agent": GITHUB_CONFIG.userAgent,
816
+ },
817
+ });
818
+ const copilotToken = copilotRes.ok ? await copilotRes.json() : {};
819
+
820
+ // Get user info from GitHub
821
+ const userRes = await fetch(GITHUB_CONFIG.userInfoUrl, {
822
+ headers: {
823
+ Authorization: `Bearer ${tokens.access_token}`,
824
+ Accept: "application/json",
825
+ "X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
826
+ "User-Agent": GITHUB_CONFIG.userAgent,
827
+ },
828
+ });
829
+ const userInfo = userRes.ok ? await userRes.json() : {};
830
+
831
+ return { copilotToken, userInfo };
832
+ },
833
+ mapTokens: (tokens, extra) => ({
834
+ accessToken: tokens.access_token,
835
+ refreshToken: tokens.refresh_token,
836
+ expiresIn: tokens.expires_in,
837
+ providerSpecificData: {
838
+ copilotToken: extra?.copilotToken?.token,
839
+ copilotTokenExpiresAt: extra?.copilotToken?.expires_at,
840
+ githubUserId: extra?.userInfo?.id,
841
+ githubLogin: extra?.userInfo?.login,
842
+ githubName: extra?.userInfo?.name,
843
+ githubEmail: extra?.userInfo?.email,
844
+ },
845
+ }),
846
+ },
847
+
848
+ kiro: {
849
+ config: KIRO_CONFIG,
850
+ flowType: "device_code",
851
+ // Kiro uses AWS SSO OIDC - requires client registration first
852
+ requestDeviceCode: async (config, codeChallenge, options = {}) => {
853
+ const trimmedRegion = typeof options.region === "string" ? options.region.trim() : "";
854
+ const region = trimmedRegion || "us-east-1";
855
+ const trimmedStartUrl = typeof options.startUrl === "string" ? options.startUrl.trim() : "";
856
+ const startUrl = trimmedStartUrl || config.startUrl;
857
+ const authMethod = options.authMethod === "idc" ? "idc" : "builder-id";
858
+ const registerClientUrl = `https://oidc.${region}.amazonaws.com/client/register`;
859
+ const deviceAuthUrl = `https://oidc.${region}.amazonaws.com/device_authorization`;
860
+
861
+ // Step 1: Register client with AWS SSO OIDC
862
+ const registerRes = await fetch(registerClientUrl, {
863
+ method: "POST",
864
+ headers: {
865
+ "Content-Type": "application/json",
866
+ Accept: "application/json",
867
+ },
868
+ body: JSON.stringify({
869
+ clientName: config.clientName,
870
+ clientType: config.clientType,
871
+ scopes: config.scopes,
872
+ grantTypes: config.grantTypes,
873
+ issuerUrl: config.issuerUrl,
874
+ }),
875
+ });
876
+
877
+ if (!registerRes.ok) {
878
+ const error = await registerRes.text();
879
+ throw new Error(`Client registration failed: ${error}`);
880
+ }
881
+
882
+ const clientInfo = await registerRes.json();
883
+
884
+ // Step 2: Request device authorization
885
+ const deviceRes = await fetch(deviceAuthUrl, {
886
+ method: "POST",
887
+ headers: {
888
+ "Content-Type": "application/json",
889
+ Accept: "application/json",
890
+ },
891
+ body: JSON.stringify({
892
+ clientId: clientInfo.clientId,
893
+ clientSecret: clientInfo.clientSecret,
894
+ startUrl,
895
+ }),
896
+ });
897
+
898
+ if (!deviceRes.ok) {
899
+ const error = await deviceRes.text();
900
+ throw new Error(`Device authorization failed: ${error}`);
901
+ }
902
+
903
+ const deviceData = await deviceRes.json();
904
+
905
+ // Return combined data for polling
906
+ return {
907
+ device_code: deviceData.deviceCode,
908
+ user_code: deviceData.userCode,
909
+ verification_uri: deviceData.verificationUri,
910
+ verification_uri_complete: deviceData.verificationUriComplete,
911
+ expires_in: deviceData.expiresIn,
912
+ interval: deviceData.interval || 5,
913
+ // Store client credentials for token exchange
914
+ _clientId: clientInfo.clientId,
915
+ _clientSecret: clientInfo.clientSecret,
916
+ _region: region,
917
+ _authMethod: authMethod,
918
+ _startUrl: startUrl,
919
+ };
920
+ },
921
+ pollToken: async (config, deviceCode, codeVerifier, extraData) => {
922
+ const region = extraData?._region || "us-east-1";
923
+ const tokenUrl = `https://oidc.${region}.amazonaws.com/token`;
924
+ const response = await fetch(tokenUrl, {
925
+ method: "POST",
926
+ headers: {
927
+ "Content-Type": "application/json",
928
+ Accept: "application/json",
929
+ },
930
+ body: JSON.stringify({
931
+ clientId: extraData?._clientId,
932
+ clientSecret: extraData?._clientSecret,
933
+ deviceCode: deviceCode,
934
+ grantType: "urn:ietf:params:oauth:grant-type:device_code",
935
+ }),
936
+ });
937
+
938
+ let data;
939
+ try {
940
+ data = await response.json();
941
+ } catch (e) {
942
+ const text = await response.text();
943
+ data = { error: "invalid_response", error_description: text };
944
+ }
945
+
946
+ // AWS SSO OIDC returns camelCase
947
+ if (data.accessToken) {
948
+ return {
949
+ ok: true,
950
+ data: {
951
+ access_token: data.accessToken,
952
+ refresh_token: data.refreshToken,
953
+ expires_in: data.expiresIn,
954
+ profile_arn: data?.profileArn || null,
955
+ // Store client credentials for refresh
956
+ _clientId: extraData?._clientId,
957
+ _clientSecret: extraData?._clientSecret,
958
+ _region: extraData?._region,
959
+ _authMethod: extraData?._authMethod,
960
+ _startUrl: extraData?._startUrl,
961
+ },
962
+ };
963
+ }
964
+
965
+ return {
966
+ ok: false,
967
+ data: {
968
+ error: data.error || "authorization_pending",
969
+ error_description: data.error_description || data.message,
970
+ },
971
+ };
972
+ },
973
+ mapTokens: (tokens) => {
974
+ const email = extractEmailFromAccessToken(tokens.access_token);
975
+ const mapped = {
976
+ accessToken: tokens.access_token,
977
+ refreshToken: tokens.refresh_token,
978
+ expiresIn: tokens.expires_in,
979
+ email,
980
+ providerSpecificData: {
981
+ profileArn: tokens?.profile_arn || null,
982
+ clientId: tokens._clientId,
983
+ clientSecret: tokens._clientSecret,
984
+ region: tokens._region || "us-east-1",
985
+ authMethod: tokens._authMethod || "builder-id",
986
+ startUrl: tokens._startUrl || KIRO_CONFIG.startUrl,
987
+ },
988
+ };
989
+ return mapped;
990
+ },
991
+ },
992
+
993
+ cursor: {
994
+ config: CURSOR_CONFIG,
995
+ flowType: "import_token",
996
+ // Cursor uses import token flow - tokens are extracted from local SQLite database
997
+ // No OAuth flow needed, handled by /api/oauth/cursor/import route
998
+ mapTokens: (tokens) => ({
999
+ accessToken: tokens.accessToken,
1000
+ refreshToken: null, // Cursor doesn't have public refresh endpoint
1001
+ expiresIn: tokens.expiresIn || 86400,
1002
+ providerSpecificData: {
1003
+ machineId: tokens.machineId,
1004
+ authMethod: "imported",
1005
+ },
1006
+ }),
1007
+ },
1008
+
1009
+ "kimi-coding": {
1010
+ config: KIMI_CODING_CONFIG,
1011
+ flowType: "device_code",
1012
+ requestDeviceCode: async (config) => {
1013
+ const response = await fetch(config.deviceCodeUrl, {
1014
+ method: "POST",
1015
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
1016
+ body: new URLSearchParams({ client_id: config.clientId }),
1017
+ });
1018
+ if (!response.ok) {
1019
+ const error = await response.text();
1020
+ throw new Error(`Device code request failed: ${error}`);
1021
+ }
1022
+ const data = await response.json();
1023
+ return {
1024
+ device_code: data.device_code,
1025
+ user_code: data.user_code,
1026
+ verification_uri: data.verification_uri || "https://www.kimi.com/code/authorize_device",
1027
+ verification_uri_complete:
1028
+ data.verification_uri_complete ||
1029
+ `https://www.kimi.com/code/authorize_device?user_code=${data.user_code}`,
1030
+ expires_in: data.expires_in,
1031
+ interval: data.interval || 5,
1032
+ };
1033
+ },
1034
+ pollToken: async (config, deviceCode) => {
1035
+ const response = await fetch(config.tokenUrl, {
1036
+ method: "POST",
1037
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
1038
+ body: new URLSearchParams({
1039
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1040
+ client_id: config.clientId,
1041
+ device_code: deviceCode,
1042
+ }),
1043
+ });
1044
+ let data;
1045
+ try {
1046
+ data = await response.json();
1047
+ } catch (e) {
1048
+ const text = await response.text();
1049
+ data = { error: "invalid_response", error_description: text };
1050
+ }
1051
+ return { ok: response.ok, data };
1052
+ },
1053
+ mapTokens: (tokens) => ({
1054
+ accessToken: tokens.access_token,
1055
+ refreshToken: tokens.refresh_token,
1056
+ expiresIn: tokens.expires_in,
1057
+ }),
1058
+ },
1059
+
1060
+ kilocode: {
1061
+ config: KILOCODE_CONFIG,
1062
+ flowType: "device_code",
1063
+ requestDeviceCode: async (config) => {
1064
+ const response = await fetch(config.initiateUrl, {
1065
+ method: "POST",
1066
+ headers: { "Content-Type": "application/json" },
1067
+ });
1068
+ if (!response.ok) {
1069
+ if (response.status === 429) {
1070
+ throw new Error("Too many pending authorization requests. Please try again later.");
1071
+ }
1072
+ const error = await response.text();
1073
+ throw new Error(`Device auth initiation failed: ${error}`);
1074
+ }
1075
+ const data = await response.json();
1076
+ return {
1077
+ device_code: data.code,
1078
+ user_code: data.code,
1079
+ verification_uri: data.verificationUrl,
1080
+ verification_uri_complete: data.verificationUrl,
1081
+ expires_in: data.expiresIn || 300,
1082
+ interval: 3,
1083
+ };
1084
+ },
1085
+ pollToken: async (config, deviceCode) => {
1086
+ const response = await fetch(`${config.pollUrlBase}/${deviceCode}`);
1087
+ if (response.status === 202) return { ok: false, data: { error: "authorization_pending" } };
1088
+ if (response.status === 403) return { ok: false, data: { error: "access_denied", error_description: "Authorization denied by user" } };
1089
+ if (response.status === 410) return { ok: false, data: { error: "expired_token", error_description: "Authorization code expired" } };
1090
+ if (!response.ok) return { ok: false, data: { error: "poll_failed", error_description: `Poll failed: ${response.status}` } };
1091
+ const data = await response.json();
1092
+ if (data.status === "approved" && data.token) {
1093
+ // Fetch profile to get orgId for X-Kilocode-OrganizationID header
1094
+ let orgId = null;
1095
+ try {
1096
+ const profileRes = await fetch(`${config.apiBaseUrl}/api/profile`, {
1097
+ headers: { "Authorization": `Bearer ${data.token}` }
1098
+ });
1099
+ if (profileRes.ok) {
1100
+ const profile = await profileRes.json();
1101
+ orgId = profile.organizations?.[0]?.id || null;
1102
+ }
1103
+ } catch {}
1104
+ return { ok: true, data: { access_token: data.token, _userEmail: data.userEmail, _orgId: orgId } };
1105
+ }
1106
+ return { ok: false, data: { error: "authorization_pending" } };
1107
+ },
1108
+ mapTokens: (tokens) => ({
1109
+ accessToken: tokens.access_token,
1110
+ refreshToken: null,
1111
+ expiresIn: null,
1112
+ email: tokens._userEmail,
1113
+ ...(tokens._orgId ? { providerSpecificData: { orgId: tokens._orgId } } : {}),
1114
+ }),
1115
+ },
1116
+
1117
+ cline: {
1118
+ config: CLINE_CONFIG,
1119
+ flowType: "authorization_code",
1120
+ buildAuthUrl: (config, redirectUri) => {
1121
+ const params = new URLSearchParams({
1122
+ client_type: "extension",
1123
+ callback_url: redirectUri,
1124
+ redirect_uri: redirectUri,
1125
+ });
1126
+ return `${config.authorizeUrl}?${params.toString()}`;
1127
+ },
1128
+ exchangeToken: async (config, code, redirectUri) => {
1129
+ try {
1130
+ // Cline encodes token data as base64 in the code param
1131
+ let base64 = code;
1132
+ const padding = 4 - (base64.length % 4);
1133
+ if (padding !== 4) base64 += "=".repeat(padding);
1134
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1135
+ const lastBrace = decoded.lastIndexOf("}");
1136
+ if (lastBrace === -1) throw new Error("No JSON found in decoded code");
1137
+ const tokenData = JSON.parse(decoded.substring(0, lastBrace + 1));
1138
+ return {
1139
+ access_token: tokenData.accessToken,
1140
+ refresh_token: tokenData.refreshToken,
1141
+ email: tokenData.email,
1142
+ firstName: tokenData.firstName,
1143
+ lastName: tokenData.lastName,
1144
+ expires_at: tokenData.expiresAt,
1145
+ };
1146
+ } catch (e) {
1147
+ const response = await fetch(config.tokenExchangeUrl, {
1148
+ method: "POST",
1149
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
1150
+ body: JSON.stringify({ grant_type: "authorization_code", code, client_type: "extension", redirect_uri: redirectUri }),
1151
+ });
1152
+ if (!response.ok) {
1153
+ const error = await response.text();
1154
+ throw new Error(`Cline token exchange failed: ${error}`);
1155
+ }
1156
+ const data = await response.json();
1157
+ return {
1158
+ access_token: data.data?.accessToken || data.accessToken,
1159
+ refresh_token: data.data?.refreshToken || data.refreshToken,
1160
+ email: data.data?.userInfo?.email || "",
1161
+ expires_at: data.data?.expiresAt || data.expiresAt,
1162
+ };
1163
+ }
1164
+ },
1165
+ mapTokens: (tokens) => ({
1166
+ accessToken: tokens.access_token,
1167
+ refreshToken: tokens.refresh_token,
1168
+ expiresIn: tokens.expires_at
1169
+ ? Math.floor((new Date(tokens.expires_at).getTime() - Date.now()) / 1000)
1170
+ : 3600,
1171
+ email: tokens.email,
1172
+ providerSpecificData: { firstName: tokens.firstName, lastName: tokens.lastName },
1173
+ }),
1174
+ },
1175
+ // GitLab Duo - Authorization Code Flow with PKCE
1176
+ // Supports two login modes via loginMode metadata: "oauth" (default) or "pat"
1177
+ gitlab: {
1178
+ config: GITLAB_CONFIG,
1179
+ flowType: "authorization_code_pkce",
1180
+ buildAuthUrl: (config, redirectUri, state, codeChallenge, meta = {}) => {
1181
+ const baseUrl = meta.baseUrl || config.defaultBaseUrl;
1182
+ const clientId = meta.clientId || "";
1183
+ const params = new URLSearchParams({
1184
+ client_id: clientId,
1185
+ redirect_uri: redirectUri,
1186
+ response_type: "code",
1187
+ state,
1188
+ scope: config.scope,
1189
+ code_challenge: codeChallenge,
1190
+ code_challenge_method: config.codeChallengeMethod,
1191
+ });
1192
+ return `${baseUrl}${config.authorizeUrlPath}?${params.toString()}`;
1193
+ },
1194
+ exchangeToken: async (config, code, redirectUri, codeVerifier, state, meta = {}) => {
1195
+ const baseUrl = meta.baseUrl || config.defaultBaseUrl;
1196
+ const clientId = meta.clientId || "";
1197
+ const clientSecret = meta.clientSecret || "";
1198
+ const body = new URLSearchParams({
1199
+ client_id: clientId,
1200
+ grant_type: "authorization_code",
1201
+ code,
1202
+ redirect_uri: redirectUri,
1203
+ code_verifier: codeVerifier,
1204
+ });
1205
+ if (clientSecret) body.set("client_secret", clientSecret);
1206
+ const response = await fetch(`${baseUrl}${config.tokenUrlPath}`, {
1207
+ method: "POST",
1208
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
1209
+ body: body.toString(),
1210
+ });
1211
+ if (!response.ok) throw new Error(`GitLab token exchange failed: ${await response.text()}`);
1212
+ const tokens = await response.json();
1213
+ // Fetch user info
1214
+ const userRes = await fetch(`${baseUrl}${config.userInfoUrlPath}`, {
1215
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
1216
+ });
1217
+ const user = userRes.ok ? await userRes.json() : {};
1218
+ return { ...tokens, _user: user, _baseUrl: baseUrl, _clientId: clientId };
1219
+ },
1220
+ mapTokens: (tokens) => ({
1221
+ accessToken: tokens.access_token,
1222
+ refreshToken: tokens.refresh_token,
1223
+ expiresIn: tokens.expires_in,
1224
+ scope: tokens.scope,
1225
+ providerSpecificData: {
1226
+ username: tokens._user?.username || "",
1227
+ email: tokens._user?.email || tokens._user?.public_email || "",
1228
+ name: tokens._user?.name || "",
1229
+ baseUrl: tokens._baseUrl,
1230
+ clientId: tokens._clientId,
1231
+ authKind: "oauth",
1232
+ },
1233
+ }),
1234
+ },
1235
+
1236
+ // CodeBuddy (Tencent) - Browser OAuth Polling Flow
1237
+ // 1. POST stateUrl → get { state, authUrl }
1238
+ // 2. Open authUrl in browser
1239
+ // 3. Poll tokenUrl with state until success (code 0) or timeout
1240
+ codebuddy: {
1241
+ config: CODEBUDDY_CONFIG,
1242
+ flowType: "device_code",
1243
+ requestDeviceCode: async (config) => {
1244
+ const response = await fetch(`${config.stateUrl}?platform=${config.platform}`, {
1245
+ method: "POST",
1246
+ headers: {
1247
+ "Content-Type": "application/json",
1248
+ Accept: "application/json",
1249
+ "User-Agent": config.userAgent,
1250
+ "X-Requested-With": "XMLHttpRequest",
1251
+ "X-Domain": config.domain,
1252
+ "X-No-Authorization": "true",
1253
+ "X-No-User-Id": "true",
1254
+ "X-No-Enterprise-Id": "true",
1255
+ "X-No-Department-Info": "true",
1256
+ "X-Product": "SaaS",
1257
+ },
1258
+ body: "{}",
1259
+ });
1260
+ if (!response.ok) throw new Error(`CodeBuddy state request failed: ${await response.text()}`);
1261
+ const data = await response.json();
1262
+ if (data.code !== 0 || !data.data?.state || !data.data?.authUrl) {
1263
+ throw new Error(`CodeBuddy state error: ${data.msg || "missing state/authUrl"}`);
1264
+ }
1265
+ return {
1266
+ device_code: data.data.state,
1267
+ verification_uri: data.data.authUrl,
1268
+ user_code: "",
1269
+ interval: config.pollInterval / 1000,
1270
+ _isCodeBuddy: true,
1271
+ };
1272
+ },
1273
+ pollToken: async (config, deviceCode) => {
1274
+ const tokenUrl = new URL(config.tokenUrl);
1275
+ tokenUrl.searchParams.set("state", deviceCode);
1276
+ const response = await fetch(tokenUrl.toString(), {
1277
+ method: "GET",
1278
+ headers: {
1279
+ Accept: "application/json",
1280
+ "User-Agent": config.userAgent,
1281
+ "X-Requested-With": "XMLHttpRequest",
1282
+ "X-Domain": config.domain,
1283
+ "X-No-Authorization": "true",
1284
+ "X-No-User-Id": "true",
1285
+ "X-No-Enterprise-Id": "true",
1286
+ "X-No-Department-Info": "true",
1287
+ "X-Product": "SaaS",
1288
+ },
1289
+ });
1290
+ if (!response.ok) {
1291
+ const errorText = await response.text().catch(() => "");
1292
+ return {
1293
+ ok: false,
1294
+ data: {
1295
+ error: "request_failed",
1296
+ error_description: errorText || `${response.status} ${response.statusText}`,
1297
+ },
1298
+ };
1299
+ }
1300
+ const data = await response.json();
1301
+ // code 11217 = pending, code 0 = success
1302
+ if (data.code === 0 && data.data?.accessToken) {
1303
+ return {
1304
+ ok: true,
1305
+ data: {
1306
+ access_token: data.data.accessToken,
1307
+ refresh_token: data.data.refreshToken || "",
1308
+ token_type: data.data.tokenType || "Bearer",
1309
+ expires_in: data.data.expiresIn,
1310
+ expires_at: data.data.expiresAt,
1311
+ refresh_expires_in: data.data.refreshExpiresIn,
1312
+ refresh_expires_at: data.data.refreshExpiresAt,
1313
+ domain: data.data.domain || config.domain,
1314
+ raw: data.data,
1315
+ },
1316
+ };
1317
+ }
1318
+ if (data.code === 11217) return { ok: true, data: { error: "authorization_pending" } };
1319
+ return { ok: false, data: { error: data.msg || "unknown_error" } };
1320
+ },
1321
+ mapTokens: (tokens) => ({
1322
+ accessToken: tokens.access_token,
1323
+ refreshToken: tokens.refresh_token,
1324
+ expiresIn: tokens.expires_in || (
1325
+ tokens.expires_at
1326
+ ? Math.max(1, Math.floor((new Date(tokens.expires_at).getTime() - Date.now()) / 1000))
1327
+ : 86400
1328
+ ),
1329
+ providerSpecificData: {
1330
+ domain: tokens.domain || CODEBUDDY_CONFIG.domain,
1331
+ refreshExpiresIn: tokens.refresh_expires_in,
1332
+ refreshExpiresAt: tokens.refresh_expires_at,
1333
+ tokenType: tokens.token_type || "Bearer",
1334
+ rawAuth: tokens.raw,
1335
+ },
1336
+ }),
1337
+ },
1338
+ };
1339
+
1340
+ /**
1341
+ * Get provider handler
1342
+ */
1343
+ export function getProvider(name) {
1344
+ const provider = PROVIDERS[name];
1345
+ if (!provider) {
1346
+ throw new Error(`Unknown provider: ${name}`);
1347
+ }
1348
+ return provider;
1349
+ }
1350
+
1351
+ /**
1352
+ * Get all provider names
1353
+ */
1354
+ export function getProviderNames() {
1355
+ return Object.keys(PROVIDERS);
1356
+ }
1357
+
1358
+ /**
1359
+ * Generate auth data for a provider
1360
+ * @param {object} [meta] - Provider-specific metadata (e.g. gitlab clientId/baseUrl)
1361
+ */
1362
+ export async function generateAuthData(providerName, redirectUri, meta) {
1363
+ const provider = getProvider(providerName);
1364
+ const config = provider.prepareConfig
1365
+ ? await provider.prepareConfig(provider.config, meta || {})
1366
+ : provider.config;
1367
+ const { codeVerifier, codeChallenge, state } = generatePKCE(provider.pkceVerifierBytes);
1368
+
1369
+ let authUrl;
1370
+ if (provider.flowType === "device_code") {
1371
+ // Device code flow doesn't have auth URL upfront
1372
+ authUrl = null;
1373
+ } else if (provider.flowType === "authorization_code_pkce") {
1374
+ authUrl = provider.buildAuthUrl(config, redirectUri, state, codeChallenge, meta || {});
1375
+ } else {
1376
+ authUrl = provider.buildAuthUrl(config, redirectUri, state, undefined, meta || {});
1377
+ }
1378
+
1379
+ return {
1380
+ authUrl,
1381
+ state,
1382
+ codeVerifier,
1383
+ codeChallenge,
1384
+ redirectUri,
1385
+ flowType: provider.flowType,
1386
+ fixedPort: provider.fixedPort,
1387
+ callbackPath: provider.callbackPath || "/callback",
1388
+ };
1389
+ }
1390
+
1391
+ /**
1392
+ * Exchange code for tokens
1393
+ * @param {object} [meta] - Provider-specific metadata (e.g. gitlab clientId/baseUrl)
1394
+ */
1395
+ export async function exchangeTokens(providerName, code, redirectUri, codeVerifier, state, meta) {
1396
+ const provider = getProvider(providerName);
1397
+ const config = provider.prepareConfig
1398
+ ? await provider.prepareConfig(provider.config, meta || {})
1399
+ : provider.config;
1400
+
1401
+ const tokens = await provider.exchangeToken(config, code, redirectUri, codeVerifier, state, meta || {});
1402
+
1403
+ let extra = null;
1404
+ if (provider.postExchange) {
1405
+ extra = await provider.postExchange(tokens);
1406
+ }
1407
+
1408
+ return provider.mapTokens(tokens, extra);
1409
+ }
1410
+
1411
+ /**
1412
+ * Request device code (for device_code flow)
1413
+ */
1414
+ export async function requestDeviceCode(providerName, codeChallenge, options) {
1415
+ const provider = getProvider(providerName);
1416
+ if (provider.flowType !== "device_code") {
1417
+ throw new Error(`Provider ${providerName} does not support device code flow`);
1418
+ }
1419
+ return await provider.requestDeviceCode(provider.config, codeChallenge, options || {});
1420
+ }
1421
+
1422
+ /**
1423
+ * Poll for token (for device_code flow)
1424
+ * @param {string} providerName - Provider name
1425
+ * @param {string} deviceCode - Device code from requestDeviceCode
1426
+ * @param {string} codeVerifier - PKCE code verifier (optional for some providers)
1427
+ * @param {object} extraData - Extra data from device code response (e.g. clientId/clientSecret for Kiro)
1428
+ */
1429
+ export async function pollForToken(providerName, deviceCode, codeVerifier, extraData) {
1430
+ const provider = getProvider(providerName);
1431
+ if (provider.flowType !== "device_code") {
1432
+ throw new Error(`Provider ${providerName} does not support device code flow`);
1433
+ }
1434
+
1435
+ const result = await provider.pollToken(provider.config, deviceCode, codeVerifier, extraData);
1436
+
1437
+ if (result.ok) {
1438
+ // For device code flows, success is only when we have an access token
1439
+ if (result.data.access_token) {
1440
+ // Call postExchange to get additional data (copilotToken, userInfo, etc.)
1441
+ let extra = null;
1442
+ if (provider.postExchange) {
1443
+ extra = await provider.postExchange(result.data);
1444
+ }
1445
+ return { success: true, tokens: provider.mapTokens(result.data, extra) };
1446
+ } else {
1447
+ // Check if it's still pending authorization
1448
+ if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
1449
+ // This is not a failure, just still waiting
1450
+ return {
1451
+ success: false,
1452
+ error: result.data.error,
1453
+ errorDescription: result.data.error_description || result.data.message,
1454
+ pending: result.data.error === 'authorization_pending'
1455
+ };
1456
+ } else {
1457
+ // Actual error
1458
+ return {
1459
+ success: false,
1460
+ error: result.data.error || 'no_access_token',
1461
+ errorDescription: result.data.error_description || result.data.message || 'No access token received'
1462
+ };
1463
+ }
1464
+ }
1465
+ }
1466
+
1467
+ return { success: false, error: result.data.error, errorDescription: result.data.error_description };
1468
+ }
1469
+
1470
+ // Run-once guard across the process lifetime
1471
+ let codexBackfillDone = false;
1472
+
1473
+ // Backfill email + chatgpt account info for existing codex OAuth connections missing them
1474
+ export async function backfillCodexEmails() {
1475
+ if (codexBackfillDone) return;
1476
+ codexBackfillDone = true;
1477
+ try {
1478
+ const { getProviderConnections, updateProviderConnection } = await import("@/lib/localDb");
1479
+ const connections = await getProviderConnections();
1480
+ const targets = connections.filter((c) => {
1481
+ if (c.provider !== "codex" || c.authType !== "oauth" || !c.idToken) return false;
1482
+ const hasEmail = !!c.email;
1483
+ const hasAccountInfo = !!c.providerSpecificData?.chatgptAccountId;
1484
+ return !hasEmail || !hasAccountInfo;
1485
+ });
1486
+ for (const conn of targets) {
1487
+ const info = extractCodexAccountInfo(conn.idToken);
1488
+ if (!info.email && !info.chatgptAccountId) continue;
1489
+ const patch = {};
1490
+ if (!conn.email && info.email) patch.email = info.email;
1491
+ if (info.chatgptAccountId || info.chatgptPlanType) {
1492
+ patch.providerSpecificData = {
1493
+ ...(conn.providerSpecificData || {}),
1494
+ chatgptAccountId: info.chatgptAccountId,
1495
+ chatgptPlanType: info.chatgptPlanType,
1496
+ };
1497
+ }
1498
+ if (Object.keys(patch).length) {
1499
+ await updateProviderConnection(conn.id, patch);
1500
+ }
1501
+ }
1502
+ } catch (err) {
1503
+ codexBackfillDone = false;
1504
+ console.log("backfillCodexEmails failed:", err?.message || err);
1505
+ }
1506
+ }