@fudrouter/fsrouter 0.6.64 → 0.6.66
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/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,99 @@
|
|
|
1
|
+
// JS-native git-log filter
|
|
2
|
+
// Compresses `git log` output: keeps commit headers, subjects, Author/Date;
|
|
3
|
+
// drops body padding, decoration, embedded diff lines.
|
|
4
|
+
import { GIT_LOG_MAX_LINES } from "../constants.js";
|
|
5
|
+
|
|
6
|
+
export function gitLog(text, maxLines = GIT_LOG_MAX_LINES) {
|
|
7
|
+
if (!text) return "";
|
|
8
|
+
|
|
9
|
+
const input = String(text);
|
|
10
|
+
const lines = input.split("\n");
|
|
11
|
+
const out = [];
|
|
12
|
+
let skipped = 0;
|
|
13
|
+
let inCommit = false;
|
|
14
|
+
let subjectSeen = false;
|
|
15
|
+
|
|
16
|
+
function pushLine(l) {
|
|
17
|
+
if (out.length < maxLines) {
|
|
18
|
+
out.push(l);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
skipped++;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26
|
+
const raw = lines[i];
|
|
27
|
+
const line = raw.trimEnd();
|
|
28
|
+
const trimmed = line.trim();
|
|
29
|
+
|
|
30
|
+
// commit <sha> header — starts new commit entry
|
|
31
|
+
// Also matched with leading graph decoration (`* commit abc1234...` — --graph without --oneline)
|
|
32
|
+
if (/^commit [0-9a-f]{7,40}$/i.test(trimmed) || /^[*|/\\ ]+commit [0-9a-f]{7,40}/i.test(trimmed)) {
|
|
33
|
+
inCommit = true;
|
|
34
|
+
subjectSeen = false;
|
|
35
|
+
pushLine(line);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (inCommit) {
|
|
40
|
+
// Author / Date — keep as-is (already column 0 in raw, or graph-prefix stripped by commit-header match)
|
|
41
|
+
if (/^[*|/\\ ]*(Author|Date):/i.test(trimmed)) {
|
|
42
|
+
pushLine(trimmed);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// blank — skip
|
|
46
|
+
if (trimmed === "") continue;
|
|
47
|
+
// indented subject (4 spaces, optionally preceded by graph decoration) — first one is subject
|
|
48
|
+
if (!subjectSeen && /^[*|/\\ ]* \S/.test(line)) {
|
|
49
|
+
pushLine(" Subject: " + trimmed);
|
|
50
|
+
subjectSeen = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// stat summary: "N file(s) changed, N insertions(+), N deletions(-)"
|
|
54
|
+
if (/^\d+ file\w* changed/.test(trimmed)) {
|
|
55
|
+
pushLine(" " + trimmed);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// embedded diff header — one-line marker
|
|
59
|
+
if (/^diff --git /.test(trimmed)) {
|
|
60
|
+
pushLine(" ... diff body omitted");
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// everything else in commit body — drop
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Not in a commit block (--oneline / --graph modes):
|
|
68
|
+
|
|
69
|
+
// Graph decoration + sha + subject: "*|/\\ <sha7> <subject>"
|
|
70
|
+
const graphMatch = trimmed.match(/^[*|/\\ ]+([0-9a-f]{7,40}\s+.+)/i);
|
|
71
|
+
if (graphMatch) {
|
|
72
|
+
pushLine(graphMatch[1]);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Plain oneline: "<sha7> <subject>"
|
|
77
|
+
if (/^[0-9a-f]{7,40}\s+/.test(trimmed)) {
|
|
78
|
+
pushLine(trimmed);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Pure graph decoration (no sha) — drop
|
|
83
|
+
if (/^[*|/\\ ]+$/.test(trimmed) && /[*|/\\]/.test(trimmed)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// catch-all pass-through
|
|
88
|
+
pushLine(trimmed);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (skipped > 0) out.push(`... (${skipped} more lines)`);
|
|
92
|
+
|
|
93
|
+
const result = out.join("\n");
|
|
94
|
+
if (!result && input) return input;
|
|
95
|
+
if (result.length > input.length) return input;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
gitLog.filterName = "git-log";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Port of git::format_status_output (rtk/src/cmds/git/git.rs:619-730)
|
|
2
|
+
// Output format:
|
|
3
|
+
// * <branch>
|
|
4
|
+
// + Staged: N files
|
|
5
|
+
// path1
|
|
6
|
+
// ... +K more
|
|
7
|
+
// ~ Modified: N files
|
|
8
|
+
// ? Untracked: N files
|
|
9
|
+
// conflicts: N files
|
|
10
|
+
// clean — nothing to commit
|
|
11
|
+
import { STATUS_MAX_FILES, STATUS_MAX_UNTRACKED } from "../constants.js";
|
|
12
|
+
|
|
13
|
+
export function gitStatus(input) {
|
|
14
|
+
const lines = input.split("\n");
|
|
15
|
+
if (lines.length === 0 || (lines.length === 1 && !lines[0].trim())) {
|
|
16
|
+
return "Clean working tree";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let branch = "";
|
|
20
|
+
const stagedFiles = [];
|
|
21
|
+
const modifiedFiles = [];
|
|
22
|
+
const untrackedFiles = [];
|
|
23
|
+
let staged = 0;
|
|
24
|
+
let modified = 0;
|
|
25
|
+
let untracked = 0;
|
|
26
|
+
let conflicts = 0;
|
|
27
|
+
|
|
28
|
+
for (const raw of lines) {
|
|
29
|
+
if (!raw.trim()) continue;
|
|
30
|
+
|
|
31
|
+
// Long-form branch detection (LLM usually sends this, not porcelain)
|
|
32
|
+
const longBranch = raw.match(/^On branch (\S+)/);
|
|
33
|
+
if (longBranch) { branch = longBranch[1]; continue; }
|
|
34
|
+
|
|
35
|
+
// Porcelain branch header: "## main...origin/main"
|
|
36
|
+
if (raw.startsWith("##")) { branch = raw.replace(/^##\s*/, ""); continue; }
|
|
37
|
+
|
|
38
|
+
// Porcelain status (2 chars + space + path)
|
|
39
|
+
if (raw.length >= 3 && /^[ MADRCU?!][ MADRCU?!] /.test(raw)) {
|
|
40
|
+
const x = raw[0];
|
|
41
|
+
const y = raw[1];
|
|
42
|
+
const file = raw.slice(3);
|
|
43
|
+
|
|
44
|
+
if (raw.slice(0, 2) === "??") {
|
|
45
|
+
untracked++;
|
|
46
|
+
untrackedFiles.push(file);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ("MADRC".includes(x)) {
|
|
51
|
+
staged++;
|
|
52
|
+
stagedFiles.push(file);
|
|
53
|
+
} else if (x === "U") {
|
|
54
|
+
conflicts++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (y === "M" || y === "D") {
|
|
58
|
+
modified++;
|
|
59
|
+
modifiedFiles.push(file);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Long form fallback ("modified: path", "new file: path", ...)
|
|
65
|
+
const longMatch = raw.match(/^\s*(modified|new file|deleted|renamed|both modified):\s+(.+)$/);
|
|
66
|
+
if (longMatch) {
|
|
67
|
+
const kind = longMatch[1];
|
|
68
|
+
const path = longMatch[2].trim();
|
|
69
|
+
if (kind === "both modified") { conflicts++; }
|
|
70
|
+
else if (kind === "modified" || kind === "deleted") { modified++; modifiedFiles.push(path); }
|
|
71
|
+
else if (kind === "new file" || kind === "renamed") { staged++; stagedFiles.push(path); }
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// "Untracked files:" section — gather bare paths after this marker
|
|
76
|
+
// Handled implicitly: plain paths without markers are skipped (safer).
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let out = "";
|
|
80
|
+
if (branch) out += `* ${branch}\n`;
|
|
81
|
+
|
|
82
|
+
if (staged > 0) {
|
|
83
|
+
out += `+ Staged: ${staged} files\n`;
|
|
84
|
+
for (const f of stagedFiles.slice(0, STATUS_MAX_FILES)) out += ` ${f}\n`;
|
|
85
|
+
if (stagedFiles.length > STATUS_MAX_FILES) {
|
|
86
|
+
out += ` ... +${stagedFiles.length - STATUS_MAX_FILES} more\n`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (modified > 0) {
|
|
91
|
+
out += `~ Modified: ${modified} files\n`;
|
|
92
|
+
for (const f of modifiedFiles.slice(0, STATUS_MAX_FILES)) out += ` ${f}\n`;
|
|
93
|
+
if (modifiedFiles.length > STATUS_MAX_FILES) {
|
|
94
|
+
out += ` ... +${modifiedFiles.length - STATUS_MAX_FILES} more\n`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (untracked > 0) {
|
|
99
|
+
out += `? Untracked: ${untracked} files\n`;
|
|
100
|
+
for (const f of untrackedFiles.slice(0, STATUS_MAX_UNTRACKED)) out += ` ${f}\n`;
|
|
101
|
+
if (untrackedFiles.length > STATUS_MAX_UNTRACKED) {
|
|
102
|
+
out += ` ... +${untrackedFiles.length - STATUS_MAX_UNTRACKED} more\n`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (conflicts > 0) {
|
|
107
|
+
out += `conflicts: ${conflicts} files\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (staged === 0 && modified === 0 && untracked === 0 && conflicts === 0) {
|
|
111
|
+
out += "clean — nothing to commit\n";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return out.replace(/\n+$/, "");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
gitStatus.filterName = "git-status";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Port of grep_wrapper (rtk/src/cmds/system/pipe_cmd.rs:50-86)
|
|
2
|
+
// Input format: "file:lineno:content" — splitn(3, ':') in Rust
|
|
3
|
+
import { GREP_PER_FILE_MAX } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
export function grep(input) {
|
|
6
|
+
const byFile = new Map();
|
|
7
|
+
let total = 0;
|
|
8
|
+
|
|
9
|
+
for (const line of input.split("\n")) {
|
|
10
|
+
// splitn(3, ':') — only split on first 2 colons
|
|
11
|
+
const first = line.indexOf(":");
|
|
12
|
+
if (first === -1) continue;
|
|
13
|
+
const second = line.indexOf(":", first + 1);
|
|
14
|
+
if (second === -1) continue;
|
|
15
|
+
const file = line.slice(0, first);
|
|
16
|
+
const lineNumStr = line.slice(first + 1, second);
|
|
17
|
+
const content = line.slice(second + 1);
|
|
18
|
+
// Rust: parts[1].parse::<usize>().is_ok()
|
|
19
|
+
if (!/^\d+$/.test(lineNumStr)) continue;
|
|
20
|
+
total++;
|
|
21
|
+
if (!byFile.has(file)) byFile.set(file, []);
|
|
22
|
+
byFile.get(file).push([lineNumStr, content]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (total === 0) return input;
|
|
26
|
+
|
|
27
|
+
// Rust: files.sort_by_key(|(f, _)| *f)
|
|
28
|
+
const files = Array.from(byFile.keys()).sort();
|
|
29
|
+
let out = `${total} matches in ${files.length}F:\n\n`;
|
|
30
|
+
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const matches = byFile.get(file);
|
|
33
|
+
out += `[file] ${file} (${matches.length}):\n`;
|
|
34
|
+
const show = matches.slice(0, GREP_PER_FILE_MAX);
|
|
35
|
+
for (const [lineNum, content] of show) {
|
|
36
|
+
// Rust: format!(" {:>4}: {}", line_num, content.trim())
|
|
37
|
+
out += ` ${lineNum.padStart(4)}: ${content.trim()}\n`;
|
|
38
|
+
}
|
|
39
|
+
if (matches.length > GREP_PER_FILE_MAX) {
|
|
40
|
+
out += ` +${matches.length - GREP_PER_FILE_MAX}\n`;
|
|
41
|
+
}
|
|
42
|
+
out += "\n";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
grep.filterName = "grep";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Port of compact_ls (rtk/src/cmds/system/ls.rs:154-232)
|
|
2
|
+
// Input: `ls -la` style output. Output: compact "name/ (dirs)\nname size"
|
|
3
|
+
import { LS_EXT_SUMMARY_TOP, LS_NOISE_DIRS } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
// Rust LS_DATE_RE: month + day + (year|HH:MM)
|
|
6
|
+
const LS_DATE_RE = /\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\s+(\d{4}|\d{2}:\d{2})\s+/;
|
|
7
|
+
|
|
8
|
+
function humanSize(bytes) {
|
|
9
|
+
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)}M`;
|
|
10
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
11
|
+
return `${bytes}B`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseLsLine(line) {
|
|
15
|
+
const m = LS_DATE_RE.exec(line);
|
|
16
|
+
if (!m) return null;
|
|
17
|
+
const name = line.slice(m.index + m[0].length);
|
|
18
|
+
const beforeDate = line.slice(0, m.index);
|
|
19
|
+
const beforeParts = beforeDate.split(/\s+/).filter(Boolean);
|
|
20
|
+
if (beforeParts.length < 4) return null;
|
|
21
|
+
|
|
22
|
+
const perms = beforeParts[0];
|
|
23
|
+
const fileType = perms.charAt(0);
|
|
24
|
+
|
|
25
|
+
// size = rightmost parseable number before the date
|
|
26
|
+
let size = 0;
|
|
27
|
+
for (let i = beforeParts.length - 1; i >= 0; i--) {
|
|
28
|
+
const n = Number(beforeParts[i]);
|
|
29
|
+
if (Number.isInteger(n) && String(n) === beforeParts[i]) { size = n; break; }
|
|
30
|
+
}
|
|
31
|
+
return { fileType, size, name };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ls(input) {
|
|
35
|
+
const dirs = [];
|
|
36
|
+
const files = []; // [name, sizeStr]
|
|
37
|
+
const byExt = new Map();
|
|
38
|
+
|
|
39
|
+
for (const line of input.split("\n")) {
|
|
40
|
+
if (line.startsWith("total ") || line.length === 0) continue;
|
|
41
|
+
const parsed = parseLsLine(line);
|
|
42
|
+
if (!parsed) continue;
|
|
43
|
+
if (parsed.name === "." || parsed.name === "..") continue;
|
|
44
|
+
|
|
45
|
+
// Rust ls.rs: show_all flag respected — for LLM context always skip noise
|
|
46
|
+
if (LS_NOISE_DIRS.includes(parsed.name)) continue;
|
|
47
|
+
|
|
48
|
+
if (parsed.fileType === "d") {
|
|
49
|
+
dirs.push(parsed.name);
|
|
50
|
+
} else if (parsed.fileType === "-" || parsed.fileType === "l") {
|
|
51
|
+
const dot = parsed.name.lastIndexOf(".");
|
|
52
|
+
const ext = dot > 0 ? parsed.name.slice(dot) : "no ext";
|
|
53
|
+
byExt.set(ext, (byExt.get(ext) || 0) + 1);
|
|
54
|
+
files.push([parsed.name, humanSize(parsed.size)]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (dirs.length === 0 && files.length === 0) return input;
|
|
59
|
+
|
|
60
|
+
let out = "";
|
|
61
|
+
for (const d of dirs) out += `${d}/\n`;
|
|
62
|
+
for (const [name, size] of files) out += `${name} ${size}\n`;
|
|
63
|
+
|
|
64
|
+
// Summary line (Rust port)
|
|
65
|
+
let summary = `\nSummary: ${files.length} files, ${dirs.length} dirs`;
|
|
66
|
+
if (byExt.size > 0) {
|
|
67
|
+
const ext = Array.from(byExt.entries()).sort((a, b) => b[1] - a[1]);
|
|
68
|
+
const parts = ext.slice(0, LS_EXT_SUMMARY_TOP).map(([e, c]) => `${c} ${e}`);
|
|
69
|
+
summary += ` (${parts.join(", ")}`;
|
|
70
|
+
if (ext.length > LS_EXT_SUMMARY_TOP) {
|
|
71
|
+
summary += `, +${ext.length - LS_EXT_SUMMARY_TOP} more`;
|
|
72
|
+
}
|
|
73
|
+
summary += ")";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return out + summary;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ls.filterName = "ls";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Handles Cursor/Codex read_file output: " 1|content\n 2|content".
|
|
2
|
+
// Strategy mirrors Rust filter::smart_truncate (filter.rs): keep head+tail, drop middle.
|
|
3
|
+
import { SMART_TRUNCATE_HEAD, SMART_TRUNCATE_TAIL, SMART_TRUNCATE_MIN_LINES } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
const LINE_RE = /^\s*\d+\|/;
|
|
6
|
+
|
|
7
|
+
export function readNumbered(input) {
|
|
8
|
+
const lines = input.split("\n");
|
|
9
|
+
if (lines.length < SMART_TRUNCATE_MIN_LINES) return input;
|
|
10
|
+
|
|
11
|
+
// Count how many lines match "N|content" to verify shape (hit ratio check
|
|
12
|
+
// already done by autodetect; here we just truncate).
|
|
13
|
+
const head = lines.slice(0, SMART_TRUNCATE_HEAD);
|
|
14
|
+
const tail = lines.slice(lines.length - SMART_TRUNCATE_TAIL);
|
|
15
|
+
const cut = lines.length - head.length - tail.length;
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
...head,
|
|
19
|
+
`... +${cut} lines truncated (file continues)`,
|
|
20
|
+
...tail
|
|
21
|
+
].join("\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
readNumbered.filterName = "read-numbered";
|
|
25
|
+
|
|
26
|
+
// Exposed for autodetect
|
|
27
|
+
export const READ_NUMBERED_LINE_RE = LINE_RE;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Compact "Result of search in '...' (total N files):\n- path\n- path" output
|
|
2
|
+
// (Cursor Glob tool). Groups by parent dir like find, shows basenames.
|
|
3
|
+
import { SEARCH_LIST_PER_DIR_MAX, SEARCH_LIST_TOTAL_DIR_MAX } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
const HEADER_RE = /^Result of search in '[^']*' \(total (\d+) files?\):/;
|
|
6
|
+
|
|
7
|
+
export function searchList(input) {
|
|
8
|
+
const lines = input.split("\n");
|
|
9
|
+
if (lines.length === 0) return input;
|
|
10
|
+
|
|
11
|
+
// First line must be the header (validated by autodetect too)
|
|
12
|
+
const header = lines[0] || "";
|
|
13
|
+
const rest = lines.slice(1);
|
|
14
|
+
|
|
15
|
+
const paths = [];
|
|
16
|
+
for (const raw of rest) {
|
|
17
|
+
const t = raw.trim();
|
|
18
|
+
if (!t.startsWith("- ")) continue;
|
|
19
|
+
paths.push(t.slice(2));
|
|
20
|
+
}
|
|
21
|
+
if (paths.length === 0) return input;
|
|
22
|
+
|
|
23
|
+
const byDir = new Map();
|
|
24
|
+
for (const p of paths) {
|
|
25
|
+
const slash = p.lastIndexOf("/");
|
|
26
|
+
const dir = slash === -1 ? "." : (p.slice(0, slash) || "/");
|
|
27
|
+
const name = slash === -1 ? p : p.slice(slash + 1);
|
|
28
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
29
|
+
byDir.get(dir).push(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const dirs = Array.from(byDir.keys()).sort();
|
|
33
|
+
let out = `${header}\n${paths.length} files in ${dirs.length} dirs:\n\n`;
|
|
34
|
+
|
|
35
|
+
for (const dir of dirs.slice(0, SEARCH_LIST_TOTAL_DIR_MAX)) {
|
|
36
|
+
const names = byDir.get(dir);
|
|
37
|
+
out += `${dir}/ (${names.length}):\n`;
|
|
38
|
+
for (const n of names.slice(0, SEARCH_LIST_PER_DIR_MAX)) out += ` ${n}\n`;
|
|
39
|
+
if (names.length > SEARCH_LIST_PER_DIR_MAX) {
|
|
40
|
+
out += ` +${names.length - SEARCH_LIST_PER_DIR_MAX}\n`;
|
|
41
|
+
}
|
|
42
|
+
out += "\n";
|
|
43
|
+
}
|
|
44
|
+
if (dirs.length > SEARCH_LIST_TOTAL_DIR_MAX) {
|
|
45
|
+
out += `+${dirs.length - SEARCH_LIST_TOTAL_DIR_MAX} more dirs\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return out.replace(/\n+$/, "");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
searchList.filterName = "search-list";
|
|
52
|
+
export const SEARCH_LIST_HEADER_RE = HEADER_RE;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Port concept of filter::smart_truncate (rtk/src/core/filter.rs).
|
|
2
|
+
// Keep HEAD + TAIL lines, replace middle with "... +N lines truncated".
|
|
3
|
+
import { SMART_TRUNCATE_HEAD, SMART_TRUNCATE_TAIL, SMART_TRUNCATE_MIN_LINES } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
export function smartTruncate(input) {
|
|
6
|
+
const lines = input.split("\n");
|
|
7
|
+
if (lines.length < SMART_TRUNCATE_MIN_LINES) return input;
|
|
8
|
+
|
|
9
|
+
const head = lines.slice(0, SMART_TRUNCATE_HEAD);
|
|
10
|
+
const tail = lines.slice(lines.length - SMART_TRUNCATE_TAIL);
|
|
11
|
+
const cut = lines.length - head.length - tail.length;
|
|
12
|
+
return [...head, `... +${cut} lines truncated`, ...tail].join("\n");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
smartTruncate.filterName = "smart-truncate";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Port of filter_tree_output (rtk/src/cmds/system/tree.rs:65-94)
|
|
2
|
+
// Removes summary line (e.g. "5 directories, 23 files") and trailing blanks.
|
|
3
|
+
import { TREE_MAX_LINES } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
export function tree(input) {
|
|
6
|
+
const lines = input.split("\n");
|
|
7
|
+
if (lines.length === 0) return input;
|
|
8
|
+
|
|
9
|
+
const filtered = [];
|
|
10
|
+
for (const line of lines) {
|
|
11
|
+
// Drop "X directories, Y files" summary
|
|
12
|
+
if (line.includes("director") && line.includes("file")) continue;
|
|
13
|
+
// Drop leading blanks
|
|
14
|
+
if (line.trim() === "" && filtered.length === 0) continue;
|
|
15
|
+
filtered.push(line);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Drop trailing blanks
|
|
19
|
+
while (filtered.length > 0 && filtered[filtered.length - 1].trim() === "") {
|
|
20
|
+
filtered.pop();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Cap overly long trees (JS-only safeguard; Rust has no cap)
|
|
24
|
+
if (filtered.length > TREE_MAX_LINES) {
|
|
25
|
+
const cut = filtered.length - TREE_MAX_LINES;
|
|
26
|
+
return filtered.slice(0, TREE_MAX_LINES).join("\n") + `\n... +${cut} more lines`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return filtered.join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
tree.filterName = "tree";
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { claudeToOpenAIRequest } from "../translator/request/claude-to-openai.js";
|
|
2
|
+
import { openaiToClaudeRequest } from "../translator/request/openai-to-claude.js";
|
|
3
|
+
import {
|
|
4
|
+
openaiResponsesToOpenAIRequest,
|
|
5
|
+
openaiToOpenAIResponsesRequest,
|
|
6
|
+
} from "../translator/request/openai-responses.js";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
9
|
+
|
|
10
|
+
function jsonBytes(value) {
|
|
11
|
+
try {
|
|
12
|
+
return new TextEncoder().encode(JSON.stringify(value) || "").length;
|
|
13
|
+
} catch {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function messagePayload(body) {
|
|
19
|
+
if (Array.isArray(body?.messages)) return body.messages;
|
|
20
|
+
if (Array.isArray(body?.input)) return body.input;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function captureSizeSnapshot(body) {
|
|
25
|
+
const messages = messagePayload(body);
|
|
26
|
+
return {
|
|
27
|
+
bodyBytes: jsonBytes(body),
|
|
28
|
+
messageBytes: messages ? jsonBytes(messages) : 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setDiagnostic(diagnostics, reason) {
|
|
33
|
+
if (diagnostics && !diagnostics.reason) diagnostics.reason = reason;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function scrubSensitiveUrlText(text) {
|
|
37
|
+
return String(text)
|
|
38
|
+
.replace(/\/\/[^/@\s]+@/g, "//")
|
|
39
|
+
.replace(/(https?:\/\/[^\s?#]+)[?#][^\s)]*/g, "$1");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function describeFetchError(error) {
|
|
43
|
+
const cause = error?.cause;
|
|
44
|
+
const code = cause?.code || error?.code;
|
|
45
|
+
const message = scrubSensitiveUrlText(cause?.message || error?.message || String(error));
|
|
46
|
+
return code ? `${code}: ${message}` : message;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildCompressEndpoint(url) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = new URL(url);
|
|
52
|
+
parsed.pathname = `${parsed.pathname.replace(/\/$/, "")}/v1/compress`;
|
|
53
|
+
parsed.hash = "";
|
|
54
|
+
return parsed.toString();
|
|
55
|
+
} catch {
|
|
56
|
+
const raw = String(url).replace(/#.*$/, "");
|
|
57
|
+
const [base, query = ""] = raw.split("?", 2);
|
|
58
|
+
const endpoint = `${base.replace(/\/$/, "")}/v1/compress`;
|
|
59
|
+
return query ? `${endpoint}?${query}` : endpoint;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function maskEndpoint(endpoint) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(endpoint);
|
|
66
|
+
parsed.username = "";
|
|
67
|
+
parsed.password = "";
|
|
68
|
+
parsed.search = "";
|
|
69
|
+
parsed.hash = "";
|
|
70
|
+
return parsed.toString();
|
|
71
|
+
} catch {
|
|
72
|
+
return String(endpoint).replace(/\/\/[^/@\s]+@/, "//").replace(/[?#].*$/, "");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hasUnsafeResponsesInputForCompression(body) {
|
|
77
|
+
if (!Array.isArray(body?.input)) return false;
|
|
78
|
+
return body.input.some((item) => {
|
|
79
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
80
|
+
return typeof item.type === "string" && item.type !== "message";
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// POST messages to Headroom /v1/compress; returns compressed messages + stats or null.
|
|
85
|
+
async function callCompress(url, messages, model, timeoutMs, compressUserMessages, diagnostics) {
|
|
86
|
+
const endpoint = buildCompressEndpoint(url);
|
|
87
|
+
diagnostics.endpoint = maskEndpoint(endpoint);
|
|
88
|
+
const payload = { messages, model };
|
|
89
|
+
if (compressUserMessages) payload.config = { compress_user_messages: true };
|
|
90
|
+
let res;
|
|
91
|
+
try {
|
|
92
|
+
res = await fetch(endpoint, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify(payload),
|
|
96
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
setDiagnostic(diagnostics, `request failed: ${describeFetchError(error)}`);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
setDiagnostic(diagnostics, `proxy returned HTTP ${res.status}`);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const data = await res.json();
|
|
107
|
+
if (!Array.isArray(data?.messages)) {
|
|
108
|
+
setDiagnostic(diagnostics, "proxy response missing messages[]");
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Compress request body via Headroom proxy. Fail-open: returns null on any error.
|
|
115
|
+
// /v1/compress only understands OpenAI shape, so Claude bodies are translated
|
|
116
|
+
// to OpenAI, compressed, then translated back using 9Router's own translators.
|
|
117
|
+
export async function compressWithHeadroom(body, { enabled, url, model, format, compressUserMessages, timeoutMs = DEFAULT_TIMEOUT_MS, diagnostics = null } = {}) {
|
|
118
|
+
if (!enabled) {
|
|
119
|
+
setDiagnostic(diagnostics, "disabled");
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (!url) {
|
|
123
|
+
setDiagnostic(diagnostics, "missing proxy URL");
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (!body) {
|
|
127
|
+
setDiagnostic(diagnostics, "missing request body");
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (diagnostics) diagnostics.before = captureSizeSnapshot(body);
|
|
133
|
+
|
|
134
|
+
// Claude shape: translate → OpenAI → compress → translate back.
|
|
135
|
+
if (format === "claude") {
|
|
136
|
+
const oai = claudeToOpenAIRequest(model, body, false);
|
|
137
|
+
if (!Array.isArray(oai?.messages)) {
|
|
138
|
+
setDiagnostic(diagnostics, "Claude request did not translate to messages[]");
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const data = await callCompress(url, oai.messages, model, timeoutMs, compressUserMessages, diagnostics || {});
|
|
142
|
+
if (!data) return null;
|
|
143
|
+
const claudeBody = openaiToClaudeRequest(model, { ...oai, messages: data.messages }, false);
|
|
144
|
+
if (Array.isArray(claudeBody?.messages)) body.messages = claudeBody.messages;
|
|
145
|
+
if (claudeBody?.system !== undefined) body.system = claudeBody.system;
|
|
146
|
+
if (diagnostics) diagnostics.after = captureSizeSnapshot(body);
|
|
147
|
+
return data;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// OpenAI Responses shape (Codex): body.input holds Responses items, NOT OpenAI
|
|
151
|
+
// messages. Translate input -> OpenAI -> compress -> translate back to input so
|
|
152
|
+
// body.input keeps the Responses contract (the proxy only understands OpenAI). (#1998)
|
|
153
|
+
if (format === "openai-responses") {
|
|
154
|
+
if (hasUnsafeResponsesInputForCompression(body)) {
|
|
155
|
+
setDiagnostic(diagnostics, "skipped: openai-responses tool/reasoning input is not safe to compress");
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const oai = openaiResponsesToOpenAIRequest(model, body, false);
|
|
159
|
+
if (!Array.isArray(oai?.messages)) return null;
|
|
160
|
+
const data = await callCompress(url, oai.messages, model, timeoutMs, compressUserMessages, diagnostics || {});
|
|
161
|
+
if (!data) return null;
|
|
162
|
+
// input: undefined so the translator rebuilds input from the compressed
|
|
163
|
+
// messages instead of returning the original input unchanged.
|
|
164
|
+
const responsesBody = openaiToOpenAIResponsesRequest(
|
|
165
|
+
model,
|
|
166
|
+
{ ...oai, input: undefined, messages: data.messages },
|
|
167
|
+
false
|
|
168
|
+
);
|
|
169
|
+
if (Array.isArray(responsesBody?.input)) body.input = responsesBody.input;
|
|
170
|
+
if (diagnostics) diagnostics.after = captureSizeSnapshot(body);
|
|
171
|
+
return data;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// OpenAI shape: messages/input go straight to the proxy.
|
|
175
|
+
const key = Array.isArray(body.messages) ? "messages"
|
|
176
|
+
: Array.isArray(body.input) ? "input"
|
|
177
|
+
: null;
|
|
178
|
+
if (!key) {
|
|
179
|
+
setDiagnostic(diagnostics, `unsupported ${format || "unknown"} request shape`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const data = await callCompress(url, body[key], model, timeoutMs, compressUserMessages, diagnostics || {});
|
|
183
|
+
if (!data) return null;
|
|
184
|
+
body[key] = data.messages;
|
|
185
|
+
if (diagnostics) diagnostics.after = captureSizeSnapshot(body);
|
|
186
|
+
return data;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
setDiagnostic(diagnostics, `unexpected error: ${error?.message || String(error)}`);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function formatHeadroomLog(stats) {
|
|
194
|
+
if (!stats) return null;
|
|
195
|
+
const before = stats.tokens_before || 0;
|
|
196
|
+
const after = stats.tokens_after || 0;
|
|
197
|
+
const delta = stats.tokens_saved || 0;
|
|
198
|
+
const pct = before > 0 ? ((delta / before) * 100).toFixed(1) : "0";
|
|
199
|
+
return `reported token delta=${delta} before=${before}${after ? ` after=${after}` : ""} (${pct}%)`.trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function formatHeadroomSizeLog(diagnostics) {
|
|
203
|
+
const before = diagnostics?.before;
|
|
204
|
+
const after = diagnostics?.after;
|
|
205
|
+
if (!before || !after) return "";
|
|
206
|
+
return `body=${before.bodyBytes}B→${after.bodyBytes}B messages=${before.messageBytes}B→${after.messageBytes}B`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function isHeadroomPhantomSavings(stats, diagnostics, minShrinkRatio = 0.05) {
|
|
210
|
+
if (!stats?.tokens_saved || stats.tokens_saved <= 0) return false;
|
|
211
|
+
const before = diagnostics?.before?.bodyBytes || 0;
|
|
212
|
+
const after = diagnostics?.after?.bodyBytes || 0;
|
|
213
|
+
if (before <= 0 || after <= 0) return false;
|
|
214
|
+
return after >= before * (1 - minShrinkRatio);
|
|
215
|
+
}
|