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,238 @@
|
|
|
1
|
+
import { ERROR_RULES, BACKOFF_CONFIG, TRANSIENT_COOLDOWN_MS } from "../config/errorConfig.js";
|
|
2
|
+
|
|
3
|
+
const NON_ACCOUNT_ERROR_TEXTS = [
|
|
4
|
+
"content_length_exceeds_threshold",
|
|
5
|
+
"input is too long",
|
|
6
|
+
"maximum context length",
|
|
7
|
+
"context length exceeds",
|
|
8
|
+
"prompt is too long",
|
|
9
|
+
"request too large",
|
|
10
|
+
"improperly formed request",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Errors caused by the request payload itself should not rotate accounts.
|
|
15
|
+
* Retrying the same oversized payload with another credential only amplifies
|
|
16
|
+
* logs and temporarily locks healthy accounts.
|
|
17
|
+
*/
|
|
18
|
+
export function isNonAccountError(status, errorText) {
|
|
19
|
+
if (status === 413) return true;
|
|
20
|
+
const lowerError = errorText
|
|
21
|
+
? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase()
|
|
22
|
+
: "";
|
|
23
|
+
return status === 400 && NON_ACCOUNT_ERROR_TEXTS.some(text => lowerError.includes(text));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculate exponential backoff cooldown for rate limits (429)
|
|
28
|
+
* Level 1: 1s, Level 2: 2s, Level 3: 4s... → max 4 min
|
|
29
|
+
* @param {number} backoffLevel - Current backoff level
|
|
30
|
+
* @returns {number} Cooldown in milliseconds
|
|
31
|
+
*/
|
|
32
|
+
export function getQuotaCooldown(backoffLevel = 0) {
|
|
33
|
+
const level = Math.max(0, backoffLevel - 1);
|
|
34
|
+
const cooldown = BACKOFF_CONFIG.base * Math.pow(2, level);
|
|
35
|
+
return Math.min(cooldown, BACKOFF_CONFIG.max);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if error should trigger account fallback (switch to next account)
|
|
40
|
+
* Config-driven: matches ERROR_RULES top-to-bottom (text rules first, then status)
|
|
41
|
+
* @param {number} status - HTTP status code
|
|
42
|
+
* @param {string} errorText - Error message text
|
|
43
|
+
* @param {number} backoffLevel - Current backoff level for exponential backoff
|
|
44
|
+
* @returns {{ shouldFallback: boolean, cooldownMs: number, newBackoffLevel?: number }}
|
|
45
|
+
*/
|
|
46
|
+
export function checkFallbackError(status, errorText, backoffLevel = 0) {
|
|
47
|
+
const lowerError = errorText
|
|
48
|
+
? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase()
|
|
49
|
+
: "";
|
|
50
|
+
|
|
51
|
+
for (const rule of ERROR_RULES) {
|
|
52
|
+
// Text-based rule: match substring in error message
|
|
53
|
+
if (rule.text && lowerError && lowerError.includes(rule.text)) {
|
|
54
|
+
if (rule.backoff) {
|
|
55
|
+
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
|
|
56
|
+
return { shouldFallback: true, cooldownMs: getQuotaCooldown(newLevel), newBackoffLevel: newLevel };
|
|
57
|
+
}
|
|
58
|
+
return { shouldFallback: true, cooldownMs: rule.cooldownMs };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Status-based rule: match HTTP status code
|
|
62
|
+
if (rule.status && rule.status === status) {
|
|
63
|
+
if (rule.backoff) {
|
|
64
|
+
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
|
|
65
|
+
return { shouldFallback: true, cooldownMs: getQuotaCooldown(newLevel), newBackoffLevel: newLevel };
|
|
66
|
+
}
|
|
67
|
+
return { shouldFallback: true, cooldownMs: rule.cooldownMs };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Default: transient cooldown for any unmatched error
|
|
72
|
+
return { shouldFallback: true, cooldownMs: TRANSIENT_COOLDOWN_MS };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if account is currently unavailable (cooldown not expired)
|
|
77
|
+
*/
|
|
78
|
+
export function isAccountUnavailable(unavailableUntil) {
|
|
79
|
+
if (!unavailableUntil) return false;
|
|
80
|
+
return new Date(unavailableUntil).getTime() > Date.now();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Calculate unavailable until timestamp
|
|
85
|
+
*/
|
|
86
|
+
export function getUnavailableUntil(cooldownMs) {
|
|
87
|
+
return new Date(Date.now() + cooldownMs).toISOString();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the earliest rateLimitedUntil from a list of accounts
|
|
92
|
+
* @param {Array} accounts - Array of account objects with rateLimitedUntil
|
|
93
|
+
* @returns {string|null} Earliest rateLimitedUntil ISO string, or null
|
|
94
|
+
*/
|
|
95
|
+
export function getEarliestRateLimitedUntil(accounts) {
|
|
96
|
+
let earliest = null;
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
for (const acc of accounts) {
|
|
99
|
+
if (!acc.rateLimitedUntil) continue;
|
|
100
|
+
const until = new Date(acc.rateLimitedUntil).getTime();
|
|
101
|
+
if (until <= now) continue;
|
|
102
|
+
if (!earliest || until < earliest) earliest = until;
|
|
103
|
+
}
|
|
104
|
+
if (!earliest) return null;
|
|
105
|
+
return new Date(earliest).toISOString();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Format rateLimitedUntil to human-readable "reset after Xm Ys"
|
|
110
|
+
* @param {string} rateLimitedUntil - ISO timestamp
|
|
111
|
+
* @returns {string} e.g. "reset after 2m 30s"
|
|
112
|
+
*/
|
|
113
|
+
export function formatRetryAfter(rateLimitedUntil) {
|
|
114
|
+
if (!rateLimitedUntil) return "";
|
|
115
|
+
const diffMs = new Date(rateLimitedUntil).getTime() - Date.now();
|
|
116
|
+
if (diffMs <= 0) return "reset after 0s";
|
|
117
|
+
const totalSec = Math.ceil(diffMs / 1000);
|
|
118
|
+
const h = Math.floor(totalSec / 3600);
|
|
119
|
+
const m = Math.floor((totalSec % 3600) / 60);
|
|
120
|
+
const s = totalSec % 60;
|
|
121
|
+
const parts = [];
|
|
122
|
+
if (h > 0) parts.push(`${h}h`);
|
|
123
|
+
if (m > 0) parts.push(`${m}m`);
|
|
124
|
+
if (s > 0 || parts.length === 0) parts.push(`${s}s`);
|
|
125
|
+
return `reset after ${parts.join(" ")}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Prefix for model lock flat fields on connection record */
|
|
129
|
+
export const MODEL_LOCK_PREFIX = "modelLock_";
|
|
130
|
+
|
|
131
|
+
/** Special key used when no model is known (account-level lock) */
|
|
132
|
+
export const MODEL_LOCK_ALL = `${MODEL_LOCK_PREFIX}__all`;
|
|
133
|
+
|
|
134
|
+
/** Build the flat field key for a model lock */
|
|
135
|
+
export function getModelLockKey(model) {
|
|
136
|
+
return model ? `${MODEL_LOCK_PREFIX}${model}` : MODEL_LOCK_ALL;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a model lock on a connection is still active.
|
|
141
|
+
* Reads flat field `modelLock_${model}` (or `modelLock___all` when model=null).
|
|
142
|
+
*/
|
|
143
|
+
export function isModelLockActive(connection, model) {
|
|
144
|
+
const key = getModelLockKey(model);
|
|
145
|
+
const expiry = connection[key] || connection[MODEL_LOCK_ALL];
|
|
146
|
+
if (!expiry) return false;
|
|
147
|
+
return new Date(expiry).getTime() > Date.now();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get earliest active model lock expiry across all modelLock_* fields.
|
|
152
|
+
* Used for UI cooldown display.
|
|
153
|
+
*/
|
|
154
|
+
export function getEarliestModelLockUntil(connection) {
|
|
155
|
+
if (!connection) return null;
|
|
156
|
+
let earliest = null;
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
for (const [key, val] of Object.entries(connection)) {
|
|
159
|
+
if (!key.startsWith(MODEL_LOCK_PREFIX) || !val) continue;
|
|
160
|
+
const t = new Date(val).getTime();
|
|
161
|
+
if (t <= now) continue;
|
|
162
|
+
if (!earliest || t < earliest) earliest = t;
|
|
163
|
+
}
|
|
164
|
+
return earliest ? new Date(earliest).toISOString() : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build update object to set a model lock on a connection.
|
|
169
|
+
*/
|
|
170
|
+
export function buildModelLockUpdate(model, cooldownMs) {
|
|
171
|
+
const key = getModelLockKey(model);
|
|
172
|
+
return { [key]: new Date(Date.now() + cooldownMs).toISOString() };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build update object to clear all model locks on a connection.
|
|
177
|
+
*/
|
|
178
|
+
export function buildClearModelLocksUpdate(connection) {
|
|
179
|
+
const cleared = {};
|
|
180
|
+
for (const key of Object.keys(connection)) {
|
|
181
|
+
if (key.startsWith(MODEL_LOCK_PREFIX)) cleared[key] = null;
|
|
182
|
+
}
|
|
183
|
+
return cleared;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Filter available accounts (not in cooldown)
|
|
188
|
+
*/
|
|
189
|
+
export function filterAvailableAccounts(accounts, excludeId = null) {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
return accounts.filter(acc => {
|
|
192
|
+
if (excludeId && acc.id === excludeId) return false;
|
|
193
|
+
if (acc.rateLimitedUntil) {
|
|
194
|
+
const until = new Date(acc.rateLimitedUntil).getTime();
|
|
195
|
+
if (until > now) return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Reset account state when request succeeds
|
|
203
|
+
* Clears cooldown and resets backoff level to 0
|
|
204
|
+
* @param {object} account - Account object
|
|
205
|
+
* @returns {object} Updated account with reset state
|
|
206
|
+
*/
|
|
207
|
+
export function resetAccountState(account) {
|
|
208
|
+
if (!account) return account;
|
|
209
|
+
return {
|
|
210
|
+
...account,
|
|
211
|
+
rateLimitedUntil: null,
|
|
212
|
+
backoffLevel: 0,
|
|
213
|
+
lastError: null,
|
|
214
|
+
status: "active"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Apply error state to account
|
|
220
|
+
* @param {object} account - Account object
|
|
221
|
+
* @param {number} status - HTTP status code
|
|
222
|
+
* @param {string} errorText - Error message
|
|
223
|
+
* @returns {object} Updated account with error state
|
|
224
|
+
*/
|
|
225
|
+
export function applyErrorState(account, status, errorText) {
|
|
226
|
+
if (!account) return account;
|
|
227
|
+
|
|
228
|
+
const backoffLevel = account.backoffLevel || 0;
|
|
229
|
+
const { cooldownMs, newBackoffLevel } = checkFallbackError(status, errorText, backoffLevel);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
...account,
|
|
233
|
+
rateLimitedUntil: cooldownMs > 0 ? getUnavailableUntil(cooldownMs) : null,
|
|
234
|
+
backoffLevel: newBackoffLevel ?? backoffLevel,
|
|
235
|
+
lastError: { status, message: errorText, timestamp: new Date().toISOString() },
|
|
236
|
+
status: "error"
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared combo (model combo) handling with fallback support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { checkFallbackError, formatRetryAfter } from "./accountFallback.js";
|
|
6
|
+
import { unavailableResponse } from "../utils/error.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Track rotation state per combo (for round-robin strategy)
|
|
10
|
+
* @type {Map<string, { index: number, consecutiveUseCount: number }>}
|
|
11
|
+
*/
|
|
12
|
+
const comboRotationState = new Map();
|
|
13
|
+
|
|
14
|
+
function normalizeStickyLimit(stickyLimit) {
|
|
15
|
+
const parsed = Number.parseInt(stickyLimit, 10);
|
|
16
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function rotateModelsFromIndex(models, currentIndex) {
|
|
20
|
+
const rotatedModels = [...models];
|
|
21
|
+
for (let i = 0; i < currentIndex; i++) {
|
|
22
|
+
const moved = rotatedModels.shift();
|
|
23
|
+
rotatedModels.push(moved);
|
|
24
|
+
}
|
|
25
|
+
return rotatedModels;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get rotated model list based on strategy
|
|
30
|
+
* @param {string[]} models - Array of model strings
|
|
31
|
+
* @param {string} comboName - Name of the combo
|
|
32
|
+
* @param {string} strategy - "fallback" or "round-robin"
|
|
33
|
+
* @param {number|string} [stickyLimit=1] - Requests per combo model before switching
|
|
34
|
+
* @returns {string[]} Rotated models array
|
|
35
|
+
*/
|
|
36
|
+
export function getRotatedModels(models, comboName, strategy, stickyLimit = 1) {
|
|
37
|
+
if (!models || models.length <= 1 || strategy !== "round-robin") {
|
|
38
|
+
return models;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rotationKey = comboName || "__default__";
|
|
42
|
+
const normalizedStickyLimit = normalizeStickyLimit(stickyLimit);
|
|
43
|
+
const existingState = comboRotationState.get(rotationKey);
|
|
44
|
+
const state = typeof existingState === "number"
|
|
45
|
+
? { index: existingState, consecutiveUseCount: 0 }
|
|
46
|
+
: (existingState || { index: 0, consecutiveUseCount: 0 });
|
|
47
|
+
|
|
48
|
+
const currentIndex = state.index % models.length;
|
|
49
|
+
const rotatedModels = rotateModelsFromIndex(models, currentIndex);
|
|
50
|
+
const nextUseCount = state.consecutiveUseCount + 1;
|
|
51
|
+
|
|
52
|
+
if (nextUseCount >= normalizedStickyLimit) {
|
|
53
|
+
comboRotationState.set(rotationKey, {
|
|
54
|
+
index: (currentIndex + 1) % models.length,
|
|
55
|
+
consecutiveUseCount: 0,
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
comboRotationState.set(rotationKey, {
|
|
59
|
+
index: currentIndex,
|
|
60
|
+
consecutiveUseCount: nextUseCount,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return rotatedModels;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reset in-memory rotation state when combo/settings change
|
|
69
|
+
* @param {string} [comboName] - Combo name to reset; omit to clear all
|
|
70
|
+
*/
|
|
71
|
+
export function resetComboRotation(comboName) {
|
|
72
|
+
if (comboName) comboRotationState.delete(comboName);
|
|
73
|
+
else comboRotationState.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get combo models from combos data
|
|
78
|
+
* @param {string} modelStr - Model string to check
|
|
79
|
+
* @param {Array|Object} combosData - Array of combos or object with combos
|
|
80
|
+
* @returns {string[]|null} Array of models or null if not a combo
|
|
81
|
+
*/
|
|
82
|
+
export function getComboModelsFromData(modelStr, combosData) {
|
|
83
|
+
// Don't check if it's in provider/model format
|
|
84
|
+
if (modelStr.includes("/")) return null;
|
|
85
|
+
|
|
86
|
+
// Handle both array and object formats
|
|
87
|
+
const combos = Array.isArray(combosData) ? combosData : (combosData?.combos || []);
|
|
88
|
+
|
|
89
|
+
const combo = combos.find(c => c.name === modelStr);
|
|
90
|
+
if (combo && combo.models && combo.models.length > 0) {
|
|
91
|
+
return combo.models;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Handle combo chat with fallback
|
|
98
|
+
* @param {Object} options
|
|
99
|
+
* @param {Object} options.body - Request body
|
|
100
|
+
* @param {string[]} options.models - Array of model strings to try
|
|
101
|
+
* @param {Function} options.handleSingleModel - Function to handle single model: (body, modelStr) => Promise<Response>
|
|
102
|
+
* @param {Object} options.log - Logger object
|
|
103
|
+
* @param {string} [options.comboName] - Name of the combo (for round-robin tracking)
|
|
104
|
+
* @param {string} [options.comboStrategy] - Strategy: "fallback" or "round-robin"
|
|
105
|
+
* @param {number|string} [options.comboStickyLimit=1] - Requests per combo model before switching
|
|
106
|
+
* @returns {Promise<Response>}
|
|
107
|
+
*/
|
|
108
|
+
export async function handleComboChat({ body, models, handleSingleModel, log, comboName, comboStrategy, comboStickyLimit = 1 }) {
|
|
109
|
+
// Apply rotation strategy if enabled
|
|
110
|
+
const rotatedModels = getRotatedModels(models, comboName, comboStrategy, comboStickyLimit);
|
|
111
|
+
|
|
112
|
+
let lastError = null;
|
|
113
|
+
let earliestRetryAfter = null;
|
|
114
|
+
let lastStatus = null;
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < rotatedModels.length; i++) {
|
|
117
|
+
const modelStr = rotatedModels[i];
|
|
118
|
+
log.info("COMBO", `Trying model ${i + 1}/${rotatedModels.length}: ${modelStr}`);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await handleSingleModel(body, modelStr);
|
|
122
|
+
|
|
123
|
+
// Success (2xx) - return response
|
|
124
|
+
if (result.ok) {
|
|
125
|
+
log.info("COMBO", `Model ${modelStr} succeeded`);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Extract error info from response
|
|
130
|
+
let errorText = result.statusText || "";
|
|
131
|
+
let retryAfter = null;
|
|
132
|
+
try {
|
|
133
|
+
const errorBody = await result.clone().json();
|
|
134
|
+
errorText = errorBody?.error?.message || errorBody?.error || errorBody?.message || errorText;
|
|
135
|
+
retryAfter = errorBody?.retryAfter || null;
|
|
136
|
+
} catch {
|
|
137
|
+
// Ignore JSON parse errors
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Track earliest retryAfter across all combo models
|
|
141
|
+
if (retryAfter && (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter))) {
|
|
142
|
+
earliestRetryAfter = retryAfter;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Normalize error text to string (Worker-safe)
|
|
146
|
+
if (typeof errorText !== "string") {
|
|
147
|
+
try { errorText = JSON.stringify(errorText); } catch { errorText = String(errorText); }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check if should fallback to next model
|
|
151
|
+
const { shouldFallback, cooldownMs } = checkFallbackError(result.status, errorText);
|
|
152
|
+
|
|
153
|
+
if (!shouldFallback) {
|
|
154
|
+
log.warn("COMBO", `Model ${modelStr} failed (no fallback)`, { status: result.status });
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// For transient errors (503/502/504), wait for cooldown before falling through
|
|
159
|
+
// so a briefly-overloaded provider gets a chance to recover rather than being
|
|
160
|
+
// skipped immediately (fixes: combo falls through on transient 503)
|
|
161
|
+
if (cooldownMs && cooldownMs > 0 && cooldownMs <= 5000 &&
|
|
162
|
+
(result.status === 503 || result.status === 502 || result.status === 504)) {
|
|
163
|
+
log.info("COMBO", `Model ${modelStr} transient ${result.status}, waiting ${cooldownMs}ms before next`);
|
|
164
|
+
await new Promise(r => setTimeout(r, cooldownMs));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fallback to next model
|
|
168
|
+
lastError = errorText || String(result.status);
|
|
169
|
+
if (!lastStatus) lastStatus = result.status;
|
|
170
|
+
log.warn("COMBO", `Model ${modelStr} failed, trying next`, { status: result.status });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Catch unexpected exceptions to ensure fallback continues
|
|
173
|
+
lastError = error.message || String(error);
|
|
174
|
+
if (!lastStatus) lastStatus = 500;
|
|
175
|
+
log.warn("COMBO", `Model ${modelStr} threw error, trying next`, { error: lastError });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// All models failed
|
|
180
|
+
// Use 503 (Service Unavailable) rather than 406 (Not Acceptable) — 406 implies
|
|
181
|
+
// the request itself is invalid, but here the providers are simply unavailable
|
|
182
|
+
// or have no active credentials. 503 is more accurate and retryable by clients.
|
|
183
|
+
const allDisabled = lastError && lastError.toLowerCase().includes("no credentials");
|
|
184
|
+
const status = allDisabled ? 503 : (lastStatus || 503);
|
|
185
|
+
const msg = lastError || "All combo models unavailable";
|
|
186
|
+
|
|
187
|
+
if (earliestRetryAfter) {
|
|
188
|
+
const retryHuman = formatRetryAfter(earliestRetryAfter);
|
|
189
|
+
log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
|
|
190
|
+
return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
log.warn("COMBO", `All models failed | ${msg}`);
|
|
194
|
+
return new Response(
|
|
195
|
+
JSON.stringify({ error: { message: msg } }),
|
|
196
|
+
{ status, headers: { "Content-Type": "application/json" } }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared combo (model combo) handling with fallback support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get combo models from combos data
|
|
7
|
+
* @param {string} modelStr - Model string to check
|
|
8
|
+
* @param {Array|Object} combosData - Array of combos or object with combos
|
|
9
|
+
* @returns {string[]|null} Array of models or null if not a combo
|
|
10
|
+
*/
|
|
11
|
+
export function getComboModelsFromData(modelStr, combosData) {
|
|
12
|
+
// Don't check if it's in provider/model format
|
|
13
|
+
if (modelStr.includes("/")) return null;
|
|
14
|
+
|
|
15
|
+
// Handle both array and object formats
|
|
16
|
+
const combos = Array.isArray(combosData) ? combosData : (combosData?.combos || []);
|
|
17
|
+
|
|
18
|
+
const combo = combos.find(c => c.name === modelStr);
|
|
19
|
+
if (combo && combo.models && combo.models.length > 0) {
|
|
20
|
+
return combo.models;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handle combo chat with fallback
|
|
27
|
+
* @param {Object} options
|
|
28
|
+
* @param {Object} options.body - Request body
|
|
29
|
+
* @param {string[]} options.models - Array of model strings to try
|
|
30
|
+
* @param {Function} options.handleSingleModel - Function to handle single model: (body, modelStr) => Promise<Response>
|
|
31
|
+
* @param {Object} options.log - Logger object
|
|
32
|
+
* @returns {Promise<Response>}
|
|
33
|
+
*/
|
|
34
|
+
export async function handleComboChat({ body, models, handleSingleModel, log }) {
|
|
35
|
+
let lastError = null;
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < models.length; i++) {
|
|
38
|
+
const modelStr = models[i];
|
|
39
|
+
log.info("COMBO", `Trying model ${i + 1}/${models.length}: ${modelStr}`);
|
|
40
|
+
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await handleSingleModel(body, modelStr);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
lastError = `${modelStr}: ${e.message}`;
|
|
46
|
+
log.warn("COMBO", `Model threw exception, trying next`, { model: modelStr, error: e.message });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Success or client error - return response
|
|
51
|
+
if (result.ok || result.status < 500) {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 5xx error - try next model
|
|
56
|
+
lastError = `${modelStr}: ${result.statusText || result.status}`;
|
|
57
|
+
log.warn("COMBO", `Model failed, trying next`, { model: modelStr, status: result.status });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.warn("COMBO", "All models failed");
|
|
61
|
+
|
|
62
|
+
// Return 503 with last error
|
|
63
|
+
return new Response(
|
|
64
|
+
JSON.stringify({ error: lastError || "All combo models unavailable" }),
|
|
65
|
+
{
|
|
66
|
+
status: 503,
|
|
67
|
+
headers: { "Content-Type": "application/json" }
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|