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.
- package/CHANGELOG.md +222 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/README.zh-CN.md +1311 -0
- package/assets/pixel-router-2d.png +0 -0
- package/cli/LICENSE +42 -0
- package/cli/README.md +125 -0
- package/cli/app/package.json +58 -0
- package/cli/cli.js +806 -0
- package/cli/package.json +48 -0
- package/i18n/README.ja-JP.md +1209 -0
- package/i18n/README.ru.md +1311 -0
- package/i18n/README.vi.md +1310 -0
- package/i18n/README.zh-CN.md +1306 -0
- package/images/9router.png +0 -0
- package/jsconfig.json +12 -0
- package/next.config.mjs +71 -0
- package/open-sse/config/appConstants.js +203 -0
- package/open-sse/config/codexInstructions.js +119 -0
- package/open-sse/config/constants.js +4 -0
- package/open-sse/config/defaultThinkingSignature.js +12 -0
- package/open-sse/config/errorConfig.js +85 -0
- package/open-sse/config/googleTtsLanguages.js +62 -0
- package/open-sse/config/kiroConstants.js +322 -0
- package/open-sse/config/models.js +13 -0
- package/open-sse/config/ollamaModels.js +19 -0
- package/open-sse/config/providerModels.js +944 -0
- package/open-sse/config/providers.js +458 -0
- package/open-sse/config/runtimeConfig.js +72 -0
- package/open-sse/config/ttsModels.js +129 -0
- package/open-sse/executors/antigravity.js +504 -0
- package/open-sse/executors/azure.js +57 -0
- package/open-sse/executors/base.js +185 -0
- package/open-sse/executors/codex.js +469 -0
- package/open-sse/executors/commandcode.js +88 -0
- package/open-sse/executors/cursor.js +795 -0
- package/open-sse/executors/default.js +497 -0
- package/open-sse/executors/gemini-cli.js +89 -0
- package/open-sse/executors/github.js +379 -0
- package/open-sse/executors/grok-web.js +345 -0
- package/open-sse/executors/iflow.js +108 -0
- package/open-sse/executors/index.js +75 -0
- package/open-sse/executors/kiro.js +508 -0
- package/open-sse/executors/ollama-local.js +14 -0
- package/open-sse/executors/opencode-go.js +41 -0
- package/open-sse/executors/opencode.js +32 -0
- package/open-sse/executors/perplexity-web.js +507 -0
- package/open-sse/executors/qoder.js +450 -0
- package/open-sse/executors/qwen.js +129 -0
- package/open-sse/executors/vertex.js +131 -0
- package/open-sse/executors/xiaomi-tokenplan.js +19 -0
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
- package/open-sse/handlers/chatCore/requestDetail.js +102 -0
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
- package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
- package/open-sse/handlers/chatCore.js +287 -0
- package/open-sse/handlers/embeddingProviders/_base.js +4 -0
- package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
- package/open-sse/handlers/embeddingProviders/index.js +23 -0
- package/open-sse/handlers/embeddingProviders/openai.js +39 -0
- package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
- package/open-sse/handlers/embeddingsCore.js +126 -0
- package/open-sse/handlers/fetch/index.js +237 -0
- package/open-sse/handlers/imageGenerationCore.js +189 -0
- package/open-sse/handlers/imageProviders/_base.js +31 -0
- package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
- package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
- package/open-sse/handlers/imageProviders/codex.js +198 -0
- package/open-sse/handlers/imageProviders/comfyui.js +8 -0
- package/open-sse/handlers/imageProviders/falAi.js +41 -0
- package/open-sse/handlers/imageProviders/gemini.js +25 -0
- package/open-sse/handlers/imageProviders/huggingface.js +22 -0
- package/open-sse/handlers/imageProviders/index.js +40 -0
- package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
- package/open-sse/handlers/imageProviders/openai.js +40 -0
- package/open-sse/handlers/imageProviders/runwayml.js +47 -0
- package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
- package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
- package/open-sse/handlers/responsesHandler.js +103 -0
- package/open-sse/handlers/search/callers.js +371 -0
- package/open-sse/handlers/search/chatSearch.js +409 -0
- package/open-sse/handlers/search/index.js +201 -0
- package/open-sse/handlers/search/normalizers.js +223 -0
- package/open-sse/handlers/sttCore.js +194 -0
- package/open-sse/handlers/ttsCore.js +74 -0
- package/open-sse/handlers/ttsProviders/_base.js +39 -0
- package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
- package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
- package/open-sse/handlers/ttsProviders/gemini.js +117 -0
- package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
- package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
- package/open-sse/handlers/ttsProviders/index.js +50 -0
- package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
- package/open-sse/handlers/ttsProviders/minimax.js +59 -0
- package/open-sse/handlers/ttsProviders/openai.js +30 -0
- package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
- package/open-sse/index.js +82 -0
- package/open-sse/rtk/applyFilter.js +15 -0
- package/open-sse/rtk/autodetect.js +111 -0
- package/open-sse/rtk/caveman.js +100 -0
- package/open-sse/rtk/cavemanPrompts.js +78 -0
- package/open-sse/rtk/constants.js +55 -0
- package/open-sse/rtk/filters/buildOutput.js +127 -0
- package/open-sse/rtk/filters/dedupLog.js +44 -0
- package/open-sse/rtk/filters/find.js +49 -0
- package/open-sse/rtk/filters/gitDiff.js +92 -0
- package/open-sse/rtk/filters/gitStatus.js +117 -0
- package/open-sse/rtk/filters/grep.js +48 -0
- package/open-sse/rtk/filters/ls.js +79 -0
- package/open-sse/rtk/filters/readNumbered.js +27 -0
- package/open-sse/rtk/filters/searchList.js +52 -0
- package/open-sse/rtk/filters/smartTruncate.js +15 -0
- package/open-sse/rtk/filters/tree.js +32 -0
- package/open-sse/rtk/index.js +155 -0
- package/open-sse/rtk/registry.js +38 -0
- package/open-sse/services/accountFallback.js +238 -0
- package/open-sse/services/combo.js +198 -0
- package/open-sse/services/compact.js +71 -0
- package/open-sse/services/kiroModels.js +332 -0
- package/open-sse/services/model.js +261 -0
- package/open-sse/services/oauthCredentialManager.js +151 -0
- package/open-sse/services/projectId.js +306 -0
- package/open-sse/services/provider.js +356 -0
- package/open-sse/services/qoderModels.js +214 -0
- package/open-sse/services/tokenRefresh.js +939 -0
- package/open-sse/services/usage.js +1496 -0
- package/open-sse/transformer/responsesTransformer.js +439 -0
- package/open-sse/transformer/streamToJsonConverter.js +103 -0
- package/open-sse/translator/formats.js +36 -0
- package/open-sse/translator/helpers/claudeHelper.js +216 -0
- package/open-sse/translator/helpers/geminiHelper.js +372 -0
- package/open-sse/translator/helpers/imageHelper.js +34 -0
- package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
- package/open-sse/translator/helpers/openaiHelper.js +130 -0
- package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
- package/open-sse/translator/helpers/toolCallHelper.js +148 -0
- package/open-sse/translator/index.js +251 -0
- package/open-sse/translator/request/antigravity-to-openai.js +229 -0
- package/open-sse/translator/request/claude-to-openai.js +232 -0
- package/open-sse/translator/request/gemini-to-openai.js +147 -0
- package/open-sse/translator/request/openai-responses.js +318 -0
- package/open-sse/translator/request/openai-to-claude.js +401 -0
- package/open-sse/translator/request/openai-to-commandcode.js +170 -0
- package/open-sse/translator/request/openai-to-cursor.js +183 -0
- package/open-sse/translator/request/openai-to-gemini.js +470 -0
- package/open-sse/translator/request/openai-to-kiro.js +629 -0
- package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
- package/open-sse/translator/request/openai-to-ollama.js +192 -0
- package/open-sse/translator/request/openai-to-vertex.js +42 -0
- package/open-sse/translator/response/claude-to-openai.js +206 -0
- package/open-sse/translator/response/commandcode-to-openai.js +197 -0
- package/open-sse/translator/response/cursor-to-openai.js +30 -0
- package/open-sse/translator/response/gemini-to-openai.js +245 -0
- package/open-sse/translator/response/kiro-to-openai.js +195 -0
- package/open-sse/translator/response/ollama-to-openai.js +152 -0
- package/open-sse/translator/response/openai-responses.js +590 -0
- package/open-sse/translator/response/openai-to-antigravity.js +122 -0
- package/open-sse/translator/response/openai-to-claude.js +266 -0
- package/open-sse/utils/bypassHandler.js +298 -0
- package/open-sse/utils/claudeCloaking.js +155 -0
- package/open-sse/utils/claudeHeaderCache.js +70 -0
- package/open-sse/utils/clientDetector.js +63 -0
- package/open-sse/utils/cursorChecksum.js +149 -0
- package/open-sse/utils/cursorProtobuf.js +904 -0
- package/open-sse/utils/debugLog.js +14 -0
- package/open-sse/utils/error.js +147 -0
- package/open-sse/utils/ollamaTransform.js +85 -0
- package/open-sse/utils/proxyFetch.js +368 -0
- package/open-sse/utils/reasoningContentInjector.js +79 -0
- package/open-sse/utils/requestLogger.js +260 -0
- package/open-sse/utils/responsesStreamHelpers.js +49 -0
- package/open-sse/utils/sessionManager.js +82 -0
- package/open-sse/utils/stream.js +462 -0
- package/open-sse/utils/streamHandler.js +250 -0
- package/open-sse/utils/streamHelpers.js +122 -0
- package/open-sse/utils/toolDeduper.js +49 -0
- package/open-sse/utils/usageTracking.js +347 -0
- package/package.json +100 -0
- package/postcss.config.mjs +12 -0
- package/public/favicon.svg +11 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/i18n/literals/ar.json +195 -0
- package/public/i18n/literals/bn.json +195 -0
- package/public/i18n/literals/cs.json +195 -0
- package/public/i18n/literals/da.json +195 -0
- package/public/i18n/literals/de.json +195 -0
- package/public/i18n/literals/el.json +195 -0
- package/public/i18n/literals/es.json +195 -0
- package/public/i18n/literals/fi.json +195 -0
- package/public/i18n/literals/fr.json +195 -0
- package/public/i18n/literals/he.json +195 -0
- package/public/i18n/literals/hi.json +195 -0
- package/public/i18n/literals/hu.json +195 -0
- package/public/i18n/literals/id.json +195 -0
- package/public/i18n/literals/it.json +195 -0
- package/public/i18n/literals/ja.json +195 -0
- package/public/i18n/literals/ko.json +195 -0
- package/public/i18n/literals/nl.json +195 -0
- package/public/i18n/literals/no.json +195 -0
- package/public/i18n/literals/pl.json +195 -0
- package/public/i18n/literals/pt-BR.json +195 -0
- package/public/i18n/literals/pt-PT.json +195 -0
- package/public/i18n/literals/ro.json +195 -0
- package/public/i18n/literals/ru.json +195 -0
- package/public/i18n/literals/sv.json +195 -0
- package/public/i18n/literals/th.json +195 -0
- package/public/i18n/literals/tl.json +195 -0
- package/public/i18n/literals/tr.json +195 -0
- package/public/i18n/literals/uk.json +195 -0
- package/public/i18n/literals/ur.json +195 -0
- package/public/i18n/literals/vi.json +195 -0
- package/public/i18n/literals/zh-CN.json +772 -0
- package/public/i18n/literals/zh-TW.json +195 -0
- package/public/icons/discord.svg +4 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/next.svg +1 -0
- package/public/providers/alicode-intl.png +0 -0
- package/public/providers/alicode.png +0 -0
- package/public/providers/amp.png +0 -0
- package/public/providers/anthropic-m.png +0 -0
- package/public/providers/anthropic.png +0 -0
- package/public/providers/antigravity.png +0 -0
- package/public/providers/assemblyai.png +0 -0
- package/public/providers/aws-polly.png +0 -0
- package/public/providers/azure.png +0 -0
- package/public/providers/black-forest-labs.png +0 -0
- package/public/providers/blackbox.png +0 -0
- package/public/providers/brave-search.png +0 -0
- package/public/providers/byteplus.png +0 -0
- package/public/providers/cartesia.png +0 -0
- package/public/providers/cerebras.png +0 -0
- package/public/providers/chutes.png +0 -0
- package/public/providers/claude.png +0 -0
- package/public/providers/cline.png +0 -0
- package/public/providers/cloudflare-ai.png +0 -0
- package/public/providers/codebuddy.svg +37 -0
- package/public/providers/codex.png +0 -0
- package/public/providers/cohere.png +0 -0
- package/public/providers/comfyui.png +0 -0
- package/public/providers/commandcode.png +0 -0
- package/public/providers/continue.png +0 -0
- package/public/providers/copilot.png +0 -0
- package/public/providers/coqui.png +0 -0
- package/public/providers/cursor.png +0 -0
- package/public/providers/deepgram.png +0 -0
- package/public/providers/deepseek-tui.png +0 -0
- package/public/providers/deepseek.png +0 -0
- package/public/providers/droid.png +0 -0
- package/public/providers/edge-tts.png +0 -0
- package/public/providers/elevenlabs.png +0 -0
- package/public/providers/exa.png +0 -0
- package/public/providers/fal-ai.png +0 -0
- package/public/providers/firecrawl.png +0 -0
- package/public/providers/fireworks.png +0 -0
- package/public/providers/gemini-cli.png +0 -0
- package/public/providers/gemini.png +0 -0
- package/public/providers/github.png +0 -0
- package/public/providers/glm-cn.png +0 -0
- package/public/providers/glm.png +0 -0
- package/public/providers/google-pse.png +0 -0
- package/public/providers/google-tts.png +0 -0
- package/public/providers/grok-web.png +0 -0
- package/public/providers/groq.png +0 -0
- package/public/providers/hermes.png +0 -0
- package/public/providers/huggingface.png +0 -0
- package/public/providers/hyperbolic.png +0 -0
- package/public/providers/iflow.png +0 -0
- package/public/providers/inworld.png +0 -0
- package/public/providers/jcode.png +0 -0
- package/public/providers/jina-ai.png +0 -0
- package/public/providers/jina-reader.png +0 -0
- package/public/providers/kilocode.png +0 -0
- package/public/providers/kimi-coding.png +0 -0
- package/public/providers/kimi.png +0 -0
- package/public/providers/kiro.png +0 -0
- package/public/providers/linkup.png +0 -0
- package/public/providers/local-device.png +0 -0
- package/public/providers/minimax-cn.png +0 -0
- package/public/providers/minimax.png +0 -0
- package/public/providers/mistral.png +0 -0
- package/public/providers/nanobanana.png +0 -0
- package/public/providers/nebius.png +0 -0
- package/public/providers/nvidia.png +0 -0
- package/public/providers/oai-cc.png +0 -0
- package/public/providers/oai-r.png +0 -0
- package/public/providers/ollama-local.png +0 -0
- package/public/providers/ollama.png +0 -0
- package/public/providers/openai.png +0 -0
- package/public/providers/openclaw.png +0 -0
- package/public/providers/opencode-go.png +0 -0
- package/public/providers/opencode.png +0 -0
- package/public/providers/openrouter.png +0 -0
- package/public/providers/perplexity-web.png +0 -0
- package/public/providers/perplexity.png +0 -0
- package/public/providers/playht.png +0 -0
- package/public/providers/qoder.png +0 -0
- package/public/providers/qwen.png +0 -0
- package/public/providers/recraft.png +0 -0
- package/public/providers/roo.png +0 -0
- package/public/providers/runwayml.png +0 -0
- package/public/providers/sdwebui.png +0 -0
- package/public/providers/searchapi.png +0 -0
- package/public/providers/searxng.png +0 -0
- package/public/providers/serper.png +0 -0
- package/public/providers/siliconflow.png +0 -0
- package/public/providers/stability-ai.png +0 -0
- package/public/providers/tavily.png +0 -0
- package/public/providers/together.png +0 -0
- package/public/providers/topaz.png +0 -0
- package/public/providers/tortoise.png +0 -0
- package/public/providers/vertex-partner.png +0 -0
- package/public/providers/vertex.png +0 -0
- package/public/providers/volcengine-ark.png +0 -0
- package/public/providers/voyage-ai.png +0 -0
- package/public/providers/xai.png +0 -0
- package/public/providers/xiaomi-mimo.png +0 -0
- package/public/providers/xiaomi-tokenplan.png +0 -0
- package/public/providers/youcom.png +0 -0
- package/public/sw.js +22 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/compact-request-details.mjs +71 -0
- package/scripts/import-codex-gptjson.mjs +342 -0
- package/scripts/start-standalone.mjs +25 -0
- package/scripts/translate-readme.js +201 -0
- package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
- package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
- package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
- package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
- package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
- package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
- package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
- package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
- package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
- package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
- package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
- package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
- package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
- package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
- package/src/app/(dashboard)/dashboard/page.js +7 -0
- package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
- package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
- package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
- package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
- package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
- package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
- package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
- package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
- package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
- package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
- package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
- package/src/app/(dashboard)/layout.js +6 -0
- package/src/app/api/auth/login/route.js +76 -0
- package/src/app/api/auth/logout/route.js +12 -0
- package/src/app/api/auth/oidc/callback/route.js +87 -0
- package/src/app/api/auth/oidc/start/route.js +52 -0
- package/src/app/api/auth/oidc/test/route.js +84 -0
- package/src/app/api/auth/status/route.js +45 -0
- package/src/app/api/cli-tools/all-statuses/route.js +46 -0
- package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
- package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
- package/src/app/api/cli-tools/claude-settings/route.js +203 -0
- package/src/app/api/cli-tools/cline-settings/route.js +133 -0
- package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
- package/src/app/api/cli-tools/codex-settings/route.js +239 -0
- package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
- package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
- package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
- package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
- package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
- package/src/app/api/cli-tools/droid-settings/route.js +213 -0
- package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
- package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
- package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
- package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
- package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
- package/src/app/api/combos/[id]/route.js +81 -0
- package/src/app/api/combos/route.js +48 -0
- package/src/app/api/health/route.js +15 -0
- package/src/app/api/init/route.js +4 -0
- package/src/app/api/keys/[id]/route.js +58 -0
- package/src/app/api/keys/route.js +42 -0
- package/src/app/api/locale/route.js +30 -0
- package/src/app/api/mcp/[plugin]/message/route.js +21 -0
- package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
- package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
- package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
- package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
- package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
- package/src/app/api/media-providers/tts/voices/route.js +99 -0
- package/src/app/api/models/alias/route.js +53 -0
- package/src/app/api/models/availability/route.js +103 -0
- package/src/app/api/models/custom/route.js +48 -0
- package/src/app/api/models/disabled/route.js +50 -0
- package/src/app/api/models/route.js +64 -0
- package/src/app/api/models/test/ping.js +191 -0
- package/src/app/api/models/test/route.js +14 -0
- package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
- package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
- package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
- package/src/app/api/oauth/codex/import-token/route.js +96 -0
- package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
- package/src/app/api/oauth/cursor/import/route.js +100 -0
- package/src/app/api/oauth/gitlab/pat/route.js +62 -0
- package/src/app/api/oauth/iflow/cookie/route.js +137 -0
- package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
- package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
- package/src/app/api/oauth/kiro/import/route.js +110 -0
- package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
- package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
- package/src/app/api/pricing/route.js +134 -0
- package/src/app/api/provider-nodes/[id]/route.js +101 -0
- package/src/app/api/provider-nodes/route.js +104 -0
- package/src/app/api/provider-nodes/validate/route.js +201 -0
- package/src/app/api/providers/[id]/models/route.js +526 -0
- package/src/app/api/providers/[id]/route.js +189 -0
- package/src/app/api/providers/[id]/test/route.js +23 -0
- package/src/app/api/providers/[id]/test/testUtils.js +714 -0
- package/src/app/api/providers/[id]/test-models/route.js +66 -0
- package/src/app/api/providers/client/route.js +126 -0
- package/src/app/api/providers/kilo/free-models/route.js +55 -0
- package/src/app/api/providers/route.js +206 -0
- package/src/app/api/providers/suggested-models/filters.js +20 -0
- package/src/app/api/providers/suggested-models/route.js +32 -0
- package/src/app/api/providers/test-batch/route.js +131 -0
- package/src/app/api/providers/validate/route.js +637 -0
- package/src/app/api/proxy-pools/[id]/route.js +123 -0
- package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
- package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
- package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
- package/src/app/api/proxy-pools/route.js +93 -0
- package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
- package/src/app/api/settings/database/route.js +36 -0
- package/src/app/api/settings/proxy-test/route.js +23 -0
- package/src/app/api/settings/require-login/route.js +15 -0
- package/src/app/api/settings/route.js +100 -0
- package/src/app/api/shutdown/route.js +24 -0
- package/src/app/api/tags/route.js +18 -0
- package/src/app/api/translator/console-logs/route.js +24 -0
- package/src/app/api/translator/console-logs/stream/route.js +79 -0
- package/src/app/api/translator/load/route.js +45 -0
- package/src/app/api/translator/save/route.js +44 -0
- package/src/app/api/translator/send/route.js +94 -0
- package/src/app/api/translator/translate/route.js +90 -0
- package/src/app/api/tunnel/disable/route.js +12 -0
- package/src/app/api/tunnel/enable/route.js +16 -0
- package/src/app/api/tunnel/status/route.js +13 -0
- package/src/app/api/tunnel/tailscale-check/route.js +50 -0
- package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-install/route.js +72 -0
- package/src/app/api/usage/[connectionId]/route.js +188 -0
- package/src/app/api/usage/chart/route.js +21 -0
- package/src/app/api/usage/history/route.js +12 -0
- package/src/app/api/usage/logs/route.js +12 -0
- package/src/app/api/usage/providers/route.js +42 -0
- package/src/app/api/usage/request-details/route.js +57 -0
- package/src/app/api/usage/request-logs/route.js +13 -0
- package/src/app/api/usage/stats/route.js +23 -0
- package/src/app/api/usage/stream/route.js +79 -0
- package/src/app/api/v1/api/chat/route.js +37 -0
- package/src/app/api/v1/audio/speech/route.js +16 -0
- package/src/app/api/v1/audio/transcriptions/route.js +19 -0
- package/src/app/api/v1/audio/voices/route.js +68 -0
- package/src/app/api/v1/chat/completions/route.js +35 -0
- package/src/app/api/v1/embeddings/route.js +21 -0
- package/src/app/api/v1/images/generations/route.js +16 -0
- package/src/app/api/v1/messages/count_tokens/route.js +52 -0
- package/src/app/api/v1/messages/route.js +36 -0
- package/src/app/api/v1/models/[kind]/route.js +55 -0
- package/src/app/api/v1/models/info/route.js +110 -0
- package/src/app/api/v1/models/route.js +451 -0
- package/src/app/api/v1/responses/compact/route.js +37 -0
- package/src/app/api/v1/responses/route.js +30 -0
- package/src/app/api/v1/route.js +1 -0
- package/src/app/api/v1/search/route.js +21 -0
- package/src/app/api/v1/web/fetch/route.js +21 -0
- package/src/app/api/v1beta/models/[...path]/route.js +328 -0
- package/src/app/api/v1beta/models/route.js +44 -0
- package/src/app/api/version/route.js +45 -0
- package/src/app/api/version/shutdown/route.js +15 -0
- package/src/app/api/version/update/route.js +21 -0
- package/src/app/callback/page.js +148 -0
- package/src/app/dashboard/settings/pricing/page.js +173 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +496 -0
- package/src/app/landing/components/AnimatedBackground.js +57 -0
- package/src/app/landing/components/Features.js +133 -0
- package/src/app/landing/components/FlowAnimation.js +175 -0
- package/src/app/landing/components/Footer.js +61 -0
- package/src/app/landing/components/GetStarted.js +97 -0
- package/src/app/landing/components/HeroSection.js +47 -0
- package/src/app/landing/components/HowItWorks.js +66 -0
- package/src/app/landing/components/Navigation.js +72 -0
- package/src/app/landing/page.js +106 -0
- package/src/app/layout.js +49 -0
- package/src/app/login/page.js +197 -0
- package/src/app/manifest.js +30 -0
- package/src/app/page.js +5 -0
- package/src/dashboardGuard.js +242 -0
- package/src/i18n/RuntimeI18nProvider.js +27 -0
- package/src/i18n/config.js +146 -0
- package/src/i18n/runtime.js +162 -0
- package/src/lib/appUpdater.js +200 -0
- package/src/lib/auth/dashboardSession.js +68 -0
- package/src/lib/auth/loginLimiter.js +52 -0
- package/src/lib/auth/oidc.js +234 -0
- package/src/lib/consoleLogBuffer.js +79 -0
- package/src/lib/dataDir.js +29 -0
- package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
- package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
- package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
- package/src/lib/db/adapters/sqljsAdapter.js +115 -0
- package/src/lib/db/backup.js +35 -0
- package/src/lib/db/driver.js +85 -0
- package/src/lib/db/helpers/jsonCol.js +9 -0
- package/src/lib/db/helpers/kvStore.js +39 -0
- package/src/lib/db/helpers/metaStore.js +22 -0
- package/src/lib/db/index.js +171 -0
- package/src/lib/db/migrate.js +286 -0
- package/src/lib/db/migrations/001-initial.js +14 -0
- package/src/lib/db/migrations/index.js +10 -0
- package/src/lib/db/paths.js +18 -0
- package/src/lib/db/repos/aliasRepo.js +62 -0
- package/src/lib/db/repos/apiKeysRepo.js +75 -0
- package/src/lib/db/repos/combosRepo.js +73 -0
- package/src/lib/db/repos/connectionsRepo.js +226 -0
- package/src/lib/db/repos/disabledModelsRepo.js +56 -0
- package/src/lib/db/repos/nodesRepo.js +95 -0
- package/src/lib/db/repos/pricingRepo.js +108 -0
- package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
- package/src/lib/db/repos/requestDetailsRepo.js +259 -0
- package/src/lib/db/repos/settingsRepo.js +104 -0
- package/src/lib/db/repos/usageRepo.js +731 -0
- package/src/lib/db/schema.js +157 -0
- package/src/lib/db/version.js +21 -0
- package/src/lib/disabledModelsDb.js +4 -0
- package/src/lib/localDb.js +21 -0
- package/src/lib/mcp/stdioSseBridge.js +198 -0
- package/src/lib/mitmAliasCache.js +46 -0
- package/src/lib/network/connectionProxy.js +160 -0
- package/src/lib/network/initOutboundProxy.js +25 -0
- package/src/lib/network/outboundProxy.js +68 -0
- package/src/lib/network/proxyTest.js +91 -0
- package/src/lib/oauth/constants/oauth.js +284 -0
- package/src/lib/oauth/constants/xai.js +61 -0
- package/src/lib/oauth/providers.js +1506 -0
- package/src/lib/oauth/services/antigravity.js +321 -0
- package/src/lib/oauth/services/claude.js +136 -0
- package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
- package/src/lib/oauth/services/codex.js +144 -0
- package/src/lib/oauth/services/cursor.js +179 -0
- package/src/lib/oauth/services/gemini.js +240 -0
- package/src/lib/oauth/services/github.js +225 -0
- package/src/lib/oauth/services/iflow.js +202 -0
- package/src/lib/oauth/services/index.js +17 -0
- package/src/lib/oauth/services/kiro.js +334 -0
- package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
- package/src/lib/oauth/services/kiroConnections.js +93 -0
- package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
- package/src/lib/oauth/services/oauth.js +157 -0
- package/src/lib/oauth/services/openai.js +123 -0
- package/src/lib/oauth/services/qoder.js +216 -0
- package/src/lib/oauth/services/qwen.js +170 -0
- package/src/lib/oauth/services/xai.js +238 -0
- package/src/lib/oauth/utils/banner.js +63 -0
- package/src/lib/oauth/utils/pkce.js +39 -0
- package/src/lib/oauth/utils/server.js +415 -0
- package/src/lib/oauth/utils/ui.js +48 -0
- package/src/lib/providerNormalization.js +45 -0
- package/src/lib/qoder/constants.js +64 -0
- package/src/lib/qoder/cosy.js +175 -0
- package/src/lib/qoder/encoding.js +55 -0
- package/src/lib/requestDetailsDb.js +4 -0
- package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
- package/src/lib/tunnel/cloudflare/config.js +9 -0
- package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
- package/src/lib/tunnel/cloudflare/manager.js +151 -0
- package/src/lib/tunnel/cloudflare/pid.js +23 -0
- package/src/lib/tunnel/index.js +48 -0
- package/src/lib/tunnel/shared/dnsResolver.js +17 -0
- package/src/lib/tunnel/shared/internetCheck.js +26 -0
- package/src/lib/tunnel/shared/state.js +41 -0
- package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
- package/src/lib/tunnel/tailscale/config.js +7 -0
- package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
- package/src/lib/tunnel/tailscale/manager.js +129 -0
- package/src/lib/tunnel/tailscale/tailscale.js +790 -0
- package/src/lib/updater/updater.js +235 -0
- package/src/lib/usage/fetcher.js +208 -0
- package/src/lib/usageDb.js +7 -0
- package/src/mitm/antigravityIdeVersion.js +50 -0
- package/src/mitm/cert/generate.js +32 -0
- package/src/mitm/cert/install.js +269 -0
- package/src/mitm/cert/rootCA.js +173 -0
- package/src/mitm/config.js +87 -0
- package/src/mitm/dbReader.js +22 -0
- package/src/mitm/dns/dnsConfig.js +266 -0
- package/src/mitm/handlers/antigravity.js +33 -0
- package/src/mitm/handlers/base.js +226 -0
- package/src/mitm/handlers/copilot.js +35 -0
- package/src/mitm/handlers/cursor.js +15 -0
- package/src/mitm/handlers/kiro.js +526 -0
- package/src/mitm/logger.js +106 -0
- package/src/mitm/manager.js +851 -0
- package/src/mitm/paths.js +32 -0
- package/src/mitm/server.js +435 -0
- package/src/mitm/winElevated.js +81 -0
- package/src/models/index.js +38 -0
- package/src/proxy.js +5 -0
- package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
- package/src/shared/components/Avatar.js +88 -0
- package/src/shared/components/Badge.js +54 -0
- package/src/shared/components/BulkAccountAutomationModal.js +508 -0
- package/src/shared/components/Button.js +56 -0
- package/src/shared/components/Card.js +116 -0
- package/src/shared/components/ChangelogModal.js +97 -0
- package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
- package/src/shared/components/ComboFormModal.js +176 -0
- package/src/shared/components/CursorAuthModal.js +212 -0
- package/src/shared/components/DonateModal.js +136 -0
- package/src/shared/components/Drawer.js +82 -0
- package/src/shared/components/EditConnectionModal.js +286 -0
- package/src/shared/components/Footer.js +132 -0
- package/src/shared/components/GitLabAuthModal.js +194 -0
- package/src/shared/components/Header.js +380 -0
- package/src/shared/components/HeaderLanguage.js +46 -0
- package/src/shared/components/HeaderMenu.js +126 -0
- package/src/shared/components/IFlowCookieModal.js +132 -0
- package/src/shared/components/Input.js +65 -0
- package/src/shared/components/KiroAuthModal.js +1171 -0
- package/src/shared/components/KiroOAuthWrapper.js +149 -0
- package/src/shared/components/KiroSocialOAuthModal.js +205 -0
- package/src/shared/components/LanguageSwitcher.js +190 -0
- package/src/shared/components/Loading.js +75 -0
- package/src/shared/components/ManualConfigModal.js +44 -0
- package/src/shared/components/McpMarketplaceModal.js +255 -0
- package/src/shared/components/Modal.js +146 -0
- package/src/shared/components/ModelSelectModal.js +537 -0
- package/src/shared/components/NineRemoteButton.js +23 -0
- package/src/shared/components/NineRemotePromoModal.js +99 -0
- package/src/shared/components/NoAuthProxyCard.js +86 -0
- package/src/shared/components/OAuthModal.js +682 -0
- package/src/shared/components/Pagination.js +150 -0
- package/src/shared/components/PricingModal.js +208 -0
- package/src/shared/components/ProviderIcon.js +63 -0
- package/src/shared/components/ProviderInfoCard.js +82 -0
- package/src/shared/components/RequestLogger.js +121 -0
- package/src/shared/components/SegmentedControl.js +48 -0
- package/src/shared/components/Select.js +67 -0
- package/src/shared/components/Sidebar.js +441 -0
- package/src/shared/components/ThemeProvider.js +15 -0
- package/src/shared/components/ThemeToggle.js +42 -0
- package/src/shared/components/Toggle.js +69 -0
- package/src/shared/components/Tooltip.js +25 -0
- package/src/shared/components/UsageStats.js +505 -0
- package/src/shared/components/index.js +46 -0
- package/src/shared/components/layouts/AuthLayout.js +29 -0
- package/src/shared/components/layouts/DashboardLayout.js +104 -0
- package/src/shared/components/layouts/index.js +4 -0
- package/src/shared/constants/cliTools.js +397 -0
- package/src/shared/constants/colors.js +77 -0
- package/src/shared/constants/config.js +99 -0
- package/src/shared/constants/coworkPlugins.js +75 -0
- package/src/shared/constants/index.js +4 -0
- package/src/shared/constants/locales.js +36 -0
- package/src/shared/constants/mitmToolHosts.js +12 -0
- package/src/shared/constants/models.js +38 -0
- package/src/shared/constants/pricing.js +303 -0
- package/src/shared/constants/providers.js +289 -0
- package/src/shared/constants/skills.js +78 -0
- package/src/shared/constants/ttsProviders.js +138 -0
- package/src/shared/hooks/index.js +2 -0
- package/src/shared/hooks/useCopyToClipboard.js +43 -0
- package/src/shared/hooks/useTheme.js +60 -0
- package/src/shared/services/bootstrap.js +12 -0
- package/src/shared/services/initializeApp.js +268 -0
- package/src/shared/utils/api.js +93 -0
- package/src/shared/utils/apiKey.js +98 -0
- package/src/shared/utils/clineAuth.js +37 -0
- package/src/shared/utils/cn.js +11 -0
- package/src/shared/utils/connectionStatus.js +162 -0
- package/src/shared/utils/index.js +40 -0
- package/src/shared/utils/machine.js +6 -0
- package/src/shared/utils/machineId.js +66 -0
- package/src/shared/utils/providerModelsFetcher.js +30 -0
- package/src/sse/handlers/chat.js +261 -0
- package/src/sse/handlers/embeddings.js +141 -0
- package/src/sse/handlers/fetch.js +213 -0
- package/src/sse/handlers/imageGeneration.js +142 -0
- package/src/sse/handlers/search.js +206 -0
- package/src/sse/handlers/stt.js +88 -0
- package/src/sse/handlers/tts.js +114 -0
- package/src/sse/services/auth.js +346 -0
- package/src/sse/services/codexGateway.js +215 -0
- package/src/sse/services/model.js +99 -0
- package/src/sse/services/tokenRefresh.js +319 -0
- package/src/sse/utils/logger.js +75 -0
- package/src/store/headerSearchStore.js +19 -0
- package/src/store/index.js +6 -0
- package/src/store/notificationStore.js +45 -0
- package/src/store/providerStore.js +55 -0
- package/src/store/settingsStore.js +51 -0
- package/src/store/themeStore.js +54 -0
- package/src/store/userStore.js +20 -0
- package/start.sh +4 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { getProviderConnections, validateApiKey, updateProviderConnection, getSettings } from "@/lib/localDb";
|
|
2
|
+
import { resolveConnectionProxyConfig } from "@/lib/network/connectionProxy";
|
|
3
|
+
import { formatRetryAfter, checkFallbackError, isModelLockActive, buildModelLockUpdate, getEarliestModelLockUntil, isNonAccountError } from "open-sse/services/accountFallback.js";
|
|
4
|
+
import { MAX_RATE_LIMIT_COOLDOWN_MS } from "open-sse/config/errorConfig.js";
|
|
5
|
+
import { resolveProviderId, FREE_PROVIDERS } from "@/shared/constants/providers.js";
|
|
6
|
+
import * as log from "../utils/logger.js";
|
|
7
|
+
|
|
8
|
+
// Mutex to prevent race conditions during account selection
|
|
9
|
+
let selectionMutex = Promise.resolve();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get provider credentials from localDb
|
|
13
|
+
* Filters out unavailable accounts and returns the selected account based on strategy
|
|
14
|
+
* @param {string} provider - Provider name
|
|
15
|
+
* @param {Set<string>|string|null} excludeConnectionIds - Connection ID(s) to exclude (for retry with next account)
|
|
16
|
+
* @param {string|null} model - Model name for per-model rate limit filtering
|
|
17
|
+
*/
|
|
18
|
+
export async function getProviderCredentials(provider, excludeConnectionIds = null, model = null, options = {}) {
|
|
19
|
+
// Normalize to Set for consistent handling
|
|
20
|
+
const excludeSet = excludeConnectionIds instanceof Set
|
|
21
|
+
? excludeConnectionIds
|
|
22
|
+
: (excludeConnectionIds ? new Set([excludeConnectionIds]) : new Set());
|
|
23
|
+
const preferredConnectionId = options?.preferredConnectionId || null;
|
|
24
|
+
const strictPreferred = !!options?.strictPreferred;
|
|
25
|
+
// Acquire mutex to prevent race conditions
|
|
26
|
+
const currentMutex = selectionMutex;
|
|
27
|
+
let resolveMutex;
|
|
28
|
+
selectionMutex = new Promise(resolve => { resolveMutex = resolve; });
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await currentMutex;
|
|
32
|
+
|
|
33
|
+
// Resolve alias to provider ID (e.g., "kc" -> "kilocode")
|
|
34
|
+
const providerId = resolveProviderId(provider);
|
|
35
|
+
|
|
36
|
+
// Inject a virtual connection for no-auth free providers (with optional proxy pool from settings)
|
|
37
|
+
if (FREE_PROVIDERS[providerId]?.noAuth) {
|
|
38
|
+
const settings = await getSettings();
|
|
39
|
+
const override = (settings.providerStrategies || {})[providerId] || {};
|
|
40
|
+
const resolvedProxy = await resolveConnectionProxyConfig({ proxyPoolId: override.proxyPoolId || "" });
|
|
41
|
+
return {
|
|
42
|
+
id: "noauth",
|
|
43
|
+
connectionName: "Public",
|
|
44
|
+
isActive: true,
|
|
45
|
+
accessToken: "public",
|
|
46
|
+
providerSpecificData: {
|
|
47
|
+
connectionProxyEnabled: resolvedProxy.connectionProxyEnabled,
|
|
48
|
+
connectionProxyUrl: resolvedProxy.connectionProxyUrl,
|
|
49
|
+
connectionNoProxy: resolvedProxy.connectionNoProxy,
|
|
50
|
+
connectionProxyPoolId: resolvedProxy.proxyPoolId || null,
|
|
51
|
+
vercelRelayUrl: resolvedProxy.vercelRelayUrl || "",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const connections = await getProviderConnections({ provider: providerId, isActive: true });
|
|
57
|
+
log.debug("AUTH", `${provider} | total connections: ${connections.length}, excludeIds: ${excludeSet.size > 0 ? [...excludeSet].join(",") : "none"}, model: ${model || "any"}`);
|
|
58
|
+
|
|
59
|
+
if (connections.length === 0) {
|
|
60
|
+
log.warn("AUTH", `No credentials for ${provider}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const baseAvailableConnections = connections.filter(c => {
|
|
65
|
+
if (excludeSet.has(c.id)) return false;
|
|
66
|
+
if (isModelLockActive(c, model)) return false;
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Filter out model-locked and excluded connections. Strict pinning is used
|
|
71
|
+
// by Codex gateway account aliases, where silently switching identity would
|
|
72
|
+
// break the user's mental model of "this session/account".
|
|
73
|
+
let availableConnections = baseAvailableConnections;
|
|
74
|
+
if (strictPreferred && preferredConnectionId) {
|
|
75
|
+
const preferred = connections.find((c) => c.id === preferredConnectionId);
|
|
76
|
+
if (!preferred) {
|
|
77
|
+
log.warn("AUTH", `${provider} | pinned account not found: ${preferredConnectionId}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (excludeSet.has(preferred.id)) {
|
|
81
|
+
log.warn("AUTH", `${provider} | pinned account excluded after failure: ${preferred.id?.slice(0, 8)}`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (isModelLockActive(preferred, model)) {
|
|
85
|
+
const retryAfter = getEarliestModelLockUntil(preferred);
|
|
86
|
+
return {
|
|
87
|
+
allRateLimited: true,
|
|
88
|
+
retryAfter,
|
|
89
|
+
retryAfterHuman: formatRetryAfter(retryAfter),
|
|
90
|
+
lastError: preferred.lastError || "Pinned account unavailable",
|
|
91
|
+
lastErrorCode: preferred.errorCode || null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
availableConnections = [preferred];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
log.debug("AUTH", `${provider} | available: ${availableConnections.length}/${connections.length}`);
|
|
98
|
+
connections.forEach(c => {
|
|
99
|
+
const excluded = excludeSet.has(c.id);
|
|
100
|
+
const locked = isModelLockActive(c, model);
|
|
101
|
+
if (excluded || locked) {
|
|
102
|
+
const lockUntil = getEarliestModelLockUntil(c);
|
|
103
|
+
log.debug("AUTH", ` → ${c.id?.slice(0, 8)} | ${excluded ? "excluded" : ""} ${locked ? `modelLocked(${model}) until ${lockUntil}` : ""}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (availableConnections.length === 0) {
|
|
108
|
+
// Find earliest lock expiry across all connections for retry timing
|
|
109
|
+
const lockedConns = connections.filter(c => isModelLockActive(c, model));
|
|
110
|
+
const expiries = lockedConns.map(c => getEarliestModelLockUntil(c)).filter(Boolean);
|
|
111
|
+
const earliest = expiries.sort()[0] || null;
|
|
112
|
+
if (earliest) {
|
|
113
|
+
const earliestConn = lockedConns[0];
|
|
114
|
+
log.warn("AUTH", `${provider} | all ${connections.length} accounts locked for ${model || "all"} (${formatRetryAfter(earliest)}) | lastError=${earliestConn?.lastError?.slice(0, 50)}`);
|
|
115
|
+
return {
|
|
116
|
+
allRateLimited: true,
|
|
117
|
+
retryAfter: earliest,
|
|
118
|
+
retryAfterHuman: formatRetryAfter(earliest),
|
|
119
|
+
lastError: earliestConn?.lastError || null,
|
|
120
|
+
lastErrorCode: earliestConn?.errorCode || null
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
log.warn("AUTH", `${provider} | all ${connections.length} accounts unavailable`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const settings = await getSettings();
|
|
128
|
+
// Per-provider strategy overrides global setting
|
|
129
|
+
const providerOverride = (settings.providerStrategies || {})[providerId] || {};
|
|
130
|
+
const strategy = providerOverride.fallbackStrategy || settings.fallbackStrategy || "fill-first";
|
|
131
|
+
|
|
132
|
+
let connection;
|
|
133
|
+
// Pin to preferred connection if specified and available
|
|
134
|
+
if (preferredConnectionId) {
|
|
135
|
+
connection = availableConnections.find((c) => c.id === preferredConnectionId);
|
|
136
|
+
if (connection) {
|
|
137
|
+
log.info("AUTH", `${provider} | pinned to ${connection.id?.slice(0, 8)} (${connection.name || connection.email || "unnamed"})`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (connection) {
|
|
141
|
+
// skip strategy
|
|
142
|
+
} else if (strictPreferred && preferredConnectionId) {
|
|
143
|
+
log.warn("AUTH", `${provider} | pinned account unavailable: ${preferredConnectionId}`);
|
|
144
|
+
return null;
|
|
145
|
+
} else if (strategy === "round-robin") {
|
|
146
|
+
const stickyLimit = providerOverride.stickyRoundRobinLimit || settings.stickyRoundRobinLimit || 3;
|
|
147
|
+
|
|
148
|
+
// Sort by lastUsed (most recent first) to find current candidate
|
|
149
|
+
const byRecency = [...availableConnections].sort((a, b) => {
|
|
150
|
+
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
|
|
151
|
+
if (!a.lastUsedAt) return 1;
|
|
152
|
+
if (!b.lastUsedAt) return -1;
|
|
153
|
+
return new Date(b.lastUsedAt) - new Date(a.lastUsedAt);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const current = byRecency[0];
|
|
157
|
+
const currentCount = current?.consecutiveUseCount || 0;
|
|
158
|
+
|
|
159
|
+
if (current && current.lastUsedAt && currentCount < stickyLimit) {
|
|
160
|
+
// Stay with current account
|
|
161
|
+
connection = current;
|
|
162
|
+
// Update lastUsedAt and increment count (await to ensure persistence)
|
|
163
|
+
await updateProviderConnection(connection.id, {
|
|
164
|
+
lastUsedAt: new Date().toISOString(),
|
|
165
|
+
consecutiveUseCount: (connection.consecutiveUseCount || 0) + 1
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
// Pick the least recently used (excluding current if possible)
|
|
169
|
+
const sortedByOldest = [...availableConnections].sort((a, b) => {
|
|
170
|
+
if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
|
|
171
|
+
if (!a.lastUsedAt) return -1;
|
|
172
|
+
if (!b.lastUsedAt) return 1;
|
|
173
|
+
return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
connection = sortedByOldest[0];
|
|
177
|
+
|
|
178
|
+
// Update lastUsedAt and reset count to 1 (await to ensure persistence)
|
|
179
|
+
await updateProviderConnection(connection.id, {
|
|
180
|
+
lastUsedAt: new Date().toISOString(),
|
|
181
|
+
consecutiveUseCount: 1
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Default: fill-first (already sorted by priority in getProviderConnections)
|
|
186
|
+
connection = availableConnections[0];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const resolvedProxy = await resolveConnectionProxyConfig(connection.providerSpecificData || {});
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
authType: connection.authType,
|
|
193
|
+
apiKey: connection.apiKey,
|
|
194
|
+
accessToken: connection.accessToken,
|
|
195
|
+
refreshToken: connection.refreshToken,
|
|
196
|
+
idToken: connection.idToken,
|
|
197
|
+
expiresAt: connection.expiresAt,
|
|
198
|
+
expiresIn: connection.expiresIn,
|
|
199
|
+
lastRefreshAt: connection.lastRefreshAt,
|
|
200
|
+
projectId: connection.projectId,
|
|
201
|
+
connectionName: connection.displayName || connection.name || connection.email || connection.id,
|
|
202
|
+
copilotToken: connection.providerSpecificData?.copilotToken,
|
|
203
|
+
providerSpecificData: {
|
|
204
|
+
...(connection.providerSpecificData || {}),
|
|
205
|
+
connectionProxyEnabled: resolvedProxy.connectionProxyEnabled,
|
|
206
|
+
connectionProxyUrl: resolvedProxy.connectionProxyUrl,
|
|
207
|
+
connectionNoProxy: resolvedProxy.connectionNoProxy,
|
|
208
|
+
connectionProxyPoolId: resolvedProxy.proxyPoolId || null,
|
|
209
|
+
vercelRelayUrl: resolvedProxy.vercelRelayUrl || "",
|
|
210
|
+
},
|
|
211
|
+
connectionId: connection.id,
|
|
212
|
+
// Include current status for optimization check
|
|
213
|
+
testStatus: connection.testStatus,
|
|
214
|
+
lastError: connection.lastError,
|
|
215
|
+
// Pass full connection for clearAccountError to read modelLock_* keys
|
|
216
|
+
_connection: connection
|
|
217
|
+
};
|
|
218
|
+
} finally {
|
|
219
|
+
if (resolveMutex) resolveMutex();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Mark account+model as unavailable — locks modelLock_${model} in DB.
|
|
225
|
+
* All errors (429, 401, 5xx, etc.) lock per model, not per account.
|
|
226
|
+
* @param {string} connectionId
|
|
227
|
+
* @param {number} status - HTTP status code from upstream
|
|
228
|
+
* @param {string} errorText
|
|
229
|
+
* @param {string|null} provider
|
|
230
|
+
* @param {string|null} model - The specific model that triggered the error
|
|
231
|
+
* @returns {{ shouldFallback: boolean, cooldownMs: number }}
|
|
232
|
+
*/
|
|
233
|
+
export async function markAccountUnavailable(connectionId, status, errorText, provider = null, model = null, resetsAtMs = null) {
|
|
234
|
+
if (!connectionId || connectionId === "noauth") return { shouldFallback: false, cooldownMs: 0 };
|
|
235
|
+
if (isNonAccountError(status, errorText)) {
|
|
236
|
+
log.warn("AUTH", `non-account error; no fallback [${status}] ${typeof errorText === "string" ? errorText.slice(0, 100) : "Provider error"}`);
|
|
237
|
+
return { shouldFallback: false, cooldownMs: 0 };
|
|
238
|
+
}
|
|
239
|
+
const connections = await getProviderConnections({ provider });
|
|
240
|
+
const conn = connections.find(c => c.id === connectionId);
|
|
241
|
+
const backoffLevel = conn?.backoffLevel || 0;
|
|
242
|
+
|
|
243
|
+
// Provider-specific precise cooldown (e.g. codex usage_limit_reached resets_at) overrides backoff
|
|
244
|
+
let shouldFallback, cooldownMs, newBackoffLevel;
|
|
245
|
+
if (resetsAtMs && resetsAtMs > Date.now()) {
|
|
246
|
+
shouldFallback = true;
|
|
247
|
+
cooldownMs = Math.min(resetsAtMs - Date.now(), MAX_RATE_LIMIT_COOLDOWN_MS);
|
|
248
|
+
newBackoffLevel = 0;
|
|
249
|
+
} else {
|
|
250
|
+
({ shouldFallback, cooldownMs, newBackoffLevel } = checkFallbackError(status, errorText, backoffLevel));
|
|
251
|
+
}
|
|
252
|
+
if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 };
|
|
253
|
+
|
|
254
|
+
const reason = typeof errorText === "string" ? errorText.slice(0, 100) : "Provider error";
|
|
255
|
+
const lockUpdate = buildModelLockUpdate(model, cooldownMs);
|
|
256
|
+
|
|
257
|
+
await updateProviderConnection(connectionId, {
|
|
258
|
+
...lockUpdate,
|
|
259
|
+
testStatus: "unavailable",
|
|
260
|
+
lastError: reason,
|
|
261
|
+
errorCode: status,
|
|
262
|
+
lastErrorAt: new Date().toISOString(),
|
|
263
|
+
backoffLevel: newBackoffLevel ?? backoffLevel
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const lockKey = Object.keys(lockUpdate)[0];
|
|
267
|
+
const connName = conn?.displayName || conn?.name || conn?.email || connectionId.slice(0, 8);
|
|
268
|
+
log.warn("AUTH", `${connName} locked ${lockKey} for ${Math.round(cooldownMs / 1000)}s [${status}]`);
|
|
269
|
+
|
|
270
|
+
if (provider && status && reason) {
|
|
271
|
+
console.error(`❌ ${provider} [${status}]: ${reason}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { shouldFallback: true, cooldownMs };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Clear account error status on successful request.
|
|
279
|
+
* - Clears modelLock_${model} (the model that just succeeded)
|
|
280
|
+
* - Lazy-cleans any other expired modelLock_* keys
|
|
281
|
+
* - Resets error state only if no active locks remain
|
|
282
|
+
* @param {string} connectionId
|
|
283
|
+
* @param {object} currentConnection - credentials object (has _connection) or raw connection
|
|
284
|
+
* @param {string|null} model - model that succeeded
|
|
285
|
+
*/
|
|
286
|
+
export async function clearAccountError(connectionId, currentConnection, model = null) {
|
|
287
|
+
if (!connectionId || connectionId === "noauth") return;
|
|
288
|
+
const conn = currentConnection._connection || currentConnection;
|
|
289
|
+
const now = Date.now();
|
|
290
|
+
const allLockKeys = Object.keys(conn).filter(k => k.startsWith("modelLock_"));
|
|
291
|
+
|
|
292
|
+
if (!conn.testStatus && !conn.lastError && allLockKeys.length === 0) return;
|
|
293
|
+
|
|
294
|
+
// Keys to clear: current model's lock + all expired locks
|
|
295
|
+
const keysToClear = allLockKeys.filter(k => {
|
|
296
|
+
if (model && k === `modelLock_${model}`) return true; // succeeded model
|
|
297
|
+
if (model && k === "modelLock___all") return true; // account-level lock
|
|
298
|
+
const expiry = conn[k];
|
|
299
|
+
return expiry && new Date(expiry).getTime() <= now; // expired
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (keysToClear.length === 0 && conn.testStatus !== "unavailable" && !conn.lastError) return;
|
|
303
|
+
|
|
304
|
+
// Check if any active locks remain after clearing
|
|
305
|
+
const remainingActiveLocks = allLockKeys.filter(k => {
|
|
306
|
+
if (keysToClear.includes(k)) return false;
|
|
307
|
+
const expiry = conn[k];
|
|
308
|
+
return expiry && new Date(expiry).getTime() > now;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const clearObj = Object.fromEntries(keysToClear.map(k => [k, null]));
|
|
312
|
+
|
|
313
|
+
// Only reset error state if no active locks remain
|
|
314
|
+
if (remainingActiveLocks.length === 0) {
|
|
315
|
+
Object.assign(clearObj, { testStatus: "active", lastError: null, lastErrorAt: null, backoffLevel: 0 });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await updateProviderConnection(connectionId, clearObj);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Extract API key from request headers
|
|
323
|
+
*/
|
|
324
|
+
export function extractApiKey(request) {
|
|
325
|
+
// Check Authorization header first
|
|
326
|
+
const authHeader = request.headers.get("Authorization");
|
|
327
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
328
|
+
return authHeader.slice(7);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check Anthropic x-api-key header
|
|
332
|
+
const xApiKey = request.headers.get("x-api-key");
|
|
333
|
+
if (xApiKey) {
|
|
334
|
+
return xApiKey;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Validate API key (optional - for local use can skip)
|
|
342
|
+
*/
|
|
343
|
+
export async function isValidApiKey(apiKey) {
|
|
344
|
+
if (!apiKey) return false;
|
|
345
|
+
return await validateApiKey(apiKey);
|
|
346
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { getProviderConnections, getSettings } from "@/lib/localDb";
|
|
2
|
+
import { PROVIDER_MODELS } from "open-sse/config/providerModels.js";
|
|
3
|
+
|
|
4
|
+
export const CODEX_GATEWAY_DEFAULT_MODEL = "gpt-5.5";
|
|
5
|
+
export const CODEX_GATEWAY_ACCOUNT_LIMIT = 5000;
|
|
6
|
+
|
|
7
|
+
function cleanSegment(value) {
|
|
8
|
+
return String(value || "").trim().replace(/^\/+|\/+$/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function stripCodexPrefix(model) {
|
|
12
|
+
const value = cleanSegment(model);
|
|
13
|
+
if (value.startsWith("cx/")) return value.slice(3);
|
|
14
|
+
if (value.startsWith("codex/")) return value.slice(6);
|
|
15
|
+
return value || CODEX_GATEWAY_DEFAULT_MODEL;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeRouterTarget(value) {
|
|
19
|
+
const target = cleanSegment(value) || CODEX_GATEWAY_DEFAULT_MODEL;
|
|
20
|
+
if (!target.includes("/")) return `cx/${target}`;
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeCodexTarget(value) {
|
|
25
|
+
return `cx/${stripCodexPrefix(value)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseCodexGatewayModel(modelStr) {
|
|
29
|
+
const value = cleanSegment(modelStr);
|
|
30
|
+
if (!value) return null;
|
|
31
|
+
|
|
32
|
+
if (value === "auto-codex") {
|
|
33
|
+
return {
|
|
34
|
+
mode: "router",
|
|
35
|
+
modelString: `cx/${CODEX_GATEWAY_DEFAULT_MODEL}`,
|
|
36
|
+
strictAccount: false,
|
|
37
|
+
label: "Auto Codex",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (value.startsWith("router/")) {
|
|
42
|
+
return {
|
|
43
|
+
mode: "router",
|
|
44
|
+
modelString: normalizeRouterTarget(value.slice("router/".length)),
|
|
45
|
+
strictAccount: false,
|
|
46
|
+
label: "Router Pool",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (value.startsWith("original/")) {
|
|
51
|
+
return {
|
|
52
|
+
mode: "original",
|
|
53
|
+
modelString: normalizeCodexTarget(value.slice("original/".length)),
|
|
54
|
+
strictAccount: true,
|
|
55
|
+
label: "Original Codex",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (value === "original") {
|
|
60
|
+
return {
|
|
61
|
+
mode: "original",
|
|
62
|
+
modelString: `cx/${CODEX_GATEWAY_DEFAULT_MODEL}`,
|
|
63
|
+
strictAccount: true,
|
|
64
|
+
label: "Original Codex",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const accountPrefix = value.startsWith("account/")
|
|
69
|
+
? "account/"
|
|
70
|
+
: (value.startsWith("codex-account/") ? "codex-account/" : null);
|
|
71
|
+
if (accountPrefix) {
|
|
72
|
+
const rest = value.slice(accountPrefix.length);
|
|
73
|
+
const slash = rest.indexOf("/");
|
|
74
|
+
const accountRef = slash === -1 ? rest : rest.slice(0, slash);
|
|
75
|
+
const model = slash === -1 ? CODEX_GATEWAY_DEFAULT_MODEL : rest.slice(slash + 1);
|
|
76
|
+
if (!accountRef) return null;
|
|
77
|
+
return {
|
|
78
|
+
mode: "account",
|
|
79
|
+
accountRef,
|
|
80
|
+
modelString: normalizeCodexTarget(model),
|
|
81
|
+
strictAccount: true,
|
|
82
|
+
label: "Pinned Codex Account",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getCodexConnectionLabel(connection) {
|
|
90
|
+
return (
|
|
91
|
+
connection?.displayName ||
|
|
92
|
+
connection?.name ||
|
|
93
|
+
connection?.email ||
|
|
94
|
+
connection?.id ||
|
|
95
|
+
"Codex account"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function slugifyCodexAccount(connection) {
|
|
100
|
+
const raw = getCodexConnectionLabel(connection);
|
|
101
|
+
const base = String(raw)
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.replace(/@/g, "-")
|
|
104
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
105
|
+
.replace(/^-+|-+$/g, "")
|
|
106
|
+
.slice(0, 48) || "account";
|
|
107
|
+
return `${base}-${String(connection?.id || "").slice(0, 8)}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeRef(value) {
|
|
111
|
+
return String(value || "").trim().toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isImportedGptJson(connection) {
|
|
115
|
+
const data = connection?.providerSpecificData || {};
|
|
116
|
+
return data.importedFrom === "GPTJson" || String(data.tokenSource || "").startsWith("ChatGPT_team");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function listCodexGatewayAccounts(limit = CODEX_GATEWAY_ACCOUNT_LIMIT) {
|
|
120
|
+
const connections = await getProviderConnections({ provider: "codex", isActive: true });
|
|
121
|
+
const accounts = connections.slice(0, limit).map((connection) => ({
|
|
122
|
+
id: connection.id,
|
|
123
|
+
slug: slugifyCodexAccount(connection),
|
|
124
|
+
label: getCodexConnectionLabel(connection),
|
|
125
|
+
email: connection.email || null,
|
|
126
|
+
imported: isImportedGptJson(connection),
|
|
127
|
+
priority: connection.priority || null,
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
const original = findOriginalCodexConnection(connections);
|
|
131
|
+
return {
|
|
132
|
+
accounts,
|
|
133
|
+
original: original ? {
|
|
134
|
+
id: original.id,
|
|
135
|
+
slug: slugifyCodexAccount(original),
|
|
136
|
+
label: getCodexConnectionLabel(original),
|
|
137
|
+
email: original.email || null,
|
|
138
|
+
imported: isImportedGptJson(original),
|
|
139
|
+
priority: original.priority || null,
|
|
140
|
+
} : null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function findOriginalCodexConnection(connections) {
|
|
145
|
+
if (!Array.isArray(connections) || connections.length === 0) return null;
|
|
146
|
+
return connections.find((connection) => !isImportedGptJson(connection)) || connections[0];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function resolveCodexConnectionRef(ref) {
|
|
150
|
+
const target = normalizeRef(ref);
|
|
151
|
+
if (!target) return null;
|
|
152
|
+
const connections = await getProviderConnections({ provider: "codex", isActive: true });
|
|
153
|
+
|
|
154
|
+
const exact = connections.find((connection) => (
|
|
155
|
+
normalizeRef(connection.id) === target ||
|
|
156
|
+
slugifyCodexAccount(connection) === target ||
|
|
157
|
+
normalizeRef(connection.email) === target ||
|
|
158
|
+
normalizeRef(connection.name) === target ||
|
|
159
|
+
normalizeRef(connection.displayName) === target
|
|
160
|
+
));
|
|
161
|
+
if (exact) return exact;
|
|
162
|
+
|
|
163
|
+
const prefixMatches = connections.filter((connection) => (
|
|
164
|
+
normalizeRef(connection.id).startsWith(target) ||
|
|
165
|
+
slugifyCodexAccount(connection).startsWith(target)
|
|
166
|
+
));
|
|
167
|
+
if (prefixMatches.length === 1) return prefixMatches[0];
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function resolveCodexGatewayConnection(gateway) {
|
|
173
|
+
if (!gateway || (gateway.mode !== "original" && gateway.mode !== "account")) return null;
|
|
174
|
+
|
|
175
|
+
if (gateway.mode === "account") {
|
|
176
|
+
return resolveCodexConnectionRef(gateway.accountRef);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const settings = await getSettings();
|
|
180
|
+
const configuredId = settings.codexGatewayOriginalConnectionId || settings.codexOriginalConnectionId || "";
|
|
181
|
+
if (configuredId) {
|
|
182
|
+
const configured = await resolveCodexConnectionRef(configuredId);
|
|
183
|
+
if (configured) return configured;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const connections = await getProviderConnections({ provider: "codex", isActive: true });
|
|
187
|
+
return findOriginalCodexConnection(connections);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function buildCodexGatewayModelEntries(connections = []) {
|
|
191
|
+
const hasCodex = connections.some((connection) => connection.provider === "codex" && connection.isActive !== false);
|
|
192
|
+
if (!hasCodex) return [];
|
|
193
|
+
|
|
194
|
+
const codexModels = (PROVIDER_MODELS.cx || [])
|
|
195
|
+
.filter((model) => !model.type || model.type === "llm")
|
|
196
|
+
.map((model) => model.id);
|
|
197
|
+
|
|
198
|
+
const entries = [
|
|
199
|
+
{ id: "auto-codex", object: "model", owned_by: "9router-codex-gateway" },
|
|
200
|
+
...codexModels.flatMap((model) => [
|
|
201
|
+
{ id: `router/${model}`, object: "model", owned_by: "9router-codex-gateway" },
|
|
202
|
+
{ id: `original/${model}`, object: "model", owned_by: "9router-codex-gateway" },
|
|
203
|
+
]),
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const connection of connections.filter((conn) => conn.provider === "codex" && conn.isActive !== false)) {
|
|
207
|
+
entries.push({
|
|
208
|
+
id: `account/${slugifyCodexAccount(connection)}`,
|
|
209
|
+
object: "model",
|
|
210
|
+
owned_by: "9router-codex-account",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return entries;
|
|
215
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Re-export from open-sse with localDb integration
|
|
2
|
+
import { getModelAliases, getComboByName, getProviderNodes } from "@/lib/localDb";
|
|
3
|
+
import { parseModel as parseModelCore, resolveModelAliasFromMap, getModelInfoCore } from "open-sse/services/model.js";
|
|
4
|
+
import { parseCodexGatewayModel } from "./codexGateway.js";
|
|
5
|
+
|
|
6
|
+
// Local provider alias overrides (HMR-friendly, applied on top of open-sse map)
|
|
7
|
+
const LOCAL_PROVIDER_ALIASES = {
|
|
8
|
+
xmtp: "xiaomi-tokenplan",
|
|
9
|
+
"xiaomi-tokenplan": "xiaomi-tokenplan",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function parseModel(modelStr) {
|
|
13
|
+
const parsed = parseModelCore(modelStr);
|
|
14
|
+
if (parsed?.providerAlias && LOCAL_PROVIDER_ALIASES[parsed.providerAlias]) {
|
|
15
|
+
return { ...parsed, provider: LOCAL_PROVIDER_ALIASES[parsed.providerAlias] };
|
|
16
|
+
}
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve model alias from localDb
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveModelAlias(alias) {
|
|
24
|
+
const aliases = await getModelAliases();
|
|
25
|
+
return resolveModelAliasFromMap(alias, aliases);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get full model info (parse or resolve)
|
|
30
|
+
*/
|
|
31
|
+
export async function getModelInfo(modelStr) {
|
|
32
|
+
const gateway = parseCodexGatewayModel(modelStr);
|
|
33
|
+
if (gateway) {
|
|
34
|
+
const resolved = await getModelInfo(gateway.modelString);
|
|
35
|
+
return {
|
|
36
|
+
...resolved,
|
|
37
|
+
gateway: {
|
|
38
|
+
mode: gateway.mode,
|
|
39
|
+
accountRef: gateway.accountRef || null,
|
|
40
|
+
strictAccount: !!gateway.strictAccount,
|
|
41
|
+
requestedModel: modelStr,
|
|
42
|
+
modelString: gateway.modelString,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parsed = parseModel(modelStr);
|
|
48
|
+
|
|
49
|
+
if (!parsed.isAlias) {
|
|
50
|
+
// Always check provider-node prefix matching using original input first
|
|
51
|
+
const openaiNodes = await getProviderNodes({ type: "openai-compatible" });
|
|
52
|
+
const matchedOpenAI = openaiNodes.find((node) => node.prefix === parsed.providerAlias);
|
|
53
|
+
if (matchedOpenAI) {
|
|
54
|
+
return { provider: matchedOpenAI.id, model: parsed.model };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const anthropicNodes = await getProviderNodes({ type: "anthropic-compatible" });
|
|
58
|
+
const matchedAnthropic = anthropicNodes.find((node) => node.prefix === parsed.providerAlias);
|
|
59
|
+
if (matchedAnthropic) {
|
|
60
|
+
return { provider: matchedAnthropic.id, model: parsed.model };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const embeddingNodes = await getProviderNodes({ type: "custom-embedding" });
|
|
64
|
+
const matchedEmbedding = embeddingNodes.find((node) => node.prefix === parsed.providerAlias);
|
|
65
|
+
if (matchedEmbedding) {
|
|
66
|
+
return { provider: matchedEmbedding.id, model: parsed.model };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
provider: parsed.provider,
|
|
70
|
+
model: parsed.model
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if this is a combo name before resolving as alias
|
|
75
|
+
// This prevents combo names from being incorrectly routed to providers
|
|
76
|
+
const combo = await getComboByName(parsed.model);
|
|
77
|
+
if (combo) {
|
|
78
|
+
// Return null provider to signal this should be handled as combo
|
|
79
|
+
// The caller (handleChat) will detect this and handle it as combo
|
|
80
|
+
return { provider: null, model: parsed.model };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return getModelInfoCore(modelStr, getModelAliases);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if model is a combo and get models list
|
|
88
|
+
* @returns {Promise<string[]|null>} Array of models or null if not a combo
|
|
89
|
+
*/
|
|
90
|
+
export async function getComboModels(modelStr) {
|
|
91
|
+
// Only check if it's not in provider/model format
|
|
92
|
+
if (modelStr.includes("/")) return null;
|
|
93
|
+
|
|
94
|
+
const combo = await getComboByName(modelStr);
|
|
95
|
+
if (combo && combo.models && combo.models.length > 0) {
|
|
96
|
+
return combo.models;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|