@fudrouter/fsrouter 0.6.62 → 0.6.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/copyJs.js +0 -4
- package/dist/lib/oauth/constants/oauth.js +2 -2
- package/dist/lib/oauth/providers.js +1 -1
- package/dist/routes/providers/[id]/test/testUtils.js +3 -3
- package/dist/shared/constants/models.js +2 -2
- package/dist/sse/handlers/chat.js +10 -10
- package/dist/sse/handlers/embeddings.js +3 -3
- package/dist/sse/handlers/fetch.js +4 -4
- package/dist/sse/handlers/imageGeneration.js +5 -5
- package/dist/sse/handlers/search.js +4 -4
- package/dist/sse/handlers/stt.js +3 -3
- package/dist/sse/handlers/tts.js +4 -4
- package/dist/sse/handlers/videoGeneration.js +4 -4
- package/dist/sse/services/auth.js +2 -2
- package/dist/sse/services/model.js +1 -1
- package/dist/sse/services/tokenRefresh.js +3 -3
- package/open-sse/AGENTS.md +39 -0
- package/open-sse/config/anthropicHeaders.js +87 -0
- package/open-sse/config/appConstants.js +238 -0
- package/open-sse/config/audioRegistry.js +506 -0
- package/open-sse/config/cliFingerprints.js +304 -0
- package/open-sse/config/codexInstructions.js +119 -0
- package/open-sse/config/codexQuotaScopes.js +42 -0
- package/open-sse/config/constants.js +4 -0
- package/open-sse/config/contextEditing.js +68 -0
- package/open-sse/config/credentialLoader.js +83 -0
- package/open-sse/config/defaultThinkingSignature.js +12 -0
- package/open-sse/config/embeddingRegistry.js +264 -0
- package/open-sse/config/errorConfig.js +96 -0
- package/open-sse/config/freeModelCatalog.data.js +121 -0
- package/open-sse/config/freeModelCatalog.js +90 -0
- package/open-sse/config/freeTierCatalog.js +81 -0
- package/open-sse/config/googleTtsLanguages.js +62 -0
- package/open-sse/config/imageParamDefs.js +360 -0
- package/open-sse/config/imageRegistry.js +493 -0
- package/open-sse/config/kiroConstants.js +268 -0
- package/open-sse/config/mediaConfig.js +27 -0
- package/open-sse/config/mediaServiceKinds.js +71 -0
- package/open-sse/config/models.js +13 -0
- package/open-sse/config/moderationRegistry.js +71 -0
- package/open-sse/config/musicRegistry.js +99 -0
- package/open-sse/config/ocrRegistry.js +61 -0
- package/open-sse/config/ollamaModels.js +19 -0
- package/open-sse/config/providerErrorRules.js +114 -0
- package/open-sse/config/providerFieldStrips.js +35 -0
- package/open-sse/config/providerHeaderProfiles.js +191 -0
- package/open-sse/config/providerModels.js +106 -0
- package/open-sse/config/providerRegistry.js +16 -0
- package/open-sse/config/providers.js +19 -0
- package/open-sse/config/registryUtils.js +106 -0
- package/open-sse/config/rerankRegistry.js +177 -0
- package/open-sse/config/runtimeConfig.js +96 -0
- package/open-sse/config/runway.js +39 -0
- package/open-sse/config/searchRegistry.js +153 -0
- package/open-sse/config/toolCloaking.js +199 -0
- package/open-sse/config/ttsModels.js +129 -0
- package/open-sse/config/videoRegistry.js +207 -0
- package/open-sse/executors/adapta-web.js +435 -0
- package/open-sse/executors/antigravity.js +534 -0
- package/open-sse/executors/antigravityUpstreamError.js +13 -0
- package/open-sse/executors/auggie.js +437 -0
- package/open-sse/executors/azure-openai.js +32 -0
- package/open-sse/executors/azure.js +57 -0
- package/open-sse/executors/base.js +181 -0
- package/open-sse/executors/bedrock.js +633 -0
- package/open-sse/executors/blackbox-web.js +578 -0
- package/open-sse/executors/chatgpt-web.js +2042 -0
- package/open-sse/executors/chipotle.js +357 -0
- package/open-sse/executors/claude-web-with-auto-refresh.js +73 -0
- package/open-sse/executors/claude-web.js +586 -0
- package/open-sse/executors/cliproxyapi.js +296 -0
- package/open-sse/executors/cloudflare-ai.js +46 -0
- package/open-sse/executors/codebuddy-cn.js +40 -0
- package/open-sse/executors/codex.js +1324 -0
- package/open-sse/executors/commandCode.js +558 -0
- package/open-sse/executors/commandcode.js +88 -0
- package/open-sse/executors/copilot-m365-connection.js +176 -0
- package/open-sse/executors/copilot-m365-frames.js +192 -0
- package/open-sse/executors/copilot-m365-web.js +299 -0
- package/open-sse/executors/copilot-web.js +580 -0
- package/open-sse/executors/cursor.js +795 -0
- package/open-sse/executors/deepseek-web-with-auto-refresh.js +150 -0
- package/open-sse/executors/deepseek-web.js +875 -0
- package/open-sse/executors/default.js +430 -0
- package/open-sse/executors/devin-cli.js +351 -0
- package/open-sse/executors/doubao-web.js +550 -0
- package/open-sse/executors/duckduckgo-web.js +629 -0
- package/open-sse/executors/forceResponsesUpstream.js +33 -0
- package/open-sse/executors/gemini-business.js +320 -0
- package/open-sse/executors/gemini-cli.js +89 -0
- package/open-sse/executors/gemini-web.js +297 -0
- package/open-sse/executors/github.js +379 -0
- package/open-sse/executors/gitlab.js +569 -0
- package/open-sse/executors/glm.js +361 -0
- package/open-sse/executors/grok-cli.js +119 -0
- package/open-sse/executors/grok-web.js +345 -0
- package/open-sse/executors/huggingchat.js +467 -0
- package/open-sse/executors/iflow.js +108 -0
- package/open-sse/executors/index.js +78 -0
- package/open-sse/executors/inner-ai.js +527 -0
- package/open-sse/executors/kimchi.js +123 -0
- package/open-sse/executors/kimi-web.js +325 -0
- package/open-sse/executors/kimi.js +97 -0
- package/open-sse/executors/kiro.js +503 -0
- package/open-sse/executors/kiroThinking.js +44 -0
- package/open-sse/executors/lmarena.js +395 -0
- package/open-sse/executors/mimo-free.js +167 -0
- package/open-sse/executors/mimocode.js +392 -0
- package/open-sse/executors/ninerouter.js +155 -0
- package/open-sse/executors/nlpcloud.js +438 -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 +581 -0
- package/open-sse/executors/poe-web.js +133 -0
- package/open-sse/executors/pollinations.js +95 -0
- package/open-sse/executors/puter.js +31 -0
- package/open-sse/executors/qoder.js +448 -0
- package/open-sse/executors/qwen-web.js +372 -0
- package/open-sse/executors/qwen.js +129 -0
- package/open-sse/executors/t3-chat-web.js +417 -0
- package/open-sse/executors/theoldllm.js +290 -0
- package/open-sse/executors/trae.js +418 -0
- package/open-sse/executors/v0-vercel-web.js +141 -0
- package/open-sse/executors/venice-web.js +142 -0
- package/open-sse/executors/vertex.js +131 -0
- package/open-sse/executors/vertexMedia.js +236 -0
- package/open-sse/executors/windsurf.js +560 -0
- package/open-sse/executors/xai.js +46 -0
- package/open-sse/executors/xiaomi-tokenplan.js +19 -0
- package/open-sse/executors/yuanbao-web.js +385 -0
- package/open-sse/executors/zed-hosted.js +281 -0
- package/open-sse/executors/zenmux-free.js +247 -0
- package/open-sse/handlers/audioSpeech.js +759 -0
- package/open-sse/handlers/audioTranscription.js +395 -0
- package/open-sse/handlers/audioTranslation.js +181 -0
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +300 -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 +95 -0
- package/open-sse/handlers/chatCore.js +404 -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 +230 -0
- package/open-sse/handlers/imageProviders/_base.js +31 -0
- package/open-sse/handlers/imageProviders/antigravity.js +73 -0
- package/open-sse/handlers/imageProviders/blackForestLabs.js +63 -0
- package/open-sse/handlers/imageProviders/cloudflareAi.js +207 -0
- package/open-sse/handlers/imageProviders/codex.js +217 -0
- package/open-sse/handlers/imageProviders/comfyui.js +25 -0
- package/open-sse/handlers/imageProviders/falAi.js +62 -0
- package/open-sse/handlers/imageProviders/gemini.js +40 -0
- package/open-sse/handlers/imageProviders/huggingface.js +38 -0
- package/open-sse/handlers/imageProviders/index.js +44 -0
- package/open-sse/handlers/imageProviders/leonardo.js +435 -0
- package/open-sse/handlers/imageProviders/nanobanana.js +79 -0
- package/open-sse/handlers/imageProviders/openai.js +61 -0
- package/open-sse/handlers/imageProviders/runwayml.js +72 -0
- package/open-sse/handlers/imageProviders/sdwebui.js +36 -0
- package/open-sse/handlers/imageProviders/stabilityAi.js +59 -0
- package/open-sse/handlers/imageProviders/weavy.js +222 -0
- package/open-sse/handlers/moderations.js +92 -0
- package/open-sse/handlers/ocr.js +89 -0
- package/open-sse/handlers/rerank.js +223 -0
- package/open-sse/handlers/responseSanitizer/reasoning.js +144 -0
- package/open-sse/handlers/responsesHandler.js +99 -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/search.js +1056 -0
- package/open-sse/handlers/sseParser.js +469 -0
- package/open-sse/handlers/sttCore.js +193 -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/handlers/usageExtractor.js +93 -0
- package/open-sse/handlers/videoGenerationCore.js +119 -0
- package/open-sse/handlers/videoProviders/index.js +22 -0
- package/open-sse/handlers/videoProviders/leonardo.js +511 -0
- package/open-sse/handlers/videoProviders/weavy.js +198 -0
- package/open-sse/handlers/webFetch.js +200 -0
- package/open-sse/index.js +79 -0
- package/open-sse/mcp-server/audit.js +283 -0
- package/open-sse/mcp-server/catalog.js +184 -0
- package/open-sse/mcp-server/descriptionCompressor.js +175 -0
- package/open-sse/mcp-server/httpAuthContext.js +32 -0
- package/open-sse/mcp-server/httpTransport.js +229 -0
- package/open-sse/mcp-server/index.js +33 -0
- package/open-sse/mcp-server/mcpCallerIdentity.js +21 -0
- package/open-sse/mcp-server/runtimeHeartbeat.js +110 -0
- package/open-sse/mcp-server/schemas/a2a.js +153 -0
- package/open-sse/mcp-server/schemas/audit.js +17 -0
- package/open-sse/mcp-server/schemas/index.js +166 -0
- package/open-sse/mcp-server/schemas/pickFastestModel.js +90 -0
- package/open-sse/mcp-server/schemas/toolDefinition.js +0 -0
- package/open-sse/mcp-server/schemas/toolSearch.js +32 -0
- package/open-sse/mcp-server/schemas/tools.js +1219 -0
- package/open-sse/mcp-server/scopeEnforcement.js +80 -0
- package/open-sse/mcp-server/server.js +1168 -0
- package/open-sse/mcp-server/toolCardinality.js +78 -0
- package/open-sse/mcp-server/toolSearch/catalog.js +66 -0
- package/open-sse/mcp-server/toolSearch/handler.js +20 -0
- package/open-sse/mcp-server/toolSearch/index.js +10 -0
- package/open-sse/mcp-server/toolSearch/register.js +21 -0
- package/open-sse/mcp-server/toolSearch/search.js +55 -0
- package/open-sse/mcp-server/toolSearch/signature.js +67 -0
- package/open-sse/mcp-server/tools/advancedTools.js +893 -0
- package/open-sse/mcp-server/tools/agentSkillTools.js +69 -0
- package/open-sse/mcp-server/tools/compressionTools.js +309 -0
- package/open-sse/mcp-server/tools/gamificationTools.js +134 -0
- package/open-sse/mcp-server/tools/githubSkillTools.js +102 -0
- package/open-sse/mcp-server/tools/memoryTools.js +118 -0
- package/open-sse/mcp-server/tools/notionTools.js +101 -0
- package/open-sse/mcp-server/tools/obsidianTools.js +307 -0
- package/open-sse/mcp-server/tools/pickFastestModel.js +223 -0
- package/open-sse/mcp-server/tools/pluginTools.js +181 -0
- package/open-sse/mcp-server/tools/poolTools.js +139 -0
- package/open-sse/mcp-server/tools/skillTools.js +113 -0
- package/open-sse/package.json +10 -0
- package/open-sse/providers/REGISTRY_TEMPLATE.js +98 -0
- package/open-sse/providers/capabilities.js +293 -0
- package/open-sse/providers/index.js +51 -0
- package/open-sse/providers/models/helpers.js +20 -0
- package/open-sse/providers/models/namePatterns.js +33 -0
- package/open-sse/providers/models/schema.js +40 -0
- package/open-sse/providers/pricing.js +308 -0
- package/open-sse/providers/registry/alicode-intl.js +30 -0
- package/open-sse/providers/registry/alicode.js +31 -0
- package/open-sse/providers/registry/anthropic.js +37 -0
- package/open-sse/providers/registry/antigravity.js +81 -0
- package/open-sse/providers/registry/assemblyai.js +38 -0
- package/open-sse/providers/registry/aws-polly.js +45 -0
- package/open-sse/providers/registry/azure.js +21 -0
- package/open-sse/providers/registry/black-forest-labs.js +32 -0
- package/open-sse/providers/registry/blackbox.js +41 -0
- package/open-sse/providers/registry/brave-search.js +35 -0
- package/open-sse/providers/registry/byteplus.js +35 -0
- package/open-sse/providers/registry/cartesia.js +36 -0
- package/open-sse/providers/registry/cerebras.js +31 -0
- package/open-sse/providers/registry/chutes.js +24 -0
- package/open-sse/providers/registry/claude.js +87 -0
- package/open-sse/providers/registry/cline.js +51 -0
- package/open-sse/providers/registry/clinepass.js +57 -0
- package/open-sse/providers/registry/cloudflare-ai.js +55 -0
- package/open-sse/providers/registry/codebuddy-cn.js +77 -0
- package/open-sse/providers/registry/codex.js +100 -0
- package/open-sse/providers/registry/cohere.js +27 -0
- package/open-sse/providers/registry/comfyui.js +20 -0
- package/open-sse/providers/registry/commandcode.js +43 -0
- package/open-sse/providers/registry/coqui.js +30 -0
- package/open-sse/providers/registry/cursor.js +58 -0
- package/open-sse/providers/registry/deepgram.js +33 -0
- package/open-sse/providers/registry/deepseek.js +51 -0
- package/open-sse/providers/registry/digitalocean.js +92 -0
- package/open-sse/providers/registry/edge-tts.js +24 -0
- package/open-sse/providers/registry/elevenlabs.js +35 -0
- package/open-sse/providers/registry/exa.js +50 -0
- package/open-sse/providers/registry/fal-ai.js +34 -0
- package/open-sse/providers/registry/featherless.js +34 -0
- package/open-sse/providers/registry/firecrawl.js +34 -0
- package/open-sse/providers/registry/fireworks.js +29 -0
- package/open-sse/providers/registry/gemini-cli.js +58 -0
- package/open-sse/providers/registry/gemini.js +83 -0
- package/open-sse/providers/registry/github.js +88 -0
- package/open-sse/providers/registry/gitlab.js +32 -0
- package/open-sse/providers/registry/glm-cn.js +35 -0
- package/open-sse/providers/registry/glm.js +58 -0
- package/open-sse/providers/registry/google-pse.js +35 -0
- package/open-sse/providers/registry/google-tts.js +24 -0
- package/open-sse/providers/registry/grok-cli.js +86 -0
- package/open-sse/providers/registry/grok-web.js +39 -0
- package/open-sse/providers/registry/groq.js +37 -0
- package/open-sse/providers/registry/huggingface.js +34 -0
- package/open-sse/providers/registry/hyperbolic.js +35 -0
- package/open-sse/providers/registry/ibm-bob.js +27 -0
- package/open-sse/providers/registry/iflow.js +52 -0
- package/open-sse/providers/registry/index.js +210 -0
- package/open-sse/providers/registry/inworld.js +36 -0
- package/open-sse/providers/registry/jina-ai.js +42 -0
- package/open-sse/providers/registry/jina-reader.js +34 -0
- package/open-sse/providers/registry/kilocode.js +51 -0
- package/open-sse/providers/registry/kimchi.js +49 -0
- package/open-sse/providers/registry/kimi-coding.js +65 -0
- package/open-sse/providers/registry/kimi.js +56 -0
- package/open-sse/providers/registry/kiro.js +115 -0
- package/open-sse/providers/registry/linkup.js +34 -0
- package/open-sse/providers/registry/local-device.js +24 -0
- package/open-sse/providers/registry/mimo-free.js +24 -0
- package/open-sse/providers/registry/minimax-cn.js +76 -0
- package/open-sse/providers/registry/minimax.js +83 -0
- package/open-sse/providers/registry/mistral.js +34 -0
- package/open-sse/providers/registry/mmf.js +19 -0
- package/open-sse/providers/registry/nanobanana.js +35 -0
- package/open-sse/providers/registry/nebius.js +27 -0
- package/open-sse/providers/registry/nvidia.js +43 -0
- package/open-sse/providers/registry/ollama-local.js +19 -0
- package/open-sse/providers/registry/ollama.js +36 -0
- package/open-sse/providers/registry/openai.js +84 -0
- package/open-sse/providers/registry/opencode-go.js +41 -0
- package/open-sse/providers/registry/opencode.js +25 -0
- package/open-sse/providers/registry/openrouter.js +60 -0
- package/open-sse/providers/registry/perplexity-agent.js +49 -0
- package/open-sse/providers/registry/perplexity-web.js +33 -0
- package/open-sse/providers/registry/perplexity.js +37 -0
- package/open-sse/providers/registry/playht.js +36 -0
- package/open-sse/providers/registry/qoder.js +54 -0
- package/open-sse/providers/registry/qwen.js +33 -0
- package/open-sse/providers/registry/recraft.js +24 -0
- package/open-sse/providers/registry/runwayml.js +30 -0
- package/open-sse/providers/registry/sdwebui.js +20 -0
- package/open-sse/providers/registry/searchapi.js +35 -0
- package/open-sse/providers/registry/searxng.js +35 -0
- package/open-sse/providers/registry/serper.js +35 -0
- package/open-sse/providers/registry/siliconflow.js +39 -0
- package/open-sse/providers/registry/stability-ai.js +31 -0
- package/open-sse/providers/registry/tavily.js +50 -0
- package/open-sse/providers/registry/together.js +31 -0
- package/open-sse/providers/registry/topaz.js +19 -0
- package/open-sse/providers/registry/tortoise.js +30 -0
- package/open-sse/providers/registry/venice.js +56 -0
- package/open-sse/providers/registry/vercel-ai-gateway.js +41 -0
- package/open-sse/providers/registry/vertex-partner.js +29 -0
- package/open-sse/providers/registry/vertex.js +32 -0
- package/open-sse/providers/registry/volcengine-ark.js +35 -0
- package/open-sse/providers/registry/voyage-ai.js +30 -0
- package/open-sse/providers/registry/xai.js +45 -0
- package/open-sse/providers/registry/xiaomi-mimo.js +46 -0
- package/open-sse/providers/registry/xiaomi-tokenplan.js +63 -0
- package/open-sse/providers/registry/youcom.js +35 -0
- package/open-sse/providers/schema.js +76 -0
- package/open-sse/providers/shared.js +74 -0
- package/open-sse/providers/thinkingLevels.js +48 -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 +50 -0
- package/open-sse/rtk/filters/gitDiff.js +92 -0
- package/open-sse/rtk/filters/gitLog.js +99 -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/headroom.js +215 -0
- package/open-sse/rtk/index.js +155 -0
- package/open-sse/rtk/paleo.js +47 -0
- package/open-sse/rtk/ponytail.js +9 -0
- package/open-sse/rtk/ponytailPrompt.js +52 -0
- package/open-sse/rtk/ponytailPrompts.js +76 -0
- package/open-sse/rtk/pxpipe.js +104 -0
- package/open-sse/rtk/registry.js +38 -0
- package/open-sse/rtk/systemInject.js +98 -0
- package/open-sse/services/accountFallback.js +323 -0
- package/open-sse/services/antigravityHeaders.js +91 -0
- package/open-sse/services/antigravityVersion.js +154 -0
- package/open-sse/services/clinepassModels.js +63 -0
- package/open-sse/services/codexQuotaFetcher.js +312 -0
- package/open-sse/services/codexVerbosity.js +30 -0
- package/open-sse/services/combo/context.js +12 -0
- package/open-sse/services/combo/fingerprintExpansion.js +51 -0
- package/open-sse/services/combo/headroomRanking.js +28 -0
- package/open-sse/services/combo/quotaExhaustionCutoff.js +87 -0
- package/open-sse/services/combo/quotaScoring.js +226 -0
- package/open-sse/services/combo/quotaShareConcurrency.js +25 -0
- package/open-sse/services/combo/quotaShareInflight.js +50 -0
- package/open-sse/services/combo/quotaShareStrategy.js +130 -0
- package/open-sse/services/combo/resolveAutoStrategy.js +231 -0
- package/open-sse/services/combo/responseValidation.js +140 -0
- package/open-sse/services/combo/runtimeUnits.js +195 -0
- package/open-sse/services/combo/sessionStickiness.js +128 -0
- package/open-sse/services/combo/shadowRouting.js +131 -0
- package/open-sse/services/combo/targetExhaustion.js +75 -0
- package/open-sse/services/combo/targetSorters.js +126 -0
- package/open-sse/services/combo/targetTimeoutRunner.js +65 -0
- package/open-sse/services/combo/validateQuality.js +271 -0
- package/open-sse/services/combo.js +571 -0
- package/open-sse/services/comboAgentMiddleware.js +91 -0
- package/open-sse/services/comboConfig.js +153 -0
- package/open-sse/services/comboManifestMetrics.js +10 -0
- package/open-sse/services/comboMetrics.js +278 -0
- package/open-sse/services/compact.js +71 -0
- package/open-sse/services/copilotModels.js +155 -0
- package/open-sse/services/emergencyFallback.js +109 -0
- package/open-sse/services/errorClassifier.js +190 -0
- package/open-sse/services/evalRouting.js +201 -0
- package/open-sse/services/freeWebSearch.js +4 -0
- package/open-sse/services/fusion.js +223 -0
- package/open-sse/services/intentClassifier.js +631 -0
- package/open-sse/services/kimchiModels.js +176 -0
- package/open-sse/services/kiroModels.js +332 -0
- package/open-sse/services/model.js +136 -0
- package/open-sse/services/modelCapabilities.js +5 -0
- package/open-sse/services/modelDeprecation.js +101 -0
- package/open-sse/services/modelFamilyFallback.js +166 -0
- package/open-sse/services/oauthCredentialManager.js +156 -0
- package/open-sse/services/pipeline.js +107 -0
- package/open-sse/services/projectId.js +306 -0
- package/open-sse/services/provider.js +169 -0
- package/open-sse/services/providerCooldownTracker.js +94 -0
- package/open-sse/services/providerDefaultRateLimit.js +11 -0
- package/open-sse/services/proxyAutoSelector.js +24 -0
- package/open-sse/services/qoderModels.js +214 -0
- package/open-sse/services/quotaFetchThrottle.js +103 -0
- package/open-sse/services/quotaMonitor.js +310 -0
- package/open-sse/services/quotaPreflight.js +266 -0
- package/open-sse/services/rateLimitManager/headers.js +56 -0
- package/open-sse/services/rateLimitManager.js +616 -0
- package/open-sse/services/rateLimitSemaphore.js +112 -0
- package/open-sse/services/responsesInputSanitizer.js +99 -0
- package/open-sse/services/slidingWindowLimiter.js +44 -0
- package/open-sse/services/streamRecovery.js +355 -0
- package/open-sse/services/taskAwareRouter.js +227 -0
- package/open-sse/services/taskAwareRouting.js +332 -0
- package/open-sse/services/thinkingBudget.js +247 -0
- package/open-sse/services/tokenRefresh/dedup.js +31 -0
- package/open-sse/services/tokenRefresh/providers.js +624 -0
- package/open-sse/services/tokenRefresh.js +254 -0
- package/open-sse/services/usage/claude.js +147 -0
- package/open-sse/services/usage/codebuddy-cn.js +138 -0
- package/open-sse/services/usage/codex.js +267 -0
- package/open-sse/services/usage/github.js +100 -0
- package/open-sse/services/usage/google.js +241 -0
- package/open-sse/services/usage/grok-cli.js +274 -0
- package/open-sse/services/usage/kiro.js +188 -0
- package/open-sse/services/usage/minimax.js +234 -0
- package/open-sse/services/usage/misc.js +288 -0
- package/open-sse/services/usage/shared.js +70 -0
- package/open-sse/services/usage.js +62 -0
- package/open-sse/services/usageDb.js +2 -0
- package/open-sse/services/webSearchRouting.js +25 -0
- package/open-sse/services/wildcardRouter.js +59 -0
- package/open-sse/shared/clineAuth.js +37 -0
- package/open-sse/shared/machineId.js +19 -0
- package/open-sse/shared/qoder/constants.js +64 -0
- package/open-sse/shared/qoder/cosy.js +175 -0
- package/open-sse/shared/qoder/encoding.js +55 -0
- package/open-sse/transformer/responsesTransformer.js +439 -0
- package/open-sse/transformer/streamToJsonConverter.js +103 -0
- package/open-sse/translator/concerns/chunk.js +11 -0
- package/open-sse/translator/concerns/finishReason.js +63 -0
- package/open-sse/translator/concerns/image.js +124 -0
- package/open-sse/translator/concerns/json.js +5 -0
- package/open-sse/translator/concerns/message.js +7 -0
- package/open-sse/translator/concerns/modality.js +155 -0
- package/open-sse/translator/concerns/paramSupport.js +75 -0
- package/open-sse/translator/concerns/prefetch.js +96 -0
- package/open-sse/translator/concerns/reasoning.js +24 -0
- package/open-sse/translator/concerns/thinking.js +53 -0
- package/open-sse/translator/concerns/thinkingUnified.js +329 -0
- package/open-sse/translator/concerns/toolCall.js +153 -0
- package/open-sse/translator/concerns/usage.js +69 -0
- package/open-sse/translator/formats/claude.js +368 -0
- package/open-sse/translator/formats/gemini.js +380 -0
- package/open-sse/translator/formats/maxTokens.js +34 -0
- package/open-sse/translator/formats/openai.js +134 -0
- package/open-sse/translator/formats/responsesApi.js +141 -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 +252 -0
- package/open-sse/translator/request/antigravity-to-openai.js +242 -0
- package/open-sse/translator/request/claude-to-kiro.js +478 -0
- package/open-sse/translator/request/claude-to-openai.js +263 -0
- package/open-sse/translator/request/gemini-to-openai.js +152 -0
- package/open-sse/translator/request/openai-responses.js +385 -0
- package/open-sse/translator/request/openai-to-claude.js +383 -0
- package/open-sse/translator/request/openai-to-commandcode.js +176 -0
- package/open-sse/translator/request/openai-to-cursor.js +185 -0
- package/open-sse/translator/request/openai-to-gemini.js +442 -0
- package/open-sse/translator/request/openai-to-kiro.js +607 -0
- package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
- package/open-sse/translator/request/openai-to-ollama.js +195 -0
- package/open-sse/translator/request/openai-to-vertex.js +42 -0
- package/open-sse/translator/response/claude-to-openai.js +195 -0
- package/open-sse/translator/response/commandcode-to-openai.js +188 -0
- package/open-sse/translator/response/cursor-to-openai.js +30 -0
- package/open-sse/translator/response/gemini-to-openai.js +147 -0
- package/open-sse/translator/response/kiro-to-claude.js +261 -0
- package/open-sse/translator/response/kiro-to-openai.js +160 -0
- package/open-sse/translator/response/ollama-to-openai.js +134 -0
- package/open-sse/translator/response/openai-responses.js +535 -0
- package/open-sse/translator/response/openai-to-antigravity.js +123 -0
- package/open-sse/translator/response/openai-to-claude.js +265 -0
- package/open-sse/translator/schema/blocks.js +43 -0
- package/open-sse/translator/schema/defaults.js +7 -0
- package/open-sse/translator/schema/finishReasons.js +27 -0
- package/open-sse/translator/schema/index.js +8 -0
- package/open-sse/translator/schema/roles.js +16 -0
- package/open-sse/utils/agentGoalPolicy.js +85 -0
- package/open-sse/utils/aiSdkCompat.js +75 -0
- package/open-sse/utils/bypassHandler.js +298 -0
- package/open-sse/utils/cacheControlPolicy.js +189 -0
- package/open-sse/utils/claudeCloaking.js +155 -0
- package/open-sse/utils/claudeHeaderCache.js +70 -0
- package/open-sse/utils/claudeSignature.js +41 -0
- package/open-sse/utils/clientDetector.js +63 -0
- package/open-sse/utils/codebuddySanitizer.js +96 -0
- package/open-sse/utils/composerToolCalls.js +158 -0
- package/open-sse/utils/cors.js +13 -0
- package/open-sse/utils/cursorChecksum.js +149 -0
- package/open-sse/utils/cursorImages.js +224 -0
- package/open-sse/utils/cursorProtobuf.js +904 -0
- package/open-sse/utils/cursorVersionDetector.js +55 -0
- package/open-sse/utils/debugLog.js +14 -0
- package/open-sse/utils/diagnostics.js +143 -0
- package/open-sse/utils/error.js +164 -0
- package/open-sse/utils/estimateSize.js +33 -0
- package/open-sse/utils/finishReason.js +32 -0
- package/open-sse/utils/flattenToolHistory.js +68 -0
- package/open-sse/utils/headers.js +36 -0
- package/open-sse/utils/heapPressure.js +35 -0
- package/open-sse/utils/jsonToSse.js +125 -0
- package/open-sse/utils/keepaliveThreshold.js +40 -0
- package/open-sse/utils/logger.js +104 -0
- package/open-sse/utils/noThinkingAlias.js +74 -0
- package/open-sse/utils/number.js +7 -0
- package/open-sse/utils/ollamaTransform.js +85 -0
- package/open-sse/utils/opencodeHeaders.js +47 -0
- package/open-sse/utils/progressTracker.js +81 -0
- package/open-sse/utils/providerRequestLogging.js +219 -0
- package/open-sse/utils/proxyFetch.js +369 -0
- package/open-sse/utils/reasoningContentInjector.js +79 -0
- package/open-sse/utils/reasoningFields.js +70 -0
- package/open-sse/utils/requestLogger.js +260 -0
- package/open-sse/utils/responsesInputNormalization.js +92 -0
- package/open-sse/utils/responsesStatePolicy.js +52 -0
- package/open-sse/utils/responsesStreamHelpers.js +49 -0
- package/open-sse/utils/sessionManager.js +240 -0
- package/open-sse/utils/sleep.js +6 -0
- package/open-sse/utils/sse.js +14 -0
- package/open-sse/utils/sseConstants.js +23 -0
- package/open-sse/utils/sseHeartbeat.js +104 -0
- package/open-sse/utils/stream.js +517 -0
- package/open-sse/utils/streamFailureFinalization.js +134 -0
- package/open-sse/utils/streamHandler.js +250 -0
- package/open-sse/utils/streamHelpers.js +137 -0
- package/open-sse/utils/textualToolCall.js +104 -0
- package/open-sse/utils/thinkCloseMarker.js +72 -0
- package/open-sse/utils/thinkTagParser.js +146 -0
- package/open-sse/utils/toolCallArguments.js +12 -0
- package/open-sse/utils/toolDeduper.js +49 -0
- package/open-sse/utils/toolSources.js +31 -0
- package/open-sse/utils/upstreamResponseHeaders.js +25 -0
- package/open-sse/utils/urlSanitize.js +15 -0
- package/open-sse/utils/usageTracking.js +347 -0
- package/package.json +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Stream handler with disconnect detection - shared for all providers
|
|
2
|
+
import { STREAM_STALL_TIMEOUT_MS } from "../config/runtimeConfig.js";
|
|
3
|
+
import { dbg, isDebugEnabled } from "./debugLog.js";
|
|
4
|
+
|
|
5
|
+
// Get HH:MM:SS timestamp
|
|
6
|
+
function getTimeString() {
|
|
7
|
+
return new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create stream controller with abort and disconnect detection
|
|
12
|
+
* @param {object} options
|
|
13
|
+
* @param {function} options.onDisconnect - Callback when client disconnects
|
|
14
|
+
* @param {object} options.log - Logger instance
|
|
15
|
+
* @param {string} options.provider - Provider name
|
|
16
|
+
* @param {string} options.model - Model name
|
|
17
|
+
*/
|
|
18
|
+
export function createStreamController({ onDisconnect, onError, log, provider, model } = {}) {
|
|
19
|
+
const abortController = new AbortController();
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
let disconnected = false;
|
|
22
|
+
let abortTimeout = null;
|
|
23
|
+
|
|
24
|
+
const logStream = (status) => {
|
|
25
|
+
const duration = Date.now() - startTime;
|
|
26
|
+
const p = provider?.toUpperCase() || "UNKNOWN";
|
|
27
|
+
console.log(`[${getTimeString()}] 🌊 [STREAM] ${p} | ${model || "unknown"} | ${duration}ms | ${status}`);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
signal: abortController.signal,
|
|
32
|
+
startTime,
|
|
33
|
+
|
|
34
|
+
isConnected: () => !disconnected,
|
|
35
|
+
|
|
36
|
+
// Call when client disconnects
|
|
37
|
+
handleDisconnect: (reason = "client_closed") => {
|
|
38
|
+
if (disconnected) return;
|
|
39
|
+
disconnected = true;
|
|
40
|
+
|
|
41
|
+
logStream(`disconnect: ${reason}`);
|
|
42
|
+
dbg("CTRL", `${provider}/${model} | disconnect=${reason} | dur=${Date.now() - startTime}ms`);
|
|
43
|
+
|
|
44
|
+
// Delay abort to allow cleanup
|
|
45
|
+
abortTimeout = setTimeout(() => {
|
|
46
|
+
abortController.abort();
|
|
47
|
+
}, 500);
|
|
48
|
+
|
|
49
|
+
onDisconnect?.({ reason, duration: Date.now() - startTime });
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Call when stream completes normally
|
|
53
|
+
handleComplete: () => {
|
|
54
|
+
if (disconnected) return;
|
|
55
|
+
disconnected = true;
|
|
56
|
+
|
|
57
|
+
logStream("complete");
|
|
58
|
+
|
|
59
|
+
if (abortTimeout) {
|
|
60
|
+
clearTimeout(abortTimeout);
|
|
61
|
+
abortTimeout = null;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Call on error
|
|
66
|
+
handleError: (error) => {
|
|
67
|
+
if (disconnected) return;
|
|
68
|
+
disconnected = true;
|
|
69
|
+
|
|
70
|
+
if (abortTimeout) {
|
|
71
|
+
clearTimeout(abortTimeout);
|
|
72
|
+
abortTimeout = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (error.name === "AbortError") {
|
|
76
|
+
logStream("aborted");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
logStream(`error: ${error.message}`);
|
|
81
|
+
onError?.(error);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
abort: () => abortController.abort()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create transform stream with disconnect detection
|
|
90
|
+
* Wraps existing transform stream and adds abort capability.
|
|
91
|
+
*
|
|
92
|
+
* Stall detection lives in pipeWithDisconnect (tied to upstream byte
|
|
93
|
+
* activity), not here — output of the transform stream may be silent
|
|
94
|
+
* for long periods while raw bytes still flow (e.g. Kiro EventStream
|
|
95
|
+
* binary frames buffering, Claude reasoning streams).
|
|
96
|
+
*/
|
|
97
|
+
export function createDisconnectAwareStream(transformStream, streamController, onAbortTerminal = null) {
|
|
98
|
+
const reader = transformStream.readable.getReader();
|
|
99
|
+
const writer = transformStream.writable.getWriter();
|
|
100
|
+
let terminalEmitted = false;
|
|
101
|
+
|
|
102
|
+
// Emit a synthesized terminal payload (e.g. Responses response.failed + [DONE]) once
|
|
103
|
+
const emitTerminal = (controller) => {
|
|
104
|
+
if (terminalEmitted || !onAbortTerminal) return;
|
|
105
|
+
terminalEmitted = true;
|
|
106
|
+
try {
|
|
107
|
+
const bytes = onAbortTerminal();
|
|
108
|
+
if (bytes) controller.enqueue(bytes);
|
|
109
|
+
} catch { /* best-effort terminal */ }
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return new ReadableStream({
|
|
113
|
+
async pull(controller) {
|
|
114
|
+
if (!streamController.isConnected()) {
|
|
115
|
+
emitTerminal(controller);
|
|
116
|
+
controller.close();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
|
|
123
|
+
if (done) {
|
|
124
|
+
streamController.handleComplete();
|
|
125
|
+
controller.close();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
controller.enqueue(value);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
const wasConnected = streamController.isConnected();
|
|
131
|
+
streamController.handleError(error);
|
|
132
|
+
reader.cancel().catch(() => {});
|
|
133
|
+
writer.abort().catch(() => {});
|
|
134
|
+
|
|
135
|
+
// Treat network resets / socket hang up / abort as graceful close
|
|
136
|
+
const msg = error?.message || "";
|
|
137
|
+
const code = error?.code || error?.cause?.code || "";
|
|
138
|
+
const isNetworkClose =
|
|
139
|
+
error.name === "AbortError" ||
|
|
140
|
+
msg.includes("aborted") ||
|
|
141
|
+
msg.includes("socket hang up") ||
|
|
142
|
+
msg.includes("ECONNRESET") ||
|
|
143
|
+
msg.includes("ETIMEDOUT") ||
|
|
144
|
+
msg.includes("EPIPE") ||
|
|
145
|
+
code === "ECONNRESET" ||
|
|
146
|
+
code === "ETIMEDOUT" ||
|
|
147
|
+
code === "EPIPE" ||
|
|
148
|
+
code === "UND_ERR_SOCKET";
|
|
149
|
+
|
|
150
|
+
// Graceful close on network/abort, or when a structured terminal is available
|
|
151
|
+
// (Responses passthrough prefers response.failed + [DONE] over a raw transport error)
|
|
152
|
+
try {
|
|
153
|
+
if (!wasConnected || isNetworkClose || onAbortTerminal) {
|
|
154
|
+
emitTerminal(controller);
|
|
155
|
+
controller.close();
|
|
156
|
+
} else {
|
|
157
|
+
controller.error(error);
|
|
158
|
+
}
|
|
159
|
+
} catch (e) { /* already closed or cancelled */ }
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
cancel(reason) {
|
|
164
|
+
streamController.handleDisconnect(reason || "cancelled");
|
|
165
|
+
reader.cancel();
|
|
166
|
+
writer.abort();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Pipe provider response through transform with disconnect detection.
|
|
173
|
+
*
|
|
174
|
+
* Stall watchdog tracks raw upstream byte activity, not transform output.
|
|
175
|
+
* Reasoning models (Claude thinking via Kiro, etc.) can produce zero SSE
|
|
176
|
+
* output for long stretches while partial EventStream frames keep arriving.
|
|
177
|
+
* Measuring stall on the transform output caused false stalls and the
|
|
178
|
+
* "failed to pipe response" error in Next.
|
|
179
|
+
*
|
|
180
|
+
* Any upstream chunk resets the timer. If no bytes arrive for
|
|
181
|
+
* STREAM_STALL_TIMEOUT_MS, abort the underlying fetch via the controller.
|
|
182
|
+
*
|
|
183
|
+
* @param {Response} providerResponse - Response from provider
|
|
184
|
+
* @param {TransformStream} transformStream - Transform stream for SSE
|
|
185
|
+
* @param {object} streamController - Stream controller from createStreamController
|
|
186
|
+
*/
|
|
187
|
+
export function pipeWithDisconnect(providerResponse, transformStream, streamController, onAbortTerminal = null) {
|
|
188
|
+
let stallTimer = null;
|
|
189
|
+
let chunkCount = 0;
|
|
190
|
+
let totalBytes = 0;
|
|
191
|
+
let lastChunkAt = Date.now();
|
|
192
|
+
const t0 = Date.now();
|
|
193
|
+
const tag = "STREAM";
|
|
194
|
+
const clearStall = () => {
|
|
195
|
+
if (stallTimer) { clearTimeout(stallTimer); stallTimer = null; }
|
|
196
|
+
};
|
|
197
|
+
const armStall = () => {
|
|
198
|
+
clearStall();
|
|
199
|
+
stallTimer = setTimeout(() => {
|
|
200
|
+
stallTimer = null;
|
|
201
|
+
dbg(tag, `STALL TIMEOUT ${STREAM_STALL_TIMEOUT_MS}ms | chunks=${chunkCount} | bytes=${totalBytes} | sinceLast=${Date.now() - lastChunkAt}ms`);
|
|
202
|
+
streamController.handleError?.(new Error("stream stall timeout"));
|
|
203
|
+
streamController.abort?.();
|
|
204
|
+
}, STREAM_STALL_TIMEOUT_MS);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Wrap controller so every termination path clears the stall timer.
|
|
208
|
+
// Without this, abort/cancel/downstream-error paths leave the timer armed
|
|
209
|
+
// and a stale abort could fire after the request has already ended.
|
|
210
|
+
const wrappedController = {
|
|
211
|
+
signal: streamController.signal,
|
|
212
|
+
startTime: streamController.startTime,
|
|
213
|
+
isConnected: () => streamController.isConnected(),
|
|
214
|
+
handleComplete: () => { dbg(tag, `complete | chunks=${chunkCount} | bytes=${totalBytes} | dur=${Date.now() - t0}ms`); clearStall(); streamController.handleComplete(); },
|
|
215
|
+
handleError: (e) => { dbg(tag, `error: ${e?.message} | chunks=${chunkCount} | bytes=${totalBytes} | dur=${Date.now() - t0}ms`); clearStall(); streamController.handleError(e); },
|
|
216
|
+
handleDisconnect: (r) => { dbg(tag, `disconnect: ${r} | chunks=${chunkCount} | bytes=${totalBytes} | dur=${Date.now() - t0}ms`); clearStall(); streamController.handleDisconnect(r); },
|
|
217
|
+
abort: () => { clearStall(); streamController.abort(); }
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
armStall();
|
|
221
|
+
dbg(tag, `pipe start | stallTimeout=${STREAM_STALL_TIMEOUT_MS}ms`);
|
|
222
|
+
|
|
223
|
+
const upstreamTap = new TransformStream({
|
|
224
|
+
transform(chunk, controller) {
|
|
225
|
+
chunkCount++;
|
|
226
|
+
const sz = chunk?.byteLength || chunk?.length || 0;
|
|
227
|
+
totalBytes += sz;
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
const gap = now - lastChunkAt;
|
|
230
|
+
lastChunkAt = now;
|
|
231
|
+
if (isDebugEnabled && (chunkCount <= 5 || chunkCount % 20 === 0 || gap > 5000)) {
|
|
232
|
+
dbg(tag, `chunk #${chunkCount} | size=${sz}B | gap=${gap}ms | total=${totalBytes}B`);
|
|
233
|
+
}
|
|
234
|
+
armStall();
|
|
235
|
+
controller.enqueue(chunk);
|
|
236
|
+
},
|
|
237
|
+
flush() { dbg(tag, `upstream EOF | chunks=${chunkCount} | bytes=${totalBytes} | dur=${Date.now() - t0}ms`); clearStall(); }
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const transformedBody = providerResponse.body
|
|
241
|
+
.pipeThrough(upstreamTap)
|
|
242
|
+
.pipeThrough(transformStream);
|
|
243
|
+
|
|
244
|
+
return createDisconnectAwareStream(
|
|
245
|
+
{ readable: transformedBody, writable: { getWriter: () => ({ abort: () => Promise.resolve() }) } },
|
|
246
|
+
wrappedController,
|
|
247
|
+
onAbortTerminal
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { FORMATS } from "../translator/formats.js";
|
|
2
|
+
|
|
3
|
+
// Parse SSE data line
|
|
4
|
+
export function parseSSELine(line, format = null) {
|
|
5
|
+
if (!line) return null;
|
|
6
|
+
|
|
7
|
+
// NDJSON format (Ollama): raw JSON lines without "data:" prefix
|
|
8
|
+
if (format === FORMATS.OLLAMA) {
|
|
9
|
+
const trimmed = line.trim();
|
|
10
|
+
if (trimmed.startsWith("{")) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(trimmed);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Handle raw JSON errors disguised as SSE
|
|
21
|
+
if (line.charCodeAt(0) === 123) { // '{' = 123
|
|
22
|
+
try {
|
|
23
|
+
const parsedJson = JSON.parse(line.trim());
|
|
24
|
+
// Check if it looks like an error payload
|
|
25
|
+
if (parsedJson && (parsedJson.error || parsedJson.code !== undefined || parsedJson.msg)) {
|
|
26
|
+
return { _isRawError: true, raw: parsedJson };
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Ignore parse errors, continue to normal SSE check
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Standard SSE format: "data: {...}"
|
|
34
|
+
if (line.charCodeAt(0) !== 100) return null; // 'd' = 100
|
|
35
|
+
|
|
36
|
+
const data = line.slice(5).trim();
|
|
37
|
+
if (data === "[DONE]") return { done: true };
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (data.length > 0 && data.length < 1000) {
|
|
43
|
+
console.log(`[WARN] Failed to parse SSE line (${data.length} chars): ${data.substring(0, 100)}...`);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if chunk has valuable content (not empty)
|
|
50
|
+
export function hasValuableContent(chunk, format) {
|
|
51
|
+
// OpenAI format
|
|
52
|
+
if (format === FORMATS.OPENAI && chunk.choices?.[0]?.delta) {
|
|
53
|
+
const delta = chunk.choices[0].delta;
|
|
54
|
+
return !!(delta.content && delta.content !== "" ||
|
|
55
|
+
delta.reasoning_content && delta.reasoning_content !== "" ||
|
|
56
|
+
delta.tool_calls && delta.tool_calls.length > 0 ||
|
|
57
|
+
delta.refusal && delta.refusal !== "" ||
|
|
58
|
+
chunk.choices[0].finish_reason ||
|
|
59
|
+
delta.role ||
|
|
60
|
+
chunk.usage);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Claude format
|
|
64
|
+
if (format === FORMATS.CLAUDE) {
|
|
65
|
+
const isContentBlockDelta = chunk.type === "content_block_delta";
|
|
66
|
+
const hasText = chunk.delta?.text && chunk.delta.text !== "";
|
|
67
|
+
const hasThinking = chunk.delta?.thinking && chunk.delta.thinking !== "";
|
|
68
|
+
const hasInputJson = chunk.delta?.partial_json && chunk.delta.partial_json !== "";
|
|
69
|
+
|
|
70
|
+
if (isContentBlockDelta && !hasText && !hasThinking && !hasInputJson) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return true; // Other formats: keep all chunks
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fix invalid id (generic or too short)
|
|
80
|
+
export function fixInvalidId(parsed) {
|
|
81
|
+
if (parsed.id && (parsed.id === "chat" || parsed.id === "completion" || parsed.id.length < 8)) {
|
|
82
|
+
const fallbackId = parsed.extend_fields?.requestId ||
|
|
83
|
+
parsed.extend_fields?.traceId ||
|
|
84
|
+
Date.now().toString(36);
|
|
85
|
+
parsed.id = `chatcmpl-${fallbackId}`;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function cleanUsagePayload(payload) {
|
|
92
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
93
|
+
return payload;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let cleaned = payload;
|
|
97
|
+
|
|
98
|
+
if ("usage" in cleaned) {
|
|
99
|
+
if (cleaned.usage === null) {
|
|
100
|
+
const { usage, ...payloadWithoutUsage } = cleaned;
|
|
101
|
+
cleaned = payloadWithoutUsage;
|
|
102
|
+
} else if (typeof cleaned.usage === "object" && cleaned.usage.perf_metrics === null) {
|
|
103
|
+
const { perf_metrics, ...usageWithoutPerf } = cleaned.usage;
|
|
104
|
+
cleaned = { ...cleaned, usage: usageWithoutPerf };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (cleaned.response && typeof cleaned.response === "object" && !Array.isArray(cleaned.response)) {
|
|
109
|
+
const cleanedResponse = cleanUsagePayload(cleaned.response);
|
|
110
|
+
if (cleanedResponse !== cleaned.response) {
|
|
111
|
+
cleaned = { ...cleaned, response: cleanedResponse };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return cleaned;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Format output as SSE
|
|
119
|
+
export function formatSSE(data, sourceFormat) {
|
|
120
|
+
if (data === null || data === undefined) return "data: null\n\n";
|
|
121
|
+
if (data && data.done) return "data: [DONE]\n\n";
|
|
122
|
+
|
|
123
|
+
// OpenAI Responses API format
|
|
124
|
+
if (data && data.event && data.data) {
|
|
125
|
+
const cleanedEventData = cleanUsagePayload(data.data);
|
|
126
|
+
return `event: ${data.event}\ndata: ${JSON.stringify(cleanedEventData)}\n\n`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
data = cleanUsagePayload(data);
|
|
130
|
+
|
|
131
|
+
// Claude format
|
|
132
|
+
if (sourceFormat === FORMATS.CLAUDE && data && data.type) {
|
|
133
|
+
return `event: ${data.type}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return `data: ${JSON.stringify(data)}\n\n`;
|
|
137
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
function stripZeroWidth(value) {
|
|
2
|
+
if (typeof value === "string") {
|
|
3
|
+
return value.replace(/[\u200B-\u200D\uFEFF]/g, "");
|
|
4
|
+
}
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value.map((item) => stripZeroWidth(item));
|
|
7
|
+
}
|
|
8
|
+
if (value && typeof value === "object") {
|
|
9
|
+
return Object.fromEntries(
|
|
10
|
+
Object.entries(value).map(([key, item]) => [
|
|
11
|
+
key,
|
|
12
|
+
stripZeroWidth(item)
|
|
13
|
+
])
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function isValidToolCallHeaderPrefix(candidate) {
|
|
19
|
+
if (!candidate.startsWith("[Tool call:")) return false;
|
|
20
|
+
const bracketIndex = candidate.indexOf("]");
|
|
21
|
+
if (bracketIndex === -1) {
|
|
22
|
+
const namePart2 = candidate.slice("[Tool call:".length);
|
|
23
|
+
if (namePart2.includes("\n") || namePart2.includes("[")) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const namePart = candidate.slice("[Tool call:".length, bracketIndex);
|
|
27
|
+
if (namePart.includes("\n") || namePart.trim().length === 0) return false;
|
|
28
|
+
const afterBracket = candidate.slice(bracketIndex + 1);
|
|
29
|
+
const leadingWhitespaceMatch = afterBracket.match(/^[\s\r\n]*/);
|
|
30
|
+
const leadingWhitespace = leadingWhitespaceMatch ? leadingWhitespaceMatch[0] : "";
|
|
31
|
+
const textAfterWhitespace = afterBracket.slice(leadingWhitespace.length);
|
|
32
|
+
if (textAfterWhitespace.length === 0) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (!leadingWhitespace.includes("\n")) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const expectedText = "Arguments:";
|
|
39
|
+
if (expectedText.startsWith(textAfterWhitespace)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (textAfterWhitespace.startsWith(expectedText)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function parseTextualToolCallCandidate(text) {
|
|
48
|
+
if (typeof text !== "string") return null;
|
|
49
|
+
const normalized = text.replace(/[\u200B-\u200D\uFEFF]/g, "");
|
|
50
|
+
const toolCallIndex = normalized.lastIndexOf("[Tool call:");
|
|
51
|
+
if (toolCallIndex < 0) {
|
|
52
|
+
const lastParen = normalized.lastIndexOf("(");
|
|
53
|
+
if (lastParen !== -1 && "(empty)[Tool call:".startsWith(normalized.slice(lastParen))) {
|
|
54
|
+
return { kind: "partial" };
|
|
55
|
+
}
|
|
56
|
+
const lastBracket = normalized.lastIndexOf("[");
|
|
57
|
+
if (lastBracket !== -1 && "[Tool call:".startsWith(normalized.slice(lastBracket))) {
|
|
58
|
+
return { kind: "partial" };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const candidate = normalized.slice(toolCallIndex);
|
|
63
|
+
if (!isValidToolCallHeaderPrefix(candidate)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const headerMatch = candidate.match(/^\[Tool call:\s*([^\]\n]+)\]\s*\nArguments:\s*/);
|
|
67
|
+
if (!headerMatch) return { kind: "partial" };
|
|
68
|
+
const name = headerMatch[1]?.trim();
|
|
69
|
+
const rawArgs = candidate.slice(headerMatch[0].length).trim();
|
|
70
|
+
if (!name || !rawArgs) return { kind: "partial" };
|
|
71
|
+
const decoders = [
|
|
72
|
+
(value) => value,
|
|
73
|
+
(value) => {
|
|
74
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
75
|
+
const decoded = JSON.parse(value);
|
|
76
|
+
return typeof decoded === "string" ? decoded : value;
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
for (const decode of decoders) {
|
|
82
|
+
try {
|
|
83
|
+
const decoded = decode(rawArgs);
|
|
84
|
+
const parsed = JSON.parse(decoded);
|
|
85
|
+
return { kind: "complete", name, args: stripZeroWidth(parsed) };
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { kind: "partial" };
|
|
90
|
+
}
|
|
91
|
+
function containsTextualToolCallMarker(text) {
|
|
92
|
+
if (typeof text !== "string") return false;
|
|
93
|
+
const normalized = text.replace(/[\u200B-\u200D\uFEFF]/g, "");
|
|
94
|
+
if (!normalized.includes("[Tool call:")) return false;
|
|
95
|
+
if (normalized.includes("Arguments:")) return true;
|
|
96
|
+
const trimmed = normalized.trim();
|
|
97
|
+
return trimmed.startsWith("[Tool call:") || trimmed.startsWith("(empty)[Tool call:");
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
containsTextualToolCallMarker,
|
|
101
|
+
isValidToolCallHeaderPrefix,
|
|
102
|
+
parseTextualToolCallCandidate,
|
|
103
|
+
stripZeroWidth
|
|
104
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `</think>` close-marker client policy.
|
|
3
|
+
*
|
|
4
|
+
* When OmniRoute translates a Claude-native streamed response to OpenAI Chat
|
|
5
|
+
* Completions shape (`claude-to-openai.ts`), it emits a single `</think>`
|
|
6
|
+
* close marker as `delta.content` so clients that scan content for the marker
|
|
7
|
+
* (Claude Code, Cursor) can split reasoning from the final answer — see #4633.
|
|
8
|
+
*
|
|
9
|
+
* Some OpenAI-compatible consumers do NOT parse that marker and render it
|
|
10
|
+
* verbatim, so a bare `</think>` leaks into the visible reply (#5245). OpenCode
|
|
11
|
+
* is one such client.
|
|
12
|
+
*
|
|
13
|
+
* Policy is conservative and opt-OUT by allowlist: the marker stays ON by
|
|
14
|
+
* default (preserving #4633 for Claude Code / Cursor and any unrecognized
|
|
15
|
+
* client), and is suppressed ONLY for known clients that render it literally.
|
|
16
|
+
* Detection is by inbound `User-Agent`.
|
|
17
|
+
*
|
|
18
|
+
* Clients that DO render the marker verbatim but are not in the UA allowlist
|
|
19
|
+
* (e.g. Cursor's reasoning_content-native OpenAI path — #5312 / #5245) can opt
|
|
20
|
+
* in explicitly with the request header `x-omniroute-thinking-marker: off`,
|
|
21
|
+
* which suppresses the marker regardless of User-Agent. `on` forces it kept
|
|
22
|
+
* (overriding the UA allowlist). The default (header absent) is byte-identical
|
|
23
|
+
* to the UA-only policy, so #4633 / #5123 are never regressed.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Header clients send to explicitly opt in/out of the `</think>` close marker. */
|
|
27
|
+
export const THINKING_MARKER_HEADER = "x-omniroute-thinking-marker";
|
|
28
|
+
|
|
29
|
+
// Lowercased User-Agent substrings of clients that render the textual
|
|
30
|
+
// `</think>` marker verbatim and therefore want it suppressed.
|
|
31
|
+
// - `opencode` (#5245): renders the marker as literal text.
|
|
32
|
+
// - `antigravity` (#1061): the Antigravity IDE client (UA
|
|
33
|
+
// `vscode/<v> (Antigravity/<v>)`) renders a bare `</think>` as the sole
|
|
34
|
+
// visible content on thinking-only turns, which trips its loop-detection.
|
|
35
|
+
const SUPPRESS_THINK_CLOSE_UA_MARKERS = ["opencode", "antigravity"];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether the streamed `</think>` close marker should be suppressed for the
|
|
39
|
+
* given inbound client. Returns false (emit the marker) for unknown clients and
|
|
40
|
+
* for Claude Code / Cursor, so #4633 is never regressed.
|
|
41
|
+
*/
|
|
42
|
+
export function shouldSuppressThinkCloseMarker(userAgent) {
|
|
43
|
+
if (!userAgent || typeof userAgent !== "string") return false;
|
|
44
|
+
const ua = userAgent.toLowerCase();
|
|
45
|
+
return SUPPRESS_THINK_CLOSE_UA_MARKERS.some((marker) => ua.includes(marker));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Interpret the explicit `x-omniroute-thinking-marker` request header.
|
|
50
|
+
* Returns `true` (suppress the marker), `false` (force-keep the marker), or
|
|
51
|
+
* `null` when the header is absent/unrecognized (defer to the UA policy).
|
|
52
|
+
*/
|
|
53
|
+
export function thinkingMarkerHeaderSignal(headerValue) {
|
|
54
|
+
if (typeof headerValue !== "string") return null;
|
|
55
|
+
const value = headerValue.trim().toLowerCase();
|
|
56
|
+
if (value === "off" || value === "false" || value === "0" || value === "suppress") return true;
|
|
57
|
+
if (value === "on" || value === "true" || value === "1" || value === "keep") return false;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve whether the streamed `</think>` close marker should be suppressed for
|
|
63
|
+
* this request. An explicit `x-omniroute-thinking-marker` header wins; absent
|
|
64
|
+
* that, the conservative User-Agent allowlist policy applies. With no header and
|
|
65
|
+
* an unrecognized UA the result is `false` (marker kept), so #4633 / #5123 stay
|
|
66
|
+
* byte-identical by default.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveSuppressThinkClose(opts) {
|
|
69
|
+
const headerSignal = thinkingMarkerHeaderSignal(opts.thinkingMarkerHeader);
|
|
70
|
+
if (headerSignal !== null) return headerSignal;
|
|
71
|
+
return shouldSuppressThinkCloseMarker(opts.userAgent);
|
|
72
|
+
}
|