merge: resolve conflicts with origin/master - keep both local and remote features

This commit is contained in:
2026-06-22 11:40:16 +07:00
424 changed files with 24520 additions and 11731 deletions

View File

@@ -0,0 +1,235 @@
{
"aliasToId": {
"cc": "claude",
"cx": "codex",
"gc": "gemini-cli",
"qw": "qwen",
"if": "iflow",
"ag": "antigravity",
"gh": "github",
"kr": "kiro",
"cu": "cursor",
"kc": "kilocode",
"kmc": "kimi-coding",
"cl": "cline",
"oc": "opencode",
"ocg": "opencode-go",
"qd": "qoder",
"qoder": "qoder",
"el": "elevenlabs",
"openai": "openai",
"vercel": "vercel-ai-gateway",
"vercel-ai-gateway": "vercel-ai-gateway",
"anthropic": "anthropic",
"gemini": "gemini",
"openrouter": "openrouter",
"glm": "glm",
"kimi": "kimi",
"minimax": "minimax",
"minimax-cn": "minimax-cn",
"hf": "huggingface",
"huggingface": "huggingface",
"ds": "deepseek",
"deepseek": "deepseek",
"cmc": "commandcode",
"commandcode": "commandcode",
"groq": "groq",
"xai": "xai",
"mistral": "mistral",
"pplx": "perplexity",
"perplexity": "perplexity",
"together": "together",
"fireworks": "fireworks",
"cerebras": "cerebras",
"cohere": "cohere",
"nvidia": "nvidia",
"nebius": "nebius",
"siliconflow": "siliconflow",
"hyp": "hyperbolic",
"hyperbolic": "hyperbolic",
"dg": "deepgram",
"deepgram": "deepgram",
"aai": "assemblyai",
"assemblyai": "assemblyai",
"nb": "nanobanana",
"nanobanana": "nanobanana",
"ch": "chutes",
"chutes": "chutes",
"ark": "volcengine-ark",
"volcengine-ark": "volcengine-ark",
"byteplus": "byteplus",
"bpm": "byteplus",
"cursor": "cursor",
"vx": "vertex",
"vertex": "vertex",
"vxp": "vertex-partner",
"vertex-partner": "vertex-partner",
"gw": "grok-web",
"grok-web": "grok-web",
"pw": "perplexity-web",
"perplexity-web": "perplexity-web",
"mimo": "xiaomi-mimo",
"xiaomi-mimo": "xiaomi-mimo",
"xmtp": "xiaomi-tokenplan",
"xiaomi-tokenplan": "xiaomi-tokenplan",
"cf": "cloudflare-ai",
"cloudflare-ai": "cloudflare-ai",
"fal": "fal-ai",
"fal-ai": "fal-ai",
"stability": "stability-ai",
"stability-ai": "stability-ai",
"bfl": "black-forest-labs",
"black-forest-labs": "black-forest-labs",
"recraft": "recraft",
"topaz": "topaz",
"runway": "runwayml",
"runwayml": "runwayml",
"jina": "jina-ai",
"jina-ai": "jina-ai",
"polly": "aws-polly",
"aws-polly": "aws-polly",
"bb": "blackbox",
"blackbox": "blackbox"
},
"idToAlias": {
"alicode": "alicode",
"alicode-intl": "alicode-intl",
"anthropic": "anthropic",
"antigravity": "ag",
"assemblyai": "assemblyai",
"azure": "azure",
"blackbox": "blackbox",
"byteplus": "byteplus",
"cerebras": "cerebras",
"chutes": "chutes",
"claude": "cc",
"cline": "cl",
"cloudflare-ai": "cloudflare-ai",
"codebuddy-cn": "cbcn",
"codex": "cx",
"cohere": "cohere",
"commandcode": "commandcode",
"cursor": "cu",
"deepgram": "deepgram",
"deepseek": "deepseek",
"fireworks": "fireworks",
"gemini": "gemini",
"gemini-cli": "gc",
"github": "gh",
"gitlab": "gitlab",
"glm": "glm",
"glm-cn": "glm-cn",
"grok-web": "grok-web",
"groq": "groq",
"hyperbolic": "hyperbolic",
"iflow": "if",
"kilocode": "kc",
"kimi": "kimi",
"kimi-coding": "kmc",
"kiro": "kr",
"mimo-free": "mmf",
"minimax": "minimax",
"minimax-cn": "minimax-cn",
"mistral": "mistral",
"mmf": "mmf",
"nanobanana": "nanobanana",
"nebius": "nebius",
"nvidia": "nvidia",
"ollama": "ollama",
"ollama-local": "ollama-local",
"openai": "openai",
"opencode": "oc",
"opencode-go": "opencode-go",
"openrouter": "openrouter",
"perplexity": "perplexity",
"perplexity-web": "perplexity-web",
"qoder": "qd",
"qwen": "qw",
"siliconflow": "siliconflow",
"together": "together",
"vercel-ai-gateway": "vercel-ai-gateway",
"vertex": "vertex",
"vertex-partner": "vertex-partner",
"volcengine-ark": "volcengine-ark",
"xai": "xai",
"xiaomi-mimo": "xiaomi-mimo",
"xiaomi-tokenplan": "xiaomi-tokenplan"
},
"modelKeys": [
"ag",
"alicode",
"alicode-intl",
"anthropic",
"assemblyai",
"black-forest-labs",
"blackbox",
"byteplus",
"cbcn",
"cc",
"cerebras",
"cl",
"cloudflare-ai",
"cohere",
"comfyui",
"commandcode",
"cu",
"cx",
"deepgram",
"deepseek",
"edge-tts",
"elevenlabs-tts-models",
"fal-ai",
"fireworks",
"gc",
"gemini",
"gemini-tts-models",
"gemini-tts-voices",
"gh",
"glm",
"glm-cn",
"google-tts",
"grok-web",
"groq",
"huggingface",
"hyperbolic",
"if",
"kc",
"kimi",
"kmc",
"kr",
"local-device",
"minimax",
"minimax-cn",
"mistral",
"mmf",
"nanobanana",
"nebius",
"nvidia",
"oc",
"ollama",
"openai",
"openai-tts-models",
"openai-tts-voices",
"opencode-go",
"openrouter",
"openrouter-tts-models",
"openrouter-tts-voices",
"perplexity",
"perplexity-web",
"qd",
"qw",
"recraft",
"runwayml",
"sdwebui",
"siliconflow",
"stability-ai",
"together",
"vertex",
"vertex-partner",
"volcengine-ark",
"voyage-ai",
"xai",
"xiaomi-mimo",
"xiaomi-tokenplan"
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
tests/unit/antigravity-mitm.test.js :: Antigravity MITM model handling flags the out-of-box agent/Default model mandatory
tests/unit/claude-header-forwarding.test.js :: proxyAwareFetch — api.anthropic.com routing routes api.anthropic.com to gotScraping (non-streaming) and returns ok response
tests/unit/kiro-model-slots.test.js :: Kiro MITM model slots offers a mappable slot for the agent default model id 'auto'
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import extracts tokens using exact keys
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import falls back to fuzzy key matching on macOS when exact keys are missing
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import linux uses single hardcoded path and original error message
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import returns descriptive error if macOS db file exists but cannot be opened
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import returns login-prompt error when tokens are missing even after fallback
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import returns not-found when no macOS cursor db paths are accessible
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import unsupported platform returns 400
tests/unit/oauth-cursor-auto-import.test.js :: GET /api/oauth/cursor/auto-import unwraps JSON-encoded string values
tests/unit/openai-to-claude.test.js :: openaiToClaudeResponse omits empty Read pages tool argument before emitting Claude input deltas
tests/unit/rtk.test.js :: RTK flag default off, toggle works
tests/unit/rtk.test.js :: compressMessages (disabled) returns null when disabled
tests/unit/rtk.test.js :: compressMessages (enabled) compresses Claude array-form tool_result text parts
tests/unit/rtk.test.js :: compressMessages (enabled) compresses Claude string-form tool_result
tests/unit/rtk.test.js :: compressMessages (enabled) compresses OpenAI tool message (string content)
tests/unit/rtk.test.js :: compressMessages (enabled) handles mix of messages without crashing
tests/unit/rtk.test.js :: compressMessages (enabled) never produces empty content (R14 guard)
tests/unit/rtk.test.js :: compressMessages (enabled) skips below MIN_COMPRESS_SIZE (<500 bytes)
tests/unit/rtk.test.js :: compressMessages (enabled) skips is_error tool_result
tests/unit/rtk.test.js :: compressMessages (enabled) skips when body has no messages
tests/unit/translator-request-normalization.test.js :: request normalization claudeToOpenAIRequest flattens text-only content arrays into string
tests/unit/translator-request-normalization.test.js :: request normalization filterToOpenAIFormat flattens text-only arrays to string
tests/unit/translator-request-normalization.test.js :: request normalization parseSSELine supports provider raw NDJSON stream lines
tests/unit/translator-request-normalization.test.js :: request normalization translateRequest keeps /v1/messages Claude->OpenAI text payloads string-safe

View File

@@ -0,0 +1,56 @@
{
"oauthEndpoints": {
"google": {
"token": "https://oauth2.googleapis.com/token",
"auth": "https://accounts.google.com/o/oauth2/auth"
},
"openai": {
"token": "https://auth.openai.com/oauth/token",
"auth": "https://auth.openai.com/oauth/authorize"
},
"anthropic": {
"token": "https://api.anthropic.com/v1/oauth/token",
"auth": "https://api.anthropic.com/v1/oauth/authorize"
},
"qwen": {
"token": "https://qwen.ai/api/v1/oauth2/token",
"auth": "https://qwen.ai/api/v1/oauth2/device/code"
},
"iflow": {
"token": "https://iflow.cn/oauth/token",
"auth": "https://iflow.cn/oauth"
},
"github": {
"token": "https://github.com/login/oauth/access_token",
"auth": "https://github.com/login/oauth/authorize",
"deviceCode": "https://github.com/login/device/code"
}
},
"tokenUrls": {
"claude": "https://api.anthropic.com/v1/oauth/token",
"codex": "https://auth.openai.com/oauth/token",
"qwen": "https://chat.qwen.ai/api/v1/oauth2/token",
"iflow": "https://iflow.cn/oauth/token",
"kiro": "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
"xai": "https://auth.x.ai/oauth2/token",
"cline": "https://api.cline.bot/api/v1/auth/token",
"kimi-coding": "https://auth.kimi.com/api/oauth/token"
},
"authUrls": {
"qwen": "https://chat.qwen.ai/api/v1/oauth2/device/code",
"iflow": "https://iflow.cn/oauth",
"kiro": "https://prod.us-east-1.auth.desktop.kiro.dev"
},
"refreshUrls": {
"cline": "https://api.cline.bot/api/v1/auth/refresh",
"kimi-coding": "https://auth.kimi.com/api/oauth/token",
"xai": "https://auth.x.ai/oauth2/token"
},
"clientIds": {
"claude": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
"codex": "app_EMoamEEZ73f0CkXaXp7hrann",
"qwen": "f0304373b74a44d2b584a3fb70ca9e56",
"iflow": "10009311001",
"kimi-coding": "17e5f671-d194-4dfb-9706-5516cb48c098"
}
}

View File

@@ -0,0 +1,402 @@
{
"claude": {
"baseUrl": "https://api.anthropic.com/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20,effort-2025-11-24,structured-outputs-2025-12-15,fast-mode-2026-02-01,redact-thinking-2026-02-12,token-efficient-tools-2026-03-28",
"Anthropic-Dangerous-Direct-Browser-Access": "true",
"User-Agent": "claude-cli/2.1.92 (external, sdk-cli)",
"X-App": "cli",
"X-Stainless-Helper-Method": "stream",
"X-Stainless-Retry-Count": "0",
"X-Stainless-Runtime-Version": "v24.14.0",
"X-Stainless-Package-Version": "0.80.0",
"X-Stainless-Runtime": "node",
"X-Stainless-Lang": "js",
"X-Stainless-Arch": "arm64",
"X-Stainless-Os": "MacOS",
"X-Stainless-Timeout": "600"
},
"clientId": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
"tokenUrl": "https://api.anthropic.com/v1/oauth/token"
},
"gemini": {
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/models",
"format": "gemini",
"clientId": "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
"clientSecret": "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"
},
"gemini-cli": {
"baseUrl": "https://cloudcode-pa.googleapis.com/v1internal",
"format": "gemini-cli",
"clientId": "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
"clientSecret": "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"
},
"codex": {
"baseUrl": "https://chatgpt.com/backend-api/codex/responses",
"format": "openai-responses",
"headers": {
"originator": "codex_cli_rs",
"User-Agent": "codex_cli_rs/0.136.0"
},
"clientId": "app_EMoamEEZ73f0CkXaXp7hrann",
"tokenUrl": "https://auth.openai.com/oauth/token"
},
"qwen": {
"baseUrl": "https://portal.qwen.ai/v1/chat/completions",
"format": "openai",
"clientId": "f0304373b74a44d2b584a3fb70ca9e56",
"tokenUrl": "https://chat.qwen.ai/api/v1/oauth2/token",
"authUrl": "https://chat.qwen.ai/api/v1/oauth2/device/code"
},
"iflow": {
"baseUrl": "https://apis.iflow.cn/v1/chat/completions",
"format": "openai",
"headers": {
"User-Agent": "iFlow-Cli"
},
"clientId": "10009311001",
"clientSecret": "4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW",
"tokenUrl": "https://iflow.cn/oauth/token",
"authUrl": "https://iflow.cn/oauth"
},
"qoder": {
"baseUrl": "https://api3.qoder.sh/algo/api/v2/service/pro/sse/agent_chat_generation",
"format": "openai",
"headers": {},
"timeoutMs": 120000,
"stallTimeoutMs": 120000
},
"antigravity": {
"baseUrls": [
"https://daily-cloudcode-pa.googleapis.com",
"https://daily-cloudcode-pa.sandbox.googleapis.com"
],
"format": "antigravity",
"headers": {
"User-Agent": "antigravity/1.107.0 darwin/arm64"
},
"clientId": "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
"clientSecret": "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"
},
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1/chat/completions",
"format": "openai",
"headers": {
"HTTP-Referer": "https://endpoint-proxy.local",
"X-Title": "Endpoint Proxy"
}
},
"openai": {
"baseUrl": "https://api.openai.com/v1/chat/completions",
"format": "openai"
},
"vercel-ai-gateway": {
"baseUrl": "https://ai-gateway.vercel.sh/v1/chat/completions",
"format": "openai",
"retry": {
"429": 2
}
},
"glm": {
"baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"glm-cn": {
"baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
"format": "openai",
"headers": {}
},
"kimi": {
"baseUrl": "https://api.kimi.com/coding/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"minimax": {
"baseUrl": "https://api.minimax.io/anthropic/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"minimax-cn": {
"baseUrl": "https://api.minimaxi.com/anthropic/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"alicode": {
"baseUrl": "https://coding.dashscope.aliyuncs.com/v1/chat/completions",
"format": "openai",
"headers": {}
},
"alicode-intl": {
"baseUrl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions",
"format": "openai",
"headers": {}
},
"volcengine-ark": {
"baseUrl": "https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
"format": "openai",
"headers": {}
},
"byteplus": {
"baseUrl": "https://ark.ap-southeast.bytepluses.com/api/coding/v3/chat/completions",
"format": "openai",
"headers": {}
},
"github": {
"baseUrl": "https://api.githubcopilot.com/chat/completions",
"responsesUrl": "https://api.githubcopilot.com/responses",
"format": "openai",
"headers": {
"copilot-integration-id": "vscode-chat",
"editor-version": "vscode/1.110.0",
"editor-plugin-version": "copilot-chat/0.38.0",
"user-agent": "GitHubCopilotChat/0.38.0",
"openai-intent": "conversation-panel",
"x-github-api-version": "2025-04-01",
"x-vscode-user-agent-library-version": "electron-fetch",
"X-Initiator": "user",
"Accept": "application/json",
"Content-Type": "application/json"
},
"clientId": "Iv1.b507a08c87ecfe98"
},
"kiro": {
"baseUrl": "https://runtime.us-east-1.kiro.dev/generateAssistantResponse",
"baseUrls": [
"https://runtime.us-east-1.kiro.dev/generateAssistantResponse",
"https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse",
"https://q.us-east-1.amazonaws.com/generateAssistantResponse"
],
"format": "kiro",
"retry": {
"429": 0
},
"headers": {
"Content-Type": "application/json",
"Accept": "application/vnd.amazon.eventstream",
"X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
"User-Agent": "AWS-SDK-JS/3.0.0 kiro-ide/1.0.0",
"X-Amz-User-Agent": "aws-sdk-js/3.0.0 kiro-ide/1.0.0"
},
"tokenUrl": "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
"authUrl": "https://prod.us-east-1.auth.desktop.kiro.dev"
},
"cursor": {
"baseUrl": "https://api2.cursor.sh",
"chatPath": "/aiserver.v1.ChatService/StreamUnifiedChatWithTools",
"format": "cursor",
"headers": {
"connect-accept-encoding": "gzip",
"connect-protocol-version": "1",
"Content-Type": "application/connect+proto",
"User-Agent": "connect-es/1.6.1"
},
"clientVersion": "3.1.0"
},
"kimi-coding": {
"baseUrl": "https://api.kimi.com/coding/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
},
"clientId": "17e5f671-d194-4dfb-9706-5516cb48c098",
"tokenUrl": "https://auth.kimi.com/api/oauth/token",
"refreshUrl": "https://auth.kimi.com/api/oauth/token"
},
"kilocode": {
"baseUrl": "https://api.kilo.ai/api/openrouter/chat/completions",
"format": "openai",
"headers": {}
},
"opencode": {
"baseUrl": "https://opencode.ai",
"format": "openai",
"headers": {
"x-opencode-client": "desktop"
},
"noAuth": true
},
"cline": {
"baseUrl": "https://api.cline.bot/api/v1/chat/completions",
"format": "openai",
"headers": {
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline"
},
"tokenUrl": "https://api.cline.bot/api/v1/auth/token",
"refreshUrl": "https://api.cline.bot/api/v1/auth/refresh"
},
"nvidia": {
"baseUrl": "https://integrate.api.nvidia.com/v1/chat/completions",
"format": "openai"
},
"anthropic": {
"baseUrl": "https://api.anthropic.com/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"deepseek": {
"baseUrl": "https://api.deepseek.com/chat/completions",
"format": "openai"
},
"commandcode": {
"baseUrl": "https://api.commandcode.ai/alpha/generate",
"format": "commandcode",
"headers": {
"x-command-code-version": "0.25.7",
"x-cli-environment": "cli"
}
},
"groq": {
"baseUrl": "https://api.groq.com/openai/v1/chat/completions",
"format": "openai"
},
"xai": {
"baseUrl": "https://api.x.ai/v1/chat/completions",
"responsesUrl": "https://api.x.ai/v1/responses",
"format": "openai",
"clientId": "b1a00492-073a-47ea-816f-4c329264a828",
"tokenUrl": "https://auth.x.ai/oauth2/token",
"refreshUrl": "https://auth.x.ai/oauth2/token"
},
"mistral": {
"baseUrl": "https://api.mistral.ai/v1/chat/completions",
"format": "openai"
},
"perplexity": {
"baseUrl": "https://api.perplexity.ai/chat/completions",
"format": "openai"
},
"together": {
"baseUrl": "https://api.together.xyz/v1/chat/completions",
"format": "openai"
},
"fireworks": {
"baseUrl": "https://api.fireworks.ai/inference/v1/chat/completions",
"format": "openai"
},
"cerebras": {
"baseUrl": "https://api.cerebras.ai/v1/chat/completions",
"format": "openai"
},
"cohere": {
"baseUrl": "https://api.cohere.ai/v1/chat/completions",
"format": "openai"
},
"nebius": {
"baseUrl": "https://api.studio.nebius.ai/v1/chat/completions",
"format": "openai"
},
"siliconflow": {
"baseUrl": "https://api.siliconflow.com/v1/chat/completions",
"format": "openai"
},
"hyperbolic": {
"baseUrl": "https://api.hyperbolic.xyz/v1/chat/completions",
"format": "openai"
},
"deepgram": {
"baseUrl": "https://api.deepgram.com/v1/listen",
"format": "openai"
},
"assemblyai": {
"baseUrl": "https://api.assemblyai.com/v1/audio/transcriptions",
"format": "openai"
},
"nanobanana": {
"baseUrl": "https://api.nanobananaapi.ai/v1/chat/completions",
"format": "openai"
},
"chutes": {
"baseUrl": "https://llm.chutes.ai/v1/chat/completions",
"format": "openai"
},
"ollama": {
"baseUrl": "https://ollama.com/api/chat",
"format": "ollama"
},
"ollama-local": {
"baseUrl": "http://localhost:11434/api/chat",
"format": "ollama"
},
"vertex": {
"baseUrl": "https://aiplatform.googleapis.com",
"format": "vertex"
},
"vertex-partner": {
"baseUrl": "https://aiplatform.googleapis.com",
"format": "openai"
},
"gitlab": {
"baseUrl": "https://gitlab.com/api/v4/chat/completions",
"format": "openai"
},
"codebuddy-cn": {
"baseUrl": "https://copilot.tencent.com/v2/chat/completions",
"format": "openai"
},
"opencode-go": {
"baseUrl": "https://opencode.ai/zen/go/v1/chat/completions",
"format": "openai",
"headers": {}
},
"grok-web": {
"baseUrl": "https://grok.com/rest/app-chat/conversations/new",
"format": "grok-web",
"authType": "cookie"
},
"perplexity-web": {
"baseUrl": "https://www.perplexity.ai/rest/sse/perplexity_ask",
"format": "perplexity-web",
"authType": "cookie"
},
"azure": {
"baseUrl": "",
"format": "openai",
"headers": {}
},
"cloudflare-ai": {
"baseUrl": "https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/v1/chat/completions",
"format": "openai"
},
"xiaomi-mimo": {
"baseUrl": "https://api.xiaomimimo.com/v1/chat/completions",
"format": "openai"
},
"mimo-free": {
"baseUrl": "https://api.xiaomimimo.com/api/free-ai/openai/chat",
"format": "openai",
"noAuth": true
},
"mmf": {
"baseUrl": "https://api.xiaomimimo.com/api/free-ai/openai/chat",
"format": "openai",
"noAuth": true
},
"xiaomi-tokenplan": {
"baseUrl": "https://token-plan-sgp.xiaomimimo.com/v1/chat/completions",
"format": "openai"
},
"blackbox": {
"baseUrl": "https://api.blackbox.ai/chat/completions",
"format": "openai"
}
}

View File

@@ -0,0 +1,11 @@
// Snapshot current PROVIDERS output to JSON (run on OLD code before refactor).
// Usage: node tests/__baseline__/snapshot-providers.mjs
import { writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { PROVIDERS } from "../../open-sse/config/providers.js";
const here = dirname(fileURLToPath(import.meta.url));
const out = join(here, "providers-baseline.json");
writeFileSync(out, JSON.stringify(PROVIDERS, null, 2));
console.log(`Snapshot ${Object.keys(PROVIDERS).length} providers → ${out}`);

View File

@@ -0,0 +1,54 @@
// Verify alias resolution is byte-for-byte stable (both directions, all sources).
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { resolveProviderAlias } from "../../open-sse/services/model.js";
import { PROVIDER_ID_TO_ALIAS, PROVIDER_MODELS } from "../../open-sse/config/providerModels.js";
const here = dirname(fileURLToPath(import.meta.url));
const snapPath = join(here, "alias-baseline.json");
// All known alias tokens to probe (collected from both maps' historical keys)
const ALIAS_TOKENS = [
"cc","cx","gc","qw","if","ag","gh","kr","cu","kc","kmc","cl","oc","ocg","qd","qoder",
"el","openai","vercel","vercel-ai-gateway","anthropic","gemini","openrouter","glm","kimi",
"minimax","minimax-cn","hf","huggingface","ds","deepseek","cmc","commandcode","groq","xai",
"mistral","pplx","perplexity","together","fireworks","cerebras","cohere","nvidia","nebius",
"siliconflow","hyp","hyperbolic","dg","deepgram","aai","assemblyai","nb","nanobanana","ch",
"chutes","ark","volcengine-ark","byteplus","bpm","cursor","vx","vertex","vxp","vertex-partner",
"gw","grok-web","pw","perplexity-web","mimo","xiaomi-mimo","xmtp","xiaomi-tokenplan","cf",
"cloudflare-ai","fal","fal-ai","stability","stability-ai","bfl","black-forest-labs","recraft",
"topaz","runway","runwayml","jina","jina-ai","polly","aws-polly","bb","blackbox",
];
// Sort idToAlias by key — runtime accesses by key, order is irrelevant (content-based)
const sortedIdToAlias = Object.fromEntries(
Object.keys(PROVIDER_ID_TO_ALIAS).sort().map(k => [k, PROVIDER_ID_TO_ALIAS[k]])
);
const resolved = {
aliasToId: Object.fromEntries(ALIAS_TOKENS.map(a => [a, resolveProviderAlias(a)])),
idToAlias: sortedIdToAlias,
modelKeys: Object.keys(PROVIDER_MODELS).sort(),
};
const current = JSON.parse(JSON.stringify(resolved));
if (process.argv[2] === "--snapshot") {
writeFileSync(snapPath, JSON.stringify(current, null, 2));
console.log(`Snapshot alias resolution → ${snapPath}`);
process.exit(0);
}
if (!existsSync(snapPath)) { console.error("No baseline. Run --snapshot first."); process.exit(1); }
const baseline = JSON.parse(readFileSync(snapPath, "utf8"));
if (JSON.stringify(baseline) === JSON.stringify(current)) {
console.log(`✅ Alias resolution byte-for-byte equal (${ALIAS_TOKENS.length} tokens).`);
process.exit(0);
}
// Diff
for (const a of ALIAS_TOKENS) {
if (baseline.aliasToId[a] !== current.aliasToId[a]) {
console.error(`~ alias ${a}: ${baseline.aliasToId[a]} -> ${current.aliasToId[a]}`);
}
}
if (JSON.stringify(baseline.idToAlias) !== JSON.stringify(current.idToAlias)) console.error("~ idToAlias changed");
if (JSON.stringify(baseline.modelKeys) !== JSON.stringify(current.modelKeys)) console.error("~ modelKeys changed");
process.exit(1);

View File

@@ -0,0 +1,28 @@
// Gate: so kết quả test hiện tại với baseline known-fails.
// PASS nếu KHÔNG có test nào pass(baseline) → fail(now). Test mới được phép.
// Usage: node tests/__baseline__/verify-no-regression.mjs <current-results.json>
import { readFileSync } from "fs";
const knownFails = new Set(
readFileSync(new URL("./known-fails.txt", import.meta.url), "utf8")
.split("\n").map(s => s.trim()).filter(Boolean)
);
const resultsPath = process.argv[2];
if (!resultsPath) { console.error("Missing results.json path"); process.exit(2); }
const r = JSON.parse(readFileSync(resultsPath, "utf8"));
const nowFails = r.testResults.flatMap(f =>
f.assertionResults.filter(a => a.status === "failed")
.map(a => f.name.split("/app/")[1] + " :: " + a.fullName)
);
// Regression = fail bây giờ NHƯNG không có trong baseline known-fails
const regressions = nowFails.filter(f => !knownFails.has(f));
if (regressions.length) {
console.error(`\n❌ REGRESSION: ${regressions.length} test pass→fail:\n`);
regressions.forEach(f => console.error(" - " + f));
process.exit(1);
}
console.log(`✅ No regression. (now fails=${nowFails.length}, baseline known=${knownFails.size}, all known)`);

View File

@@ -0,0 +1,66 @@
// Verify OAuth/token URLs resolve byte-for-byte vs snapshot (backend open-sse).
// Captures every URL executors/tokenRefresh actually use, to guard DRY dedup.
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { OAUTH_ENDPOINTS } from "../../open-sse/config/appConstants.js";
import { PROVIDERS } from "../../open-sse/config/providers.js";
const here = dirname(fileURLToPath(import.meta.url));
const snapPath = join(here, "oauth-urls-baseline.json");
// Collect resolved URLs that backend code depends on
const resolved = {
oauthEndpoints: OAUTH_ENDPOINTS,
tokenUrls: {
claude: PROVIDERS.claude?.tokenUrl,
codex: PROVIDERS.codex?.tokenUrl,
qwen: PROVIDERS.qwen?.tokenUrl,
iflow: PROVIDERS.iflow?.tokenUrl,
kiro: PROVIDERS.kiro?.tokenUrl,
xai: PROVIDERS.xai?.tokenUrl,
cline: PROVIDERS.cline?.tokenUrl,
"kimi-coding": PROVIDERS["kimi-coding"]?.tokenUrl,
},
authUrls: {
qwen: PROVIDERS.qwen?.authUrl,
iflow: PROVIDERS.iflow?.authUrl,
kiro: PROVIDERS.kiro?.authUrl,
},
refreshUrls: {
cline: PROVIDERS.cline?.refreshUrl,
"kimi-coding": PROVIDERS["kimi-coding"]?.refreshUrl,
xai: PROVIDERS.xai?.refreshUrl,
},
clientIds: {
claude: PROVIDERS.claude?.clientId,
codex: PROVIDERS.codex?.clientId,
qwen: PROVIDERS.qwen?.clientId,
iflow: PROVIDERS.iflow?.clientId,
"kimi-coding": PROVIDERS["kimi-coding"]?.clientId,
},
};
const current = JSON.parse(JSON.stringify(resolved));
const mode = process.argv[2];
if (mode === "--snapshot") {
writeFileSync(snapPath, JSON.stringify(current, null, 2));
console.log(`Snapshot OAuth URLs → ${snapPath}`);
process.exit(0);
}
if (!existsSync(snapPath)) {
console.error("No baseline. Run with --snapshot on OLD code first.");
process.exit(1);
}
const baseline = JSON.parse(readFileSync(snapPath, "utf8"));
const a = JSON.stringify(baseline);
const b = JSON.stringify(current);
if (a === b) {
console.log("✅ OAuth URLs byte-for-byte equal.");
process.exit(0);
}
console.error("❌ OAuth URL mismatch:");
console.error("baseline:", a);
console.error("current :", b);
process.exit(1);

View File

@@ -0,0 +1,46 @@
// Verify refactored PROVIDERS is byte-for-byte equal to baseline JSON.
// Exit 1 + print precise per-provider/per-field diff on mismatch.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { PROVIDERS } from "../../open-sse/config/providers.js";
const here = dirname(fileURLToPath(import.meta.url));
const baseline = JSON.parse(readFileSync(join(here, "providers-baseline.json"), "utf8"));
// Fields intentionally added during refactor (verified by dedicated runtime tests, not byte-baseline).
// authUrl: removed dead field (qwen/iflow) — no consumer reads config.authUrl (oauth block has authorize/deviceCode)
const ADDED_FIELDS = new Set(["forceStream", "urlSuffix", "retry", "quirks", "auth", "validateUrl", "usage", "clientId", "clientSecret", "tokenUrl", "cliVersion", "apiClient", "copilot", "authorizeUrl", "authUrl", "regions", "defaultRegion", "reasoningInject", "priority", "hasFree"]);
// Normalize via JSON roundtrip so function/undefined are dropped identically; drop added/removed fields.
// ADDED_FIELDS are verified by dedicated runtime tests, so drop them from BOTH sides (added or intentionally removed).
const current = JSON.parse(JSON.stringify(PROVIDERS));
for (const f of ADDED_FIELDS) {
for (const id of Object.keys(current)) delete current[id][f];
for (const id of Object.keys(baseline)) delete baseline[id][f];
}
const diffs = [];
const allIds = new Set([...Object.keys(baseline), ...Object.keys(current)]);
for (const id of allIds) {
const a = baseline[id];
const b = current[id];
if (a === undefined) { diffs.push(`+ provider added: ${id}`); continue; }
if (b === undefined) { diffs.push(`- provider removed: ${id}`); continue; }
const sa = JSON.stringify(a);
const sb = JSON.stringify(b);
if (sa === sb) continue;
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const k of keys) {
const va = JSON.stringify(a[k]);
const vb = JSON.stringify(b[k]);
if (va !== vb) diffs.push(`~ ${id}.${k}: ${va} -> ${vb}`);
}
}
if (diffs.length) {
console.error(`❌ PROVIDERS mismatch (${diffs.length} field diffs):`);
for (const d of diffs) console.error(" " + d);
process.exit(1);
}
console.log(`✅ PROVIDERS byte-for-byte equal (${Object.keys(current).length} providers).`);

View File

@@ -14,7 +14,9 @@ Components:
- `formats.js` — `FORMATS` enum (openai, claude, gemini, gemini-cli, openai-responses, antigravity, kiro, cursor, commandcode, ollama, vertex).
- `request/<from>-to-<to>.js` — one-way request translation.
- `response/<from>-to-<to>.js` — one-way SSE response translation.
- `helpers/` — `openaiHelper.js` (filterToOpenAIFormat), `toolCallHelper.js` (id/arguments), `claudeHelper.js`, `geminiHelper.js`.
- `schema/` — pure data enums (no logic): `roles.js` (ROLE, GEMINI_ROLE), `blocks.js` (OPENAI_BLOCK, CLAUDE_BLOCK, RESPONSES_ITEM, valid-type lists), `finishReasons.js` (OPENAI_FINISH, CLAUDE_STOP, GEMINI_FINISH), `defaults.js` (MODEL_FALLBACK, DEFAULT_IMAGE_MIME). Import via `schema/index.js`.
- `concerns/` — cross-format translation LOGIC: `chunk.js`, `usage.js`, `reasoning.js`, `thinking.js` (effort↔budget/level), `toolCall.js`, `finishReason.js` (mapping fns), `image.js`, `json.js`.
- `formats/` — per-format logic: `openai.js` (filterToOpenAIFormat), `claude.js`, `gemini.js`, `responsesApi.js`, `maxTokens.js`.
**OpenAI-bridge pitfalls** (source of most bugs): going through OpenAI easily loses `thinking`/`reasoning`, image URLs (non-base64), `input_audio`, `is_error`; tool `id`/`index` become unstable (parallel tool calls), non-text system blocks, `tool_choice:"none"`.

View File

@@ -0,0 +1,317 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`GOLDEN request: OpenAI → Claude > full body (system/image/tool/tool_result) 1`] = `
{
"max_tokens": 64000,
"messages": [
{
"content": [
{
"text": "What's in this image?",
"type": "text",
},
{
"source": {
"data": "IMGDATA",
"media_type": "image/png",
"type": "base64",
},
"type": "image",
},
],
"role": "user",
},
{
"content": [
{
"cache_control": {
"type": "ephemeral",
},
"id": "call_1",
"input": {
"city": "NYC",
},
"name": "get_weather",
"type": "tool_use",
},
],
"role": "assistant",
},
{
"content": [
{
"content": "sunny",
"tool_use_id": "call_1",
"type": "tool_result",
},
],
"role": "user",
},
],
"model": "claude-opus-4-6",
"stream": true,
"system": [
{
"text": "You are Claude Code, Anthropic's official CLI for Claude.",
"type": "text",
},
{
"cache_control": {
"ttl": "1h",
"type": "ephemeral",
},
"text": "You are helpful.",
"type": "text",
},
],
"temperature": 0.7,
"tools": [
{
"cache_control": {
"ttl": "1h",
"type": "ephemeral",
},
"description": "Get weather",
"input_schema": {
"properties": {
"city": {
"type": "string",
},
},
"required": [
"city",
],
"type": "object",
},
"name": "get_weather",
},
],
}
`;
exports[`GOLDEN request: OpenAI → Claude > reasoning_effort → adaptive output_config (claude 4.6+) 1`] = `
{
"max_tokens": 64000,
"messages": [
{
"content": [
{
"text": "hi",
"type": "text",
},
],
"role": "user",
},
],
"model": "claude-opus-4-6",
"output_config": {
"effort": "high",
},
"stream": true,
"system": [
{
"cache_control": {
"ttl": "1h",
"type": "ephemeral",
},
"text": "You are Claude Code, Anthropic's official CLI for Claude.",
"type": "text",
},
],
}
`;
exports[`GOLDEN request: OpenAI → Gemini > full body (system/image/tool/tool_result) 1`] = `
{
"contents": [
{
"parts": [
{
"text": "What's in this image?",
},
{
"inlineData": {
"data": "IMGDATA",
"mime_type": "image/png",
},
},
],
"role": "user",
},
{
"parts": [
{
"functionCall": {
"args": {
"city": "NYC",
},
"id": "call_1",
"name": "get_weather",
},
"thoughtSignature": "EuwGCukGAXLI2nxwZIq54WWSoL/YN0P3TsDZ7zRnLi8g0S4aVr2HUGxvaHKySuY6HAVzcE0GPGjXrytLIldxthSvfxgUlJh6Qa9Z+Oj5QZBlYdg6HaJ6yuY5R7waE6rdwBsRf7Ft2j3DJ9rMi9qhWFqApewYtPhls3VHtuvND3l8Rm09+lbAXQs6KKWEWrxNLKTBkfpMgXhRERc/TQRMZu1twAablm6/Zk1tsYRvfWKLsNbeKF+CCojJdXJKvnR/8Ouuoa+Y2Ti20hcW7aZIIjZDFYPU//k6Ybmhg69J/imbFai2ckhfLaisqdDkdoIiBJScTOUvYqP6AE9d4MsydSC+UlhIMk4hoP76R8vUSCZRMkjOaDXstf/QoVZKbt94wyRZgAJ1G0BqI8L5ow86kLpA4wJEtxsRGymOE4bKUvApveBakYDNM9APkf+LbtbzWSseGjoZcSlycF9iN8Q2XNYKRrHbv3Lr5Y8JjdH/5y/6SHkNehTEZugaeGnSPSyCTWto1kQgHpxdWmhkLfJGNUGLmue7Mesj4TSms4J33mRpYVhNB/J333FCqIP0hr/E7BkkjEn7yZ4X7SQlh+xKPurapsnHRwiKmtsilmEFrnTE9iQr+pMr6M29qqFNv1tr5yumbaJw8JW9sB15tNsRv+dW6BjNanbsKz7HCgKUBc8tGy+7YuhXzAfViyRefcjK7eZW0Fbyt7AbybJTKz78W8NH7ye6LAwzOebXpeZ4D43fNIt8bKh26qgduSQv/7o+pAflkuqHZ99YWgHQ8h8OkZFi3eOiSYjsjhdZ/czWOdoPI/OnqIldzMPF5YlrKBLFX8VhRKVmqgsmWf5PHGulHhMkVlS+XG2UIseGy69ARa93D78Gsa+1n1kJr7EEB7Rh+27vUMxVYLdz1yMSvE5nalTAlg/ZeG8+XQ0cHuAI3KbQpHW2Q++RdXfm5JzD5WdJZUU+Zn8t8UUn85BH4RxZLeE0qJikgSsKoYVBc6YhiMjhPgkR95ReimY4Z0xCJdRo1gjexOFeODZMpQF6Yxnoic7IrdgsFA3iePTbFnPp3IAM1fAThWhXJUn3QInUOTd5o1qmTmn6REbL15g/JQNl+dqUoPkhleeb2V3kjqp1okmO3wMZbPknR3S1LZNmlS72/iBQUm+n2b/RCn4PjmM2",
},
],
"role": "model",
},
{
"parts": [
{
"functionResponse": {
"id": "call_1",
"name": "get_weather",
"response": {
"result": {
"result": "sunny",
},
},
},
},
],
"role": "user",
},
],
"generationConfig": {
"temperature": 0.7,
},
"model": "gemini-3-pro",
"safetySettings": [
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "OFF",
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "OFF",
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "OFF",
},
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF",
},
{
"category": "HARM_CATEGORY_CIVIC_INTEGRITY",
"threshold": "OFF",
},
],
"systemInstruction": {
"parts": [
{
"text": "You are helpful.",
},
],
"role": "user",
},
"tools": [
{
"functionDeclarations": [
{
"description": "Get weather",
"name": "get_weather",
"parameters": {
"properties": {
"city": {
"type": "string",
},
},
"required": [
"city",
],
"type": "object",
},
},
],
},
],
}
`;
exports[`GOLDEN request: OpenAI → Kiro > full body (image base64 + tool_result) 1`] = `
{
"conversationState": {
"chatTriggerType": "MANUAL",
"currentMessage": {
"userInputMessage": {
"content": "[Context: Current time is <TS>
continue",
"modelId": "claude-sonnet-4.5",
"origin": "AI_EDITOR",
"userInputMessageContext": {
"toolResults": [
{
"content": [
{
"text": "sunny",
},
],
"status": "success",
"toolUseId": "call_1",
},
],
"tools": [
{
"toolSpecification": {
"description": "Get weather",
"inputSchema": {
"json": {
"properties": {
"city": {
"type": "string",
},
},
"required": [
"city",
],
"type": "object",
},
},
"name": "get_weather",
},
},
],
},
},
},
"history": [
{
"userInputMessage": {
"content": "You are helpful.
What's in this image?",
"images": [
{
"format": "png",
"source": {
"bytes": "IMGDATA",
},
},
],
"modelId": "claude-sonnet-4.5",
},
},
{
"assistantResponseMessage": {
"content": "...",
"toolUses": [
{
"input": {
"city": "NYC",
},
"name": "get_weather",
"toolUseId": "call_1",
},
],
},
},
],
},
"inferenceConfig": {
"maxTokens": 32000,
"temperature": 0.7,
},
"profileArn": "arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX",
}
`;

View File

@@ -0,0 +1,570 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`GOLDEN response stream: Claude → OpenAI > text + thinking + tool_use + usage + finish 1`] = `
[
{
"choices": [
{
"delta": {
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"content": "<think>",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "let me think",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"content": "</think>",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"content": "Hello",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
"name": "get_weather",
},
"id": "tu_1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
},
"id": "tu_1",
"index": 0,
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-msg_1",
"model": "claude-opus-4-6",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 5,
"prompt_tokens": 13,
"prompt_tokens_details": {
"cached_tokens": 3,
},
"total_tokens": 18,
},
},
]
`;
exports[`GOLDEN response stream: Gemini → OpenAI > image output (inlineData → delta.images) 1`] = `
[
{
"choices": [
{
"delta": {
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_2",
"model": "gemini-3-flash-image",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"images": [
{
"image_url": {
"url": "data:image/png;base64,BASE64DATA",
},
"type": "image_url",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_2",
"model": "gemini-3-flash-image",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "stop",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_2",
"model": "gemini-3-flash-image",
"object": "chat.completion.chunk",
},
]
`;
exports[`GOLDEN response stream: Gemini → OpenAI > text + thought(no-sig) + functionCall + usage + finish 1`] = `
[
{
"choices": [
{
"delta": {
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_1",
"model": "gemini-3-pro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "thinking part",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_1",
"model": "gemini-3-pro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"content": "Answer",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_1",
"model": "gemini-3-pro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"q":"x"}",
"name": "search",
},
"id": "search-<TS>-0",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_1",
"model": "gemini-3-pro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-resp_1",
"model": "gemini-3-pro",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 6,
"completion_tokens_details": {
"reasoning_tokens": 2,
},
"prompt_tokens": 8,
"prompt_tokens_details": {
"cached_tokens": 1,
},
"total_tokens": 14,
},
},
]
`;
exports[`GOLDEN response stream: Kiro → OpenAI > text + reasoning + toolUse + usage + stop 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hello",
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "thinking",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
"name": "get_weather",
},
"id": "tu_1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 5,
"prompt_tokens": 10,
"total_tokens": 15,
},
},
]
`;
exports[`GOLDEN response stream: Ollama → OpenAI > content + thinking + tool_calls + done usage 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hi",
"reasoning_content": "reason",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "qwen3",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"q":"x"}",
"name": "search",
},
"id": "call_0_<TS>",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "qwen3",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "qwen3",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 4,
"prompt_tokens": 8,
"total_tokens": 12,
},
},
]
`;
exports[`GOLDEN response stream: OpenAI-Responses (codex) → OpenAI > error event → error chunk (fallback id/created) 1`] = `
[
{
"choices": [
{
"delta": {
"content": "[Error] model_not_found",
},
"finish_reason": "stop",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
]
`;
exports[`GOLDEN response stream: OpenAI-Responses (codex) → OpenAI > text + reasoning + tool_call + completed usage 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hello",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "thinking",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "",
"name": "get_weather",
},
"id": "call_1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
},
"index": 0,
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 5,
"prompt_tokens": 10,
"prompt_tokens_details": {
"cached_tokens": 3,
},
"total_tokens": 15,
},
},
]
`;

View File

@@ -0,0 +1,226 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`GOLDEN response stream: CommandCode → OpenAI > text + reasoning + tool + finish-step usage 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hello",
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "commandcode",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "thinking",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "commandcode",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "",
"name": "get_weather",
},
"id": "t1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "commandcode",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
},
"index": 0,
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "commandcode",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "commandcode",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 5,
"prompt_tokens": 10,
"total_tokens": 15,
},
},
]
`;
exports[`GOLDEN response stream: Kiro → OpenAI (finish after tool) > toolUse then stop — lock current finish_reason behavior 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hi",
"role": "assistant",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"q":"x"}",
"name": "search",
},
"id": "tu_1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "kiro",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 3,
"prompt_tokens": 7,
"total_tokens": 10,
},
},
]
`;
exports[`GOLDEN response stream: Ollama → OpenAI (finish after tool) > tool_calls then done_reason=stop — lock current finish_reason 1`] = `
[
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"q":"x"}",
"name": "search",
},
"id": "call_0_<TS>",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "qwen3",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "qwen3",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 2,
"prompt_tokens": 5,
"total_tokens": 7,
},
},
]
`;
exports[`GOLDEN usage math: Claude prompt = input + cache (lock) > prompt_tokens sums input + cache_read + cache_creation 1`] = `
{
"completion_tokens": 5,
"prompt_tokens": 15,
"prompt_tokens_details": {
"cache_creation_tokens": 2,
"cached_tokens": 3,
},
"total_tokens": 20,
}
`;

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import { describe, it, expect } from "vitest";
import "./registerAll.js";
import { translateRequest } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
const AG2O = (req) =>
translateRequest(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, "m", { request: req }, true, null, null);
@@ -27,7 +28,7 @@ describe("Antigravity → OpenAI", () => {
// antigravity-to-openai.js:167 — functionCall without id gets a random Date.now() id
// KNOWN BUG: unstable id breaks matching with its functionResponse
it.fails("functionCall without id keeps a stable matchable id", () => {
it("functionCall without id keeps a stable matchable id", () => {
const out = AG2O({
contents: [
{ role: "model", parts: [{ functionCall: { name: "search", args: { q: "x" } } }] },
@@ -52,3 +53,32 @@ describe("Antigravity → OpenAI", () => {
expect(hasEmpty, "empty text part emitted").toBe(false);
});
});
describe("Antigravity executor", () => {
it("strips optional from nested tool schemas", () => {
const out = new AntigravityExecutor().transformRequest("gemini-2.5-pro", {
request: {
contents: [{ role: "user", parts: [{ text: "hi" }] }],
tools: [{
functionDeclarations: [{
name: "lookup",
description: "Lookup a value",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query",
optional: true,
},
},
},
}],
}],
},
}, true, { projectId: "project-1", connectionId: "conn-1" });
const query = out.request.tools[0].functionDeclarations[0].parameters.properties.query;
expect(query).toEqual({ type: "string", description: "Search query" });
});
});

View File

@@ -0,0 +1,178 @@
// Claude → Kiro (direct route) request translation + Kiro → Claude response.
// Verifies the direct claude:kiro / kiro:claude routes added to bypass the
// OpenAI pivot, and that the "Improperly formed request" 400-guards survive.
import { describe, it, expect } from "vitest";
import "./registerAll.js";
import { translateRequest, translateResponse } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const C2K = (body) =>
translateRequest(FORMATS.CLAUDE, FORMATS.KIRO, "claude-sonnet-4.5", body, true, null, "kiro");
describe("Claude → Kiro (direct route)", () => {
it("produces a Kiro conversationState payload", () => {
const out = C2K({ messages: [{ role: "user", content: "hello" }] });
expect(out.conversationState).toBeTruthy();
expect(out.conversationState.currentMessage.userInputMessage.content).toContain("hello");
});
it("guard 1: with no tools, a dangling tool_result is flattened to text (no structured ref)", () => {
// Client omitted `tools` but kept a tool_result after compaction.
const out = C2K({
messages: [
{ role: "user", content: "go" },
{ role: "assistant", content: [{ type: "tool_use", id: "t1", name: "f", input: {} }] },
{ role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "result" }] },
],
});
// No userInputMessageContext.tools/toolResults anywhere → won't trip the
// "tools required" validator.
const cur = out.conversationState.currentMessage.userInputMessage;
expect(cur.userInputMessageContext?.toolResults).toBeFalsy();
const everyHistoryClean = out.conversationState.history.every(
(h) => !h.userInputMessage?.userInputMessageContext?.toolResults
);
expect(everyHistoryClean).toBe(true);
});
it("guard 2: with tools, an orphaned tool_result is folded into user text", () => {
const out = C2K({
tools: [{ name: "f", description: "fn", input_schema: { type: "object", properties: {} } }],
messages: [
{ role: "user", content: "go" },
// tool_result references a tool_use that never appears → orphan
{ role: "user", content: [{ type: "tool_result", tool_use_id: "ghost", content: "salvage me" }] },
],
});
const cur = out.conversationState.currentMessage.userInputMessage;
// The orphan content survives as text, not as a dangling structured ref.
expect(cur.content).toContain("salvage me");
expect(cur.userInputMessageContext?.toolResults?.length ?? 0).toBe(0);
});
it("injects thinking_mode tag when model implies thinking", () => {
const out = translateRequest(
FORMATS.CLAUDE,
FORMATS.KIRO,
"claude-sonnet-4.5-thinking",
{ messages: [{ role: "user", content: "hi" }] },
true,
null,
"kiro"
);
expect(out.conversationState.currentMessage.userInputMessage.content).toContain(
"<thinking_mode>enabled</thinking_mode>"
);
});
it("maps output_config.effort high to Kiro max_thinking_length 24576", () => {
const out = C2K({
output_config: { effort: "high" },
messages: [{ role: "user", content: "think with adaptive effort" }],
});
expect(out.conversationState.currentMessage.userInputMessage.content).toContain(
"<max_thinking_length>24576</max_thinking_length>"
);
});
});
describe("Kiro → Claude (direct route, OpenAI-shaped chunks from executor)", () => {
// KiroExecutor emits chat.completion.chunk objects; translateResponse must
// convert them to Claude SSE events.
const R = (chunk, state) => translateResponse(FORMATS.KIRO, FORMATS.CLAUDE, chunk, state);
it("first text chunk emits message_start + content_block_start + text_delta", () => {
const state = {};
const events = R(
{
id: "chatcmpl-1",
object: "chat.completion.chunk",
model: "claude-sonnet-4.5",
choices: [{ index: 0, delta: { role: "assistant", content: "Hi" }, finish_reason: null }],
},
state
);
const types = events.map((e) => e.type);
expect(types).toContain("message_start");
expect(types).toContain("content_block_start");
expect(types).toContain("content_block_delta");
const delta = events.find((e) => e.type === "content_block_delta");
expect(delta.delta).toEqual({ type: "text_delta", text: "Hi" });
});
it("finish chunk emits message_delta + message_stop with stop_reason", () => {
const state = {};
R(
{
id: "chatcmpl-1",
object: "chat.completion.chunk",
model: "m",
choices: [{ index: 0, delta: { content: "x" }, finish_reason: null }],
},
state
);
const events = R(
{
id: "chatcmpl-1",
object: "chat.completion.chunk",
model: "m",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
usage: { prompt_tokens: 5, completion_tokens: 3 },
},
state
);
const md = events.find((e) => e.type === "message_delta");
expect(md.delta.stop_reason).toBe("end_turn");
expect(md.usage).toEqual({ input_tokens: 5, output_tokens: 3 });
expect(events.some((e) => e.type === "message_stop")).toBe(true);
});
it("reasoning_content maps to a thinking block", () => {
const state = {};
const events = R(
{
id: "chatcmpl-1",
object: "chat.completion.chunk",
model: "m",
choices: [{ index: 0, delta: { reasoning_content: "pondering" }, finish_reason: null }],
},
state
);
const start = events.find((e) => e.type === "content_block_start");
expect(start.content_block.type).toBe("thinking");
const delta = events.find((e) => e.type === "content_block_delta");
expect(delta.delta).toEqual({ type: "thinking_delta", thinking: "pondering" });
});
it("tool_calls map to a tool_use block with buffered input_json_delta", () => {
const state = {};
R(
{
id: "c", object: "chat.completion.chunk", model: "m",
choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: "tu1", type: "function", function: { name: "search", arguments: "" } }] }, finish_reason: null }],
},
state
);
R(
{
id: "c", object: "chat.completion.chunk", model: "m",
choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: '{"q":"x"}' } }] }, finish_reason: null }],
},
state
);
const events = R(
{
id: "c", object: "chat.completion.chunk", model: "m",
choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }],
},
state
);
const jsonDelta = events.find(
(e) => e.type === "content_block_delta" && e.delta.type === "input_json_delta"
);
expect(jsonDelta.delta.partial_json).toBe('{"q":"x"}');
const md = events.find((e) => e.type === "message_delta");
expect(md.delta.stop_reason).toBe("tool_use");
});
});

View File

@@ -0,0 +1,64 @@
// P0 GOLDEN: lock OUTPUT của translateRequest (body) cho các đích đặc biệt.
// openai → claude/gemini/kiro: thinking, tools, image, system, tool_result.
// Sau refactor chạy lại phải khớp y hệt.
import { describe, it, expect } from "vitest";
import "./registerAll.js";
import { translateRequest } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
// Body openai mẫu phủ nhiều concern (text, image, tool, tool_result, system, thinking).
function baseBody() {
return {
messages: [
{ role: "system", content: "You are helpful." },
{ role: "user", content: [
{ type: "text", text: "What's in this image?" },
{ type: "image_url", image_url: { url: "data:image/png;base64,IMGDATA", detail: "high" } },
] },
{ role: "assistant", content: "", tool_calls: [
{ id: "call_1", type: "function", function: { name: "get_weather", arguments: '{"city":"NYC"}' } },
] },
{ role: "tool", tool_call_id: "call_1", content: "sunny" },
],
tools: [
{ type: "function", function: { name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } } },
],
temperature: 0.7,
};
}
// Khử field động: toolNameMap, kiro conversationId (uuid), timestamp trong content.
function clean(body) {
const s = JSON.stringify(body, (k, v) => {
if (k === "_toolNameMap" || k === "conversationId") return undefined;
return v;
}).replace(/Current time is [^"\\]+/g, "Current time is <TS>");
return JSON.parse(s);
}
describe("GOLDEN request: OpenAI → Claude", () => {
it("full body (system/image/tool/tool_result)", () => {
const out = translateRequest(FORMATS.OPENAI, FORMATS.CLAUDE, "claude-opus-4-6", baseBody(), true, { apiKey: "sk-x" }, "claude");
expect(clean(out)).toMatchSnapshot();
});
it("reasoning_effort → adaptive output_config (claude 4.6+)", () => {
const body = { messages: [{ role: "user", content: "hi" }], reasoning_effort: "high" };
const out = translateRequest(FORMATS.OPENAI, FORMATS.CLAUDE, "claude-opus-4-6", body, true, { apiKey: "sk-x" }, "anthropic");
expect(clean(out)).toMatchSnapshot();
});
});
describe("GOLDEN request: OpenAI → Gemini", () => {
it("full body (system/image/tool/tool_result)", () => {
const out = translateRequest(FORMATS.OPENAI, FORMATS.GEMINI, "gemini-3-pro", baseBody(), true, { apiKey: "k" }, "gemini");
expect(clean(out)).toMatchSnapshot();
});
});
describe("GOLDEN request: OpenAI → Kiro", () => {
it("full body (image base64 + tool_result)", () => {
const out = translateRequest(FORMATS.OPENAI, FORMATS.KIRO, "claude-sonnet-4.5", baseBody(), true, { accessToken: "t" }, "kiro");
expect(clean(out)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,118 @@
// P0 GOLDEN: lock OUTPUT của translateResponse (stream) cho các concern đặc biệt.
// Feed chuỗi chunk THẬT (shape provider) → snapshot mảng chunk openai emit.
// Sau refactor chạy lại phải khớp y hệt (chunk/usage/thinking/tool/finish).
import { describe, it, expect } from "vitest";
import "./registerAll.js";
import { translateResponse, initState } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
// Chuẩn hoá field động (Date.now trong created + id) để snapshot ổn định.
function stripVolatile(chunks) {
return JSON.parse(JSON.stringify(chunks), (key, val) => {
if (key === "created") return 0;
if (key === "id" && typeof val === "string") {
return val
.replace(/-\d{10,}-(\d+)$/, "-<TS>-$1") // gemini: name-<ts>-idx
.replace(/^chatcmpl-\d{10,}$/, "chatcmpl-<TS>") // kiro/ollama stream id
.replace(/^call_(\d+)_\d{10,}$/, "call_$1_<TS>"); // ollama tool id
}
return val;
});
}
// Chạy 1 chuỗi event qua translateResponse, gom toàn bộ chunk emit.
function runStream(targetFormat, sourceFormat, events) {
const state = initState(sourceFormat);
const all = [];
for (const ev of events) {
const out = translateResponse(targetFormat, sourceFormat, ev, state);
if (Array.isArray(out)) all.push(...out);
else if (out) all.push(out);
}
return stripVolatile(all);
}
describe("GOLDEN response stream: Claude → OpenAI", () => {
it("text + thinking + tool_use + usage + finish", () => {
const events = [
{ type: "message_start", message: { id: "msg_1", model: "claude-opus-4-6" } },
{ type: "content_block_start", index: 0, content_block: { type: "thinking" } },
{ type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "let me think" } },
{ type: "content_block_stop", index: 0 },
{ type: "content_block_start", index: 1, content_block: { type: "text" } },
{ type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "Hello" } },
{ type: "content_block_stop", index: 1 },
{ type: "content_block_start", index: 2, content_block: { type: "tool_use", id: "tu_1", name: "get_weather" } },
{ type: "content_block_delta", index: 2, delta: { type: "input_json_delta", partial_json: '{"city":"NYC"}' } },
{ type: "content_block_stop", index: 2 },
{ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { input_tokens: 10, output_tokens: 5, cache_read_input_tokens: 3 } },
{ type: "message_stop" },
];
expect(runStream(FORMATS.CLAUDE, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: Gemini → OpenAI", () => {
it("text + thought(no-sig) + functionCall + usage + finish", () => {
const events = [
{ candidates: [{ content: { parts: [{ text: "thinking part", thought: true }] } }], responseId: "resp_1", modelVersion: "gemini-3-pro" },
{ candidates: [{ content: { parts: [{ text: "Answer" }] } }] },
{ candidates: [{ content: { parts: [{ functionCall: { name: "search", args: { q: "x" } } }] } }] },
{ candidates: [{ finishReason: "STOP" }], usageMetadata: { promptTokenCount: 8, candidatesTokenCount: 4, thoughtsTokenCount: 2, totalTokenCount: 14, cachedContentTokenCount: 1 } },
];
expect(runStream(FORMATS.GEMINI, FORMATS.OPENAI, events)).toMatchSnapshot();
});
it("image output (inlineData → delta.images)", () => {
const events = [
{ candidates: [{ content: { parts: [{ inlineData: { mimeType: "image/png", data: "BASE64DATA" } }] } }], responseId: "resp_2", modelVersion: "gemini-3-flash-image" },
{ candidates: [{ finishReason: "STOP" }] },
];
expect(runStream(FORMATS.GEMINI, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: Kiro → OpenAI", () => {
it("text + reasoning + toolUse + usage + stop", () => {
const events = [
{ assistantResponseEvent: { content: "Hello" }, _eventType: "assistantResponseEvent" },
{ reasoningContentEvent: { text: "thinking" }, _eventType: "reasoningContentEvent" },
{ toolUseEvent: { toolUseId: "tu_1", name: "get_weather", input: { city: "NYC" } }, _eventType: "toolUseEvent" },
{ usageEvent: { inputTokens: 10, outputTokens: 5 }, _eventType: "usageEvent" },
{ _eventType: "messageStopEvent" },
];
expect(runStream(FORMATS.KIRO, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: Ollama → OpenAI", () => {
it("content + thinking + tool_calls + done usage", () => {
const events = [
{ model: "qwen3", message: { role: "assistant", content: "Hi", thinking: "reason" } },
{ model: "qwen3", message: { role: "assistant", tool_calls: [{ function: { name: "search", arguments: { q: "x" } } }] } },
{ model: "qwen3", done: true, done_reason: "stop", prompt_eval_count: 8, eval_count: 4 },
];
expect(runStream(FORMATS.OLLAMA, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: OpenAI-Responses (codex) → OpenAI", () => {
it("text + reasoning + tool_call + completed usage", () => {
const events = [
{ type: "response.output_text.delta", delta: "Hello" },
{ type: "response.reasoning_summary_text.delta", delta: "thinking" },
{ type: "response.output_item.added", item: { type: "function_call", call_id: "call_1", name: "get_weather" } },
{ type: "response.function_call_arguments.delta", delta: '{"city":"NYC"}' },
{ type: "response.output_item.done", item: { type: "function_call" } },
{ type: "response.completed", response: { usage: { input_tokens: 10, output_tokens: 5, input_tokens_details: { cached_tokens: 3 } } } },
];
expect(runStream(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, events)).toMatchSnapshot();
});
it("error event → error chunk (fallback id/created)", () => {
const events = [
{ type: "error", error: { message: "model_not_found" } },
];
expect(runStream(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,106 @@
// P0 GOLDEN (refactor2): lock OUTPUT translateResponse/Request cho các concern
// SẮP refactor ở P1-P4 (usage field-map, finishReasonMap, reasoningDelta).
// Bổ sung coverage còn thiếu so với golden-response-stream.test.js:
// - commandcode usage/finish
// - passthrough openai→openai (request + response)
// - kiro/ollama finish_reason sau tool (lock behavior HIỆN TẠI, kể cả bug đã biết)
// Sau refactor chạy lại phải khớp y hệt. Lệch = regression.
import { describe, it, expect } from "vitest";
import "./registerAll.js";
import { translateRequest, translateResponse, initState } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
// Strip volatile id/created so snapshots are stable across runs.
function stripVolatile(chunks) {
return JSON.parse(JSON.stringify(chunks), (key, val) => {
if (key === "created") return 0;
if (key === "id" && typeof val === "string") {
return val
.replace(/-\d{10,}-(\d+)$/, "-<TS>-$1")
.replace(/^chatcmpl-\d{10,}$/, "chatcmpl-<TS>")
.replace(/^call_(\d+)_\d{10,}$/, "call_$1_<TS>")
.replace(/^call_\d{10,}_(\d+)$/, "call_<TS>_$1");
}
return val;
});
}
function runStream(targetFormat, sourceFormat, events) {
const state = initState(sourceFormat);
const all = [];
for (const ev of events) {
const out = translateResponse(targetFormat, sourceFormat, ev, state);
if (Array.isArray(out)) all.push(...out);
else if (out) all.push(out);
}
return stripVolatile(all);
}
describe("GOLDEN response stream: CommandCode → OpenAI", () => {
it("text + reasoning + tool + finish-step usage", () => {
const events = [
{ type: "text-delta", text: "Hello" },
{ type: "reasoning-delta", text: "thinking" },
{ type: "tool-input-start", id: "t1", toolName: "get_weather" },
{ type: "tool-input-delta", id: "t1", delta: '{"city":"NYC"}' },
{ type: "finish-step", finishReason: "tool-calls", usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } },
{ type: "finish" },
];
expect(runStream(FORMATS.COMMANDCODE, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: Kiro → OpenAI (finish after tool)", () => {
it("toolUse then stop — lock current finish_reason behavior", () => {
const events = [
{ assistantResponseEvent: { content: "Hi" }, _eventType: "assistantResponseEvent" },
{ toolUseEvent: { toolUseId: "tu_1", name: "search", input: { q: "x" } }, _eventType: "toolUseEvent" },
{ usageEvent: { inputTokens: 7, outputTokens: 3 }, _eventType: "usageEvent" },
{ _eventType: "messageStopEvent" },
];
expect(runStream(FORMATS.KIRO, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN response stream: Ollama → OpenAI (finish after tool)", () => {
it("tool_calls then done_reason=stop — lock current finish_reason", () => {
const events = [
{ model: "qwen3", message: { role: "assistant", tool_calls: [{ function: { name: "search", arguments: { q: "x" } } }] } },
{ model: "qwen3", done: true, done_reason: "stop", prompt_eval_count: 5, eval_count: 2 },
];
expect(runStream(FORMATS.OLLAMA, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});
describe("GOLDEN passthrough: same format = no translation", () => {
it("response openai→openai returns chunk unchanged", () => {
const chunk = { id: "chatcmpl-x", object: "chat.completion.chunk", created: 1, model: "gpt-4o", choices: [{ index: 0, delta: { content: "hi" }, finish_reason: null }] };
const state = initState(FORMATS.OPENAI);
const out = translateResponse(FORMATS.OPENAI, FORMATS.OPENAI, chunk, state);
expect(out).toEqual([chunk]);
});
it("request openai→openai keeps messages (filterToOpenAIFormat normalize)", () => {
const body = { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] };
const out = translateRequest(FORMATS.OPENAI, FORMATS.OPENAI, "gpt-4o", body, true);
expect(out.messages).toEqual([{ role: "user", content: "hi" }]);
});
});
describe("GOLDEN usage math: Claude prompt = input + cache (lock)", () => {
it("prompt_tokens sums input + cache_read + cache_creation", () => {
const events = [
{ type: "message_start", message: { id: "msg_1", model: "claude-opus-4-6" } },
{ type: "content_block_start", index: 0, content_block: { type: "text" } },
{ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "ok" } },
{ type: "content_block_stop", index: 0 },
{ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { input_tokens: 10, output_tokens: 5, cache_read_input_tokens: 3, cache_creation_input_tokens: 2 } },
{ type: "message_stop" },
];
const out = runStream(FORMATS.CLAUDE, FORMATS.OPENAI, events);
const finalChunk = out.find(c => c.usage);
expect(finalChunk.usage.prompt_tokens).toBe(15); // 10 + 3 + 2
expect(finalChunk.usage.completion_tokens).toBe(5);
expect(finalChunk.usage).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,72 @@
// P0 GOLDEN: lock buildUrl + buildHeaders cho mọi provider trên code CŨ.
// Sinh snapshot lần đầu (baseline) → sau refactor chạy lại phải khớp y hệt.
// Mock proxyFetch + uuid-heavy executors KHÔNG cần ở đây vì chỉ gọi buildUrl/buildHeaders (pure).
import { describe, it, expect } from "vitest";
import { PROVIDERS } from "../../open-sse/config/providers.js";
import { DefaultExecutor } from "../../open-sse/executors/default.js";
// Credentials mẫu cố định (deterministic) — KHÔNG dùng Date.now/random.
const API_KEY_CRED = { apiKey: "sk-test-APIKEY", providerSpecificData: {} };
const OAUTH_CRED = { accessToken: "tok-test-ACCESS", providerSpecificData: {} };
const SPECIAL_CRED = {
apiKey: "sk-test-APIKEY",
accessToken: "tok-test-ACCESS",
providerSpecificData: { accountId: "ACC123", region: "sgp", baseUrl: "https://custom.example.com/v1", orgId: "ORG9" },
};
// Provider cần executor riêng (buildUrl/buildHeaders không nằm ở DefaultExecutor) → bỏ qua ở golden này.
// Chúng được lock riêng ở 11-provider edge tests / unit test chuyên biệt.
const SPECIALIZED = new Set([
"antigravity", "azure", "gemini-cli", "github", "iflow", "qoder", "kiro",
"codex", "cursor", "vertex", "vertex-partner", "qwen", "opencode",
"opencode-go", "grok-web", "perplexity-web", "ollama-local", "commandcode",
"xiaomi-tokenplan", "mimo-free",
]);
// Sanitize header: khử token + field thời gian động (kimi X-Msh-Device-Id) để snapshot ổn định.
function sanitize(headers) {
const out = {};
for (const [k, v] of Object.entries(headers)) {
out[k] = typeof v === "string"
? v.replace(/Bearer .+/, "Bearer <TOK>")
.replace(/sk-test-APIKEY|tok-test-ACCESS/g, "<CRED>")
.replace(/kimi-\d{10,}/g, "kimi-<TS>")
: v;
}
return out;
}
const providerIds = Object.keys(PROVIDERS).filter((p) => !SPECIALIZED.has(p)).sort();
describe("GOLDEN buildUrl (default executor providers)", () => {
for (const pid of providerIds) {
it(`${pid} → url (stream + non-stream)`, () => {
const ex = new DefaultExecutor(pid);
const cred = PROVIDERS[pid].noAuth ? {} : SPECIAL_CRED;
const model = "test-model";
const snap = {
stream: safe(() => ex.buildUrl(model, true, 0, cred)),
nonStream: safe(() => ex.buildUrl(model, false, 0, cred)),
};
expect(snap).toMatchSnapshot();
});
}
});
describe("GOLDEN buildHeaders (default executor providers)", () => {
for (const pid of providerIds) {
it(`${pid} → headers (apiKey / oauth)`, () => {
const ex = new DefaultExecutor(pid);
const snap = {
apiKey: safe(() => sanitize(ex.buildHeaders(PROVIDERS[pid].noAuth ? {} : API_KEY_CRED, true))),
oauth: safe(() => sanitize(ex.buildHeaders(PROVIDERS[pid].noAuth ? {} : OAUTH_CRED, true))),
nonStream: safe(() => sanitize(ex.buildHeaders(PROVIDERS[pid].noAuth ? {} : API_KEY_CRED, false))),
};
expect(snap).toMatchSnapshot();
});
}
});
function safe(fn) {
try { return fn(); } catch (e) { return `THROW: ${e.message}`; }
}

View File

@@ -0,0 +1,293 @@
// REAL matrix test: every active provider in DB x every inbound client format x 4 scenarios.
// Goal: maximize translation-path coverage to surface real bugs (system, multimodal image,
// tool-call/tool-result, reasoning) across all source formats.
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/all-formats.real.test.js
// RUN_REAL=1 REAL_PROVIDERS=gemini,kiro,codex npx vitest run ... (optional filter)
//
// Skips (console.warn + pass) when: no credential/model, auth/quota status (401/402/403/429),
// or the model rejects a capability (e.g. image on a non-vision model).
import { describe, it, expect } from "vitest";
import { getProviderCredentials } from "../../../src/sse/services/auth.js";
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js";
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js";
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js";
const RUN_REAL = process.env.RUN_REAL === "1";
const TIMEOUT_MS = 90000;
const CRED_ISSUE = [401, 402, 403, 429];
// Account/plan/capability rejections -> skip (not a translate bug). Kept specific to avoid masking real bugs.
const SKIP_MSG_RE = /image|multimodal|vision|modality|unsupported|not support|reasoning_effort|deprecated|temperature|subscription|valid.*plan|embedding|quota|insufficient|model not found|context length|organization policy|disallowed|allowedmodels|failed_precondition/i;
const PROVIDER_FILTER = (process.env.REAL_PROVIDERS || "")
.split(",").map((s) => s.trim()).filter(Boolean);
// Tiny 1x1 transparent PNG (data URI body + raw base64) for multimodal scenarios.
const PNG_B64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
const PNG_DATA_URI = `data:image/png;base64,${PNG_B64}`;
// Pick first chat LLM, excluding non-chat kinds (embedding/image/tts/stt/...).
const NON_CHAT_KINDS = new Set(["embedding", "image", "imageToText", "tts", "stt", "video", "music", "webSearch"]);
function firstLlmModel(providerId) {
const models = getModelsByProviderId(providerId);
const llm = models.find((m) => {
const kind = m.kind || m.type || "llm";
return kind === "llm" || (!NON_CHAT_KINDS.has(kind) && kind === "llm");
}) || models.find((m) => !NON_CHAT_KINDS.has(m.kind || m.type || "llm"));
return llm?.id || null;
}
async function drainSSE(response) {
if (!response?.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let out = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
async function prepare(providerId) {
const model = firstLlmModel(providerId);
if (!model) return null;
const credentials = await getProviderCredentials(providerId, new Set(), model);
if (!credentials || credentials.allRateLimited) return null;
const refreshed = await checkAndRefreshToken(providerId, credentials);
return { model, credentials, refreshed };
}
// Run one request. Returns { raw } | "skip" | throws (real translate/runtime bug).
async function runChat(providerId, prep, body, sourceFormatOverride) {
const result = await handleChatCore({
body: { ...body, model: `${providerId}/${prep.model}` },
modelInfo: { provider: providerId, model: prep.model },
credentials: prep.refreshed,
connectionId: prep.credentials.connectionId,
sourceFormatOverride,
});
if (!result.success) {
const status = Number(result.status);
if (CRED_ISSUE.includes(status)) return "skip";
// Upstream 5xx and 406 are provider-side issues, not translate bugs.
if (status >= 500 || status === 406) return "skip";
// Account/plan/capability rejection (e.g. non-vision model + image) is not a translate bug.
if (status === 400 && SKIP_MSG_RE.test(String(result.error || ""))) return "skip";
throw new Error(`${providerId} [${result.status}]: ${result.error}`);
}
return { raw: await drainSSE(result.response) };
}
// SSE validity marker per inbound format (response is re-encoded back to source format).
const SSE_MARKER = {
openai: /chat\.completion\.chunk|"delta"|\[DONE\]/,
"openai-responses": /response\.|"type"\s*:\s*"response|\[DONE\]/,
claude: /event:\s*\w|"type"\s*:\s*"(message_start|content_block|message_delta)"/,
gemini: /"candidates"|"content"|data:/,
"gemini-cli": /"candidates"|"content"|data:/,
antigravity: /"candidates"|"content"|data:/,
};
// ---- Body builders: per format x scenario (full, spec-correct shapes) ----
const COMMON = { temperature: 0.3, top_p: 0.9, max_tokens: 256 };
// Reasoning models often reject custom temperature (must be default/1) -> omit sampling.
const REASON_TOK = { max_tokens: 1024 };
// OpenAI Chat Completions
const openaiBody = {
basic: () => ({
...COMMON, stream: true, stream_options: { include_usage: true },
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "Reply with the single word: hi" },
],
}),
multimodal: () => ({
...COMMON, stream: true,
messages: [
{ role: "system", content: "Describe images briefly." },
{ role: "user", content: [
{ type: "text", text: "What color dominates this image? One word." },
{ type: "image_url", image_url: { url: PNG_DATA_URI } },
] },
],
}),
tools: () => ({
...COMMON, stream: true, tool_choice: "auto",
tools: [{ type: "function", function: { name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } } }],
messages: [
{ role: "user", content: "Weather in Paris?" },
{ role: "assistant", content: "", tool_calls: [{ id: "call_1", type: "function", function: { name: "get_weather", arguments: '{"city":"Paris"}' } }] },
{ role: "tool", tool_call_id: "call_1", content: '{"temp":"20C"}' },
{ role: "user", content: "Summarize in one short sentence." },
],
}),
reasoning: () => ({
...REASON_TOK, stream: true, reasoning_effort: "low",
messages: [{ role: "user", content: "What is 17 + 26? Reply with just the number." }],
}),
};
// OpenAI Responses API
const responsesBody = {
basic: () => ({
...COMMON, stream: true, max_output_tokens: 256,
instructions: "You are concise.",
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "Reply with the single word: hi" }] }],
}),
multimodal: () => ({
...COMMON, stream: true, max_output_tokens: 256,
instructions: "Describe images briefly.",
input: [{ type: "message", role: "user", content: [
{ type: "input_text", text: "What color dominates? One word." },
{ type: "input_image", image_url: PNG_DATA_URI },
] }],
}),
tools: () => ({
...COMMON, stream: true,
tools: [{ type: "function", name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }],
input: [
{ type: "message", role: "user", content: [{ type: "input_text", text: "Weather in Paris?" }] },
{ type: "function_call", call_id: "call_1", name: "get_weather", arguments: '{"city":"Paris"}' },
{ type: "function_call_output", call_id: "call_1", output: '{"temp":"20C"}' },
{ type: "message", role: "user", content: [{ type: "input_text", text: "Summarize in one short sentence." }] },
],
}),
reasoning: () => ({
...REASON_TOK, stream: true, max_output_tokens: 1024, reasoning: { effort: "low" },
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "What is 17 + 26? Just the number." }] }],
}),
};
// Anthropic Messages (Claude)
const claudeBody = {
basic: () => ({
...COMMON, stream: true,
system: [{ type: "text", text: "You are concise." }],
messages: [{ role: "user", content: "Reply with the single word: hi" }],
}),
multimodal: () => ({
...COMMON, stream: true,
system: [{ type: "text", text: "Describe images briefly." }],
messages: [{ role: "user", content: [
{ type: "text", text: "What color dominates? One word." },
{ type: "image", source: { type: "base64", media_type: "image/png", data: PNG_B64 } },
] }],
}),
tools: () => ({
...COMMON, stream: true,
tools: [{ name: "get_weather", description: "Get weather", input_schema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }],
messages: [
{ role: "user", content: "Weather in Paris?" },
{ role: "assistant", content: [{ type: "tool_use", id: "toolu_1", name: "get_weather", input: { city: "Paris" } }] },
{ role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_1", content: '{"temp":"20C"}' }] },
{ role: "user", content: "Summarize in one short sentence." },
],
}),
reasoning: () => ({
...REASON_TOK, stream: true, thinking: { type: "enabled", budget_tokens: 1024 },
messages: [{ role: "user", content: "What is 17 + 26? Just the number." }],
}),
};
// Gemini generateContent
const geminiBody = {
basic: () => ({
systemInstruction: { parts: [{ text: "You are concise." }] },
contents: [{ role: "user", parts: [{ text: "Reply with the single word: hi" }] }],
generationConfig: { maxOutputTokens: 256, temperature: 0.3, topP: 0.9 },
}),
multimodal: () => ({
systemInstruction: { parts: [{ text: "Describe images briefly." }] },
contents: [{ role: "user", parts: [
{ text: "What color dominates? One word." },
{ inlineData: { mimeType: "image/png", data: PNG_B64 } },
] }],
generationConfig: { maxOutputTokens: 256 },
}),
tools: () => ({
tools: [{ functionDeclarations: [{ name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }] }],
contents: [
{ role: "user", parts: [{ text: "Weather in Paris?" }] },
{ role: "model", parts: [{ functionCall: { name: "get_weather", args: { city: "Paris" } } }] },
{ role: "user", parts: [{ functionResponse: { name: "get_weather", response: { temp: "20C" } } }] },
{ role: "user", parts: [{ text: "Summarize in one short sentence." }] },
],
generationConfig: { maxOutputTokens: 256 },
}),
reasoning: () => ({
contents: [{ role: "user", parts: [{ text: "What is 17 + 26? Just the number." }] }],
generationConfig: { maxOutputTokens: 1024, thinkingConfig: { thinkingBudget: 512, includeThoughts: true } },
}),
};
// Antigravity = Gemini body wrapped in { request, userAgent }.
const wrapAntigravity = (fn) => () => ({ request: fn(), userAgent: "antigravity" });
const antigravityBody = {
basic: wrapAntigravity(geminiBody.basic),
multimodal: wrapAntigravity(geminiBody.multimodal),
tools: wrapAntigravity(geminiBody.tools),
reasoning: wrapAntigravity(geminiBody.reasoning),
};
const BUILDERS = {
openai: openaiBody,
"openai-responses": responsesBody,
claude: claudeBody,
gemini: geminiBody,
"gemini-cli": geminiBody,
antigravity: antigravityBody,
};
const FORMATS = Object.keys(BUILDERS);
const SCENARIOS = ["basic", "multimodal", "tools", "reasoning"];
// Read active providers from DB at module-eval time (one test per provider/format/scenario).
function targetProviders() {
try {
const Database = require("better-sqlite3");
const os = require("os");
const path = require("path");
const dbPath = process.env.DATA_DIR
? path.join(process.env.DATA_DIR, "db", "data.sqlite")
: path.join(os.homedir(), ".9router", "db", "data.sqlite");
const db = new Database(dbPath, { readonly: true });
const rows = db.prepare("SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1").all();
db.close();
let list = rows.map((r) => r.provider).sort();
if (PROVIDER_FILTER.length) list = list.filter((p) => PROVIDER_FILTER.includes(p));
return list;
} catch {
return [];
}
}
describe.skipIf(!RUN_REAL)("REAL all-formats matrix", () => {
const providers = RUN_REAL ? targetProviders() : [];
it("has active providers in DB", () => {
expect(providers.length).toBeGreaterThan(0);
});
for (const providerId of providers) {
for (const fmt of FORMATS) {
for (const scn of SCENARIOS) {
it.concurrent(`${providerId} | ${fmt} | ${scn}`, async () => {
const prep = await prepare(providerId);
if (!prep) { console.warn(`[skip] ${providerId}: no cred/model`); return expect(true).toBe(true); }
const body = BUILDERS[fmt][scn]();
const out = await runChat(providerId, prep, body, fmt);
if (out === "skip") { console.warn(`[skip] ${providerId} ${fmt}/${scn}: cred/quota/capability`); return expect(true).toBe(true); }
expect(out.raw.length, `${providerId} ${fmt}/${scn}: empty SSE`).toBeGreaterThan(0);
expect(SSE_MARKER[fmt].test(out.raw), `${providerId} ${fmt}/${scn}: invalid SSE shape`).toBe(true);
}, TIMEOUT_MS);
}
}
}
});

View File

@@ -0,0 +1,127 @@
// REAL integration test: hits localhost:20128/v1 with real API key for all antigravity models.
// Verifies tool-call with optional field in schema doesn't cause 400 INVALID_ARGUMENT.
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/antigravity-models.real.test.js
// RUN_REAL=1 AG_URL=http://localhost:20128/v1 AG_KEY=sk-xxx npx vitest run ...
//
import { describe, it, expect } from "vitest";
const RUN_REAL = process.env.RUN_REAL === "1";
const BASE_URL = process.env.AG_URL || "http://localhost:20128/v1";
const API_KEY = process.env.AG_KEY;
const TIMEOUT_MS = 90000;
// All antigravity models (from providers/registry/antigravity.js)
const AG_MODELS = [
"ag/gemini-3-flash-agent",
"ag/gemini-3.5-flash-low",
"ag/gemini-3.5-flash-extra-low",
"ag/gemini-pro-agent",
"ag/gemini-3.1-pro-low",
"ag/claude-sonnet-4-6",
"ag/claude-opus-4-6-thinking",
"ag/gpt-oss-120b-medium",
"ag/gemini-3-flash",
];
// Simple text prompt — no tools
const SIMPLE_BODY = (model) => ({
model,
stream: false,
max_tokens: 32,
messages: [{ role: "user", content: "Reply with the single word: hi" }],
});
// Tool call with `optional` field in schema — this is the bug scenario
const TOOL_BODY = (model) => ({
model,
stream: false,
max_tokens: 64,
messages: [{ role: "user", content: "What is 2+2?" }],
tools: [
{
type: "function",
function: {
name: "calculate",
description: "Perform arithmetic",
parameters: {
type: "object",
properties: {
expression: { type: "string", description: "The math expression", optional: true },
precision: { type: "number", description: "Decimal precision", optional: true },
note: { type: "string", optional: true },
},
required: [],
},
},
},
],
});
async function callChat(body) {
const res = await fetch(`${BASE_URL}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify(body),
});
const text = await res.text();
let json = null;
try { json = JSON.parse(text); } catch { /* non-json */ }
return { status: res.status, text, json };
}
describe.skipIf(!RUN_REAL).concurrent("antigravity models — real", () => {
for (const model of AG_MODELS) {
it(`${model}: simple prompt`, async () => {
const { status, json, text } = await callChat(SIMPLE_BODY(model));
// Credential/quota issues are not translation bugs → warn and pass
if ([401, 402, 403, 429].includes(status)) {
console.warn(`[skip] ${model}: ${status} (credential/quota)`);
return;
}
// Provider-side 400 (e.g. stream_options, model quirks) → skip
if (status === 400) {
console.warn(`[skip] ${model}: 400 (provider quirk): ${text.slice(0, 200)}`);
return;
}
expect(status, `${model} simple: ${text.slice(0, 300)}`).toBe(200);
// Accept either OpenAI-shaped or raw Gemini-shaped response
const hasContent =
json?.choices?.[0]?.message?.content ||
json?.choices?.[0]?.message?.tool_calls ||
json?.candidates?.[0]?.content?.parts?.length > 0 ||
json?.choices?.[0]?.finish_reason;
expect(hasContent, `${model} simple: no content in ${text.slice(0, 300)}`).toBeTruthy();
}, TIMEOUT_MS);
it(`${model}: tool call with optional field in schema`, async () => {
const { status, json, text } = await callChat(TOOL_BODY(model));
if ([401, 402, 403, 429].includes(status)) {
console.warn(`[skip] ${model}: ${status} (credential/quota)`);
return;
}
// 400 with "optional" + "Cannot find field" = the specific bug
if (status === 400) {
const errMsg = json?.error?.message || text;
if (/optional.*Cannot find field|Cannot find field.*optional|Unknown name.*optional/i.test(errMsg)) {
throw new Error(`BUG: ${model} — 400 due to optional field: ${errMsg.slice(0, 300)}`);
}
// Other 400 (e.g. model doesn't support tools, stream_options issue) → skip
console.warn(`[skip] ${model}: 400 non-optional error: ${errMsg.slice(0, 200)}`);
return;
}
expect(status, `${model} tool: ${text.slice(0, 300)}`).toBe(200);
}, TIMEOUT_MS);
}
});

View File

@@ -0,0 +1,168 @@
// REAL survey: send PDF/DOCX as base64 to every active provider, using the file/document
// shape NATIVE to each provider's format (claude=document, gemini=inlineData, openai=file).
// Purpose: discover which providers actually accept inline base64 documents and how they
// reject DOCX. Survey-only: logs a grouped table, never asserts on accept/reject outcome.
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/file-base64-survey.real.test.js
import { describe, it, expect, afterAll } from "vitest";
import { getProviderCredentials } from "../../../src/sse/services/auth.js";
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js";
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js";
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js";
import { getTargetFormat } from "../../../open-sse/services/provider.js";
const RUN_REAL = process.env.RUN_REAL === "1";
const TIMEOUT_MS = 90000;
const CRED_ISSUE = [401, 402, 403, 429];
const CRED_MSG_RE = /subscription|unauthorized|invalid api key|invalid access token|insufficient|credits|payment|spending|organization policy|disallowed|quota|exhausted|not supported when using|not available for integrator|requires a subscription|model.*not found|does not exist|not yet known|requires a role|invalid model id/i;
const NON_CHAT_KINDS = new Set(["embedding", "image", "imageToText", "tts", "stt", "video", "music", "webSearch"]);
const PROVIDER_FILTER = (process.env.REAL_PROVIDERS || "")
.split(",").map((s) => s.trim()).filter(Boolean);
const PDF_B64 = "JVBERi0xLjEKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0+PmVuZG9iagp4cmVmCjAgNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1MiAwMDAwMCBuIAowMDAwMDAwMTAxIDAwMDAwIG4gCnRyYWlsZXI8PC9TaXplIDQvUm9vdCAxIDAgUj4+CnN0YXJ0eHJlZgoxNjYKJSVFT0Y=";
const DOCX_B64 = "UEsDBBQAAAAIAMZiz1yRzx8FvQAAACkBAAATABwAW0NvbnRlbnRfVHlwZXNdLnhtbFVUCQADA4wvagOML2p1eAsAAQT1AQAABAAAAAB9kL0OwjAMhF8lyoqoCwMDassArMDAC1ipWyKaHyXm7+1xATEwMNrf3fnkanV3g7pSyjb4Ws+KUq+a6viIlJUQn2t9Yo5LgGxO5DAXIZIX0oXkkGVMPUQ0Z+wJ5mW5ABM8k+cpjxm6qTbU4WVgtb3L+n1F7Fqt37rxVK0xxsEaZMEwUmiqvZRKtiV1wMQ7dKKCW0gttMFcnDiL/zFX3/50nYaus4a+/jEtpmAoZ+t7NxRf4tD6yacHvJ7RPAFQSwMECgAAAAAAxmLPXAAAAAAAAAAAAAAAAAUAHAB3b3JkL1VUCQADA4wvagOML2p1eAsAAQT1AQAABAAAAABQSwMEFAAAAAgAxmLPXD1WbTiKAAAAwAAAABEAHAB3b3JkL2RvY3VtZW50LnhtbFVUCQADA4wvagOML2p1eAsAAQT1AQAABAAAAABFjtEOgjAMRX9l2QdQ9MEHMuDV30BWgWRbl7aK/r0bxvhymuakt9eNrxjME1k2Sr09Na0dB7d3nuZHxKSm6CTd3ttVNXcAMq8YJ2koYyruThwnLSsvsBP7zDSjyJaWGODctheI05ZsjbyRf9eZK7hChyuGQKYcBm8URc3vr4OqK/lgPviNgH+94QNQSwECHgMUAAAACADGYs9ckc8fBb0AAAApAQAAEwAYAAAAAAABAAAApIEAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFVUBQADA4wvanV4CwABBPUBAAAEAAAAAFBLAQIeAwoAAAAAAMZiz1wAAAAAAAAAAAAAAAAFABgAAAAAAAAAEADtQQoBAAB3b3JkL1VUBQADA4wvanV4CwABBPUBAAAEAAAAAFBLAQIeAxQAAAAIAMZiz1w9Vm04igAAAMAAAAARABgAAAAAAAEAAACkgUkBAAB3b3JkL2RvY3VtZW50LnhtbFVUBQADA4wvanV4CwABBPUBAAAEAAAAAFBLBQYAAAAAAwADAPsAAAAeAgAAAAA=";
const PDF_MIME = "application/pdf";
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
// Build a native-format request body carrying one base64 document.
// fmt = provider target format; returns { sourceFormat, body } or null if format unsupported here.
function buildFileBody(fmt, mime, b64) {
const ask = "Summarize this document in one word.";
if (fmt === "claude") {
return { sourceFormat: "claude", body: {
max_tokens: 64, stream: true,
messages: [{ role: "user", content: [
{ type: "text", text: ask },
{ type: "document", source: { type: "base64", media_type: mime, data: b64 } },
] }],
} };
}
if (fmt === "gemini" || fmt === "gemini-cli" || fmt === "antigravity") {
const gem = {
contents: [{ role: "user", parts: [
{ text: ask },
{ inlineData: { mimeType: mime, data: b64 } },
] }],
generationConfig: { maxOutputTokens: 64 },
};
return fmt === "antigravity"
? { sourceFormat: "antigravity", body: { request: gem, userAgent: "antigravity" } }
: { sourceFormat: fmt, body: gem };
}
// openai + openai-compat: Chat Completions file block (file_data data URI).
return { sourceFormat: "openai", body: {
max_tokens: 64, stream: true,
messages: [{ role: "user", content: [
{ type: "text", text: ask },
{ type: "file", file: { filename: `doc.${mime === PDF_MIME ? "pdf" : "docx"}`, file_data: `data:${mime};base64,${b64}` } },
] }],
} };
}
const results = [];
async function drainSSE(response) {
if (!response?.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let out = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
function chatModels(providerId) {
return getModelsByProviderId(providerId).filter((m) => !NON_CHAT_KINDS.has(m.kind || m.type || "llm"));
}
function targetProviders() {
try {
const Database = require("better-sqlite3");
const os = require("os");
const path = require("path");
const dbPath = process.env.DATA_DIR
? path.join(process.env.DATA_DIR, "db", "data.sqlite")
: path.join(os.homedir(), ".9router", "db", "data.sqlite");
const db = new Database(dbPath, { readonly: true });
const rows = db.prepare("SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1").all();
db.close();
let list = rows.map((r) => r.provider).sort();
if (PROVIDER_FILTER.length) list = list.filter((p) => PROVIDER_FILTER.includes(p));
return list;
} catch {
return [];
}
}
// One model per provider is enough to learn provider-level document support (saves quota).
const FILE_TYPES = [["pdf", PDF_MIME, PDF_B64], ["docx", DOCX_MIME, DOCX_B64]];
describe.skipIf(!RUN_REAL)("REAL file base64 survey", () => {
const providers = RUN_REAL ? targetProviders() : [];
it("has active providers in DB", () => {
expect(providers.length).toBeGreaterThan(0);
});
for (const providerId of providers) {
const model = (RUN_REAL ? chatModels(providerId)[0]?.id : null);
if (!model) continue;
for (const [kind, mime, b64] of FILE_TYPES) {
it.concurrent(`${kind} | ${providerId} / ${model}`, async () => {
const fmt = getTargetFormat(providerId);
const { sourceFormat, body } = buildFileBody(fmt, mime, b64);
const credentials = await getProviderCredentials(providerId, new Set(), model);
if (!credentials || credentials.allRateLimited) {
results.push({ kind, providerId, model, fmt, status: "no-cred", verdict: "skip" });
return expect(true).toBe(true);
}
const refreshed = await checkAndRefreshToken(providerId, credentials);
const result = await handleChatCore({
body: { ...body, model: `${providerId}/${model}` },
modelInfo: { provider: providerId, model },
credentials: refreshed,
connectionId: credentials.connectionId,
sourceFormatOverride: sourceFormat,
});
const status = Number(result.status) || (result.success ? 200 : 0);
const errMsg = result.success ? "" : String(result.error || "");
const credIssue = CRED_ISSUE.includes(status) || CRED_MSG_RE.test(errMsg);
const ok = result.success;
let verdict;
if (credIssue) verdict = "skip-cred";
else if (ok) verdict = `ok-${kind}`;
else verdict = `reject-${kind}`;
if (ok) await drainSSE(result.response).catch(() => {});
results.push({ kind, providerId, model, fmt, status, verdict, error: ok ? "" : errMsg.slice(0, 90) });
return expect(true).toBe(true);
}, TIMEOUT_MS);
}
}
afterAll(() => {
const w = (s) => process.stdout.write(s + "\n");
if (!results.length) { w("[file-survey] no results collected"); return; }
const rank = (v) => (v.startsWith("ok") ? 0 : v.startsWith("reject") ? 1 : 2);
const groups = [...new Set(results.map((r) => r.verdict))].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
w("\n================ FILE BASE64 SURVEY ================");
for (const g of groups) {
const rows = results.filter((r) => r.verdict === g);
if (!rows.length) continue;
w(`\n### ${g} (${rows.length})`);
for (const r of rows) {
w(` [${r.status}] ${r.kind} ${r.providerId}/${r.model} fmt=${r.fmt}${r.error ? ` :: ${r.error}` : ""}`);
}
}
w("\n================ END SURVEY ================\n");
});
});

View File

@@ -0,0 +1,168 @@
// B2: REAL behavior assertions for the risky provider-specific cases.
// Unlike smoke (only "doesn't crash"), each test asserts concrete OUTPUT.
// Gated by RUN_REAL=1; any provider lacking creds/model or returning an auth/quota
// status (401/402/403/429) is skipped (console.warn + pass).
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/provider-cases.real.test.js
import { describe, it, expect } from "vitest";
import { getProviderCredentials } from "../../../src/sse/services/auth.js";
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js";
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js";
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js";
const RUN_REAL = process.env.RUN_REAL === "1";
const TIMEOUT_MS = 90000;
const CRED_ISSUE = [401, 402, 403, 429];
// Pick the first plain llm model for a provider.
function firstLlmModel(providerId) {
const models = getModelsByProviderId(providerId);
const llm = models.find((m) => (m.type || "llm") === "llm");
return llm?.id || null;
}
async function drainSSE(response) {
if (!response?.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let out = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
// Resolve creds+model for a provider, or null when unavailable (caller skips).
async function prepare(providerId) {
const model = firstLlmModel(providerId);
if (!model) {
console.warn(`[skip] ${providerId}: no llm model`);
return null;
}
const credentials = await getProviderCredentials(providerId, new Set(), model);
if (!credentials || credentials.allRateLimited) {
console.warn(`[skip] ${providerId}: no usable credential`);
return null;
}
const refreshed = await checkAndRefreshToken(providerId, credentials);
return { model, credentials, refreshed };
}
// Run handleChatCore + drain; returns { raw } or null if cred/quota issue (caller skips).
async function runChat(providerId, prep, body) {
const result = await handleChatCore({
body: { model: `${providerId}/${prep.model}`, ...body },
modelInfo: { provider: providerId, model: prep.model },
credentials: prep.refreshed,
connectionId: prep.credentials.connectionId,
});
if (!result.success) {
if (CRED_ISSUE.includes(Number(result.status))) {
console.warn(`[skip] ${providerId}: ${result.status} (credential/quota)`);
return null;
}
throw new Error(`${providerId} failed: ${result.status} ${result.error}`);
}
return { raw: await drainSSE(result.response) };
}
describe.skipIf(!RUN_REAL)("REAL provider behavior cases", () => {
// Case #1: Gemini normal prompt -> finish_reason "stop".
it("gemini: finish_reason stop", async () => {
const prep = await prepare("gemini");
if (!prep) return expect(true).toBe(true);
// Generous max_tokens so reasoning models (gemini-3 pro) don't hit "length" first.
const out = await runChat("gemini", prep, {
stream: true,
max_tokens: 2048,
messages: [{ role: "user", content: "Reply with the single word: hi" }],
});
if (!out) return expect(true).toBe(true);
expect(/"finish_reason"\s*:\s*"stop"/.test(out.raw), "no stop finish_reason").toBe(true);
}, TIMEOUT_MS);
// Case #4: Kiro tool turn -> tool_calls finish_reason + tool_calls delta.
it("kiro: tool turn -> tool_calls", async () => {
const prep = await prepare("kiro");
if (!prep) return expect(true).toBe(true);
const out = await runChat("kiro", prep, {
stream: true,
max_tokens: 128,
tool_choice: "auto",
tools: [{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: { city: { type: "string", description: "City name" } },
required: ["city"],
},
},
}],
messages: [{ role: "user", content: "What's the weather in Paris? Use the get_weather tool." }],
});
if (!out) return expect(true).toBe(true);
expect(/"finish_reason"\s*:\s*"tool_calls"/.test(out.raw), "no tool_calls finish_reason").toBe(true);
expect(/"tool_calls"/.test(out.raw), "no tool_calls delta").toBe(true);
}, TIMEOUT_MS);
// Case #3: Ollama tiny max_tokens + long prompt -> finish_reason "length".
it("ollama: max_tokens -> length", async () => {
const prep = await prepare("ollama");
if (!prep) return expect(true).toBe(true);
const out = await runChat("ollama", prep, {
stream: true,
max_tokens: 4,
messages: [{ role: "user", content: "Write a long detailed essay about the history of computing." }],
});
if (!out) return expect(true).toBe(true);
// length is model-dependent; if the model stopped on its own, skip rather than fail.
if (!/"finish_reason"\s*:\s*"length"/.test(out.raw)) {
console.warn("[skip] ollama: model did not hit length (output shorter than max_tokens)");
return expect(true).toBe(true);
}
expect(/"finish_reason"\s*:\s*"length"/.test(out.raw)).toBe(true);
}, TIMEOUT_MS);
// Case #4/#5: Codex multi-turn -> session stickiness (prompt-cache hit on 2nd turn).
it("codex: session stickiness (cached_tokens on 2nd turn)", async () => {
const prep = await prepare("codex");
if (!prep) return expect(true).toBe(true);
const longContext = "The capital of France is Paris. ".repeat(40);
const messages = [
{ role: "user", content: longContext },
{ role: "assistant", content: "Understood. I have noted that context." },
{ role: "user", content: "Reply with the single word: ok" },
];
const body = { stream: true, max_tokens: 32, messages };
const first = await runChat("codex", prep, body);
if (!first) return expect(true).toBe(true);
const second = await runChat("codex", prep, body);
if (!second) return expect(true).toBe(true);
// 2nd identical-context turn should hit prompt cache when session is sticky.
const m = second.raw.match(/"cached_tokens"\s*:\s*(\d+)/);
if (!m) {
console.warn("[skip] codex: no cached_tokens in usage (provider may not report)");
return expect(true).toBe(true);
}
expect(Number(m[1]), "cached_tokens not > 0 on 2nd turn").toBeGreaterThan(0);
}, TIMEOUT_MS);
// Case #1/#2: Antigravity normal prompt -> valid SSE response.
it("antigravity: responds OK", async () => {
const prep = await prepare("antigravity");
if (!prep) return expect(true).toBe(true);
const out = await runChat("antigravity", prep, {
stream: true,
max_tokens: 32,
messages: [{ role: "user", content: "Reply with the single word: hi" }],
});
if (!out) return expect(true).toBe(true);
expect(out.raw.length, "empty response").toBeGreaterThan(0);
expect(/data:|finish_reason|"delta"|"content"|event:/.test(out.raw), "not SSE").toBe(true);
}, TIMEOUT_MS);
});

View File

@@ -0,0 +1,122 @@
// REAL integration test for thinking normalization: sends a reasoning prompt with
// reasoning_effort to every thinking-capable provider that has an active credential,
// then asserts the upstream accepted it (no 400) and emitted reasoning output.
// Gated by RUN_REAL=1 so the default `vitest run` never touches the network.
//
// RUN_REAL=1 npx vitest run -c tests/vitest.config.js "tests/translator/real/thinking"
// RUN_REAL=1 REAL_PROVIDERS=claude,glm,deepseek npx vitest run -c tests/vitest.config.js "tests/translator/real/thinking"
//
import { describe, it, expect } from "vitest";
import { getProviderCredentials } from "../../../src/sse/services/auth.js";
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js";
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js";
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js";
import { getCapabilitiesForModel } from "../../../open-sse/providers/capabilities.js";
const RUN_REAL = process.env.RUN_REAL === "1";
const MAX_TOKENS = 512;
const TIMEOUT_MS = 120000;
const EFFORT = process.env.THINK_EFFORT || "high";
const PROVIDER_FILTER = (process.env.REAL_PROVIDERS || "")
.split(",").map((s) => s.trim()).filter(Boolean);
// First plain llm model that the capability registry marks as reasoning-capable.
function firstReasoningModel(providerId) {
const models = getModelsByProviderId(providerId);
for (const m of models) {
if ((m.type || "llm") !== "llm") continue;
if (/embedding|image|tts|whisper|rerank|vision-model/i.test(m.id)) continue;
if (getCapabilitiesForModel(providerId, m.id).reasoning) return m.id;
}
return null;
}
async function drainSSE(response) {
if (!response?.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let out = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
describe.skipIf(!RUN_REAL).concurrent("REAL thinking normalization", () => {
it("has active providers in DB", () => {
expect(targetProviders().length).toBeGreaterThan(0);
});
for (const providerId of (RUN_REAL ? targetProviders() : [])) {
it.concurrent(
`${providerId}: accepts reasoning_effort=${EFFORT} and reasons`,
async () => {
const model = firstReasoningModel(providerId);
if (!model) {
console.warn(`[skip] ${providerId}: no reasoning-capable model`);
return expect(true).toBe(true);
}
const credentials = await getProviderCredentials(providerId, new Set(), model);
if (!credentials || credentials.allRateLimited) {
console.warn(`[skip] ${providerId}: no usable credential`);
return expect(true).toBe(true);
}
const refreshed = await checkAndRefreshToken(providerId, credentials);
const result = await handleChatCore({
body: {
model: `${providerId}/${model}`,
stream: true,
max_tokens: MAX_TOKENS,
reasoning_effort: EFFORT,
messages: [{ role: "user", content: "Think step by step, then answer: what is 17 * 23?" }],
},
modelInfo: { provider: providerId, model },
credentials: refreshed,
connectionId: credentials.connectionId,
});
if (!result.success) {
// 400 = our thinking payload was rejected → real bug. Other codes = credential/quota.
const credIssue = [401, 402, 403, 429].includes(Number(result.status));
if (credIssue) {
console.warn(`[skip] ${providerId}: ${result.status} (credential/quota)`);
return expect(true).toBe(true);
}
console.error(`[REJECT] ${providerId}/${model} ${result.status}:`, JSON.stringify(result.error)?.slice(0, 600));
throw new Error(`${providerId}/${model} thinking REJECTED: ${result.status}`);
}
const raw = await drainSSE(result.response);
expect(raw.length, `${providerId}: empty response`).toBeGreaterThan(0);
const hasReasoning = /reasoning_content|"thinking"|reasoning_details|<think/.test(raw);
// Log so the operator can eyeball which providers actually streamed reasoning.
console.log(`[ok] ${providerId}/${model} reasoning=${hasReasoning}`);
expect(/data:|"delta"|"content"|finish_reason/.test(raw), `${providerId}: not SSE`).toBe(true);
},
TIMEOUT_MS
);
}
});
function targetProviders() {
try {
const Database = require("better-sqlite3");
const os = require("os");
const path = require("path");
const dbPath = process.env.DATA_DIR
? path.join(process.env.DATA_DIR, "db", "data.sqlite")
: path.join(os.homedir(), ".9router", "db", "data.sqlite");
const db = new Database(dbPath, { readonly: true });
const rows = db.prepare("SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1").all();
db.close();
let list = rows.map((r) => r.provider).sort();
if (PROVIDER_FILTER.length) list = list.filter((p) => PROVIDER_FILTER.includes(p));
return list;
} catch {
return [];
}
}

View File

@@ -0,0 +1,183 @@
// REAL survey: send an image to EVERY chat LLM (all providers with active creds) and
// record how each model behaves vs its declared vision capability.
// Purpose: discover where non-vision models 400 on image input (need auto-strip) and
// where capability data is wrong. This is a SURVEY: it logs a table, it does not fail
// on capability mismatches (only real harness errors throw).
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/vision-capability-survey.real.test.js
// RUN_REAL=1 REAL_PROVIDERS=mistral,nvidia npx vitest run ... (optional filter)
import { describe, it, expect, afterAll } from "vitest";
import { getProviderCredentials } from "../../../src/sse/services/auth.js";
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js";
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js";
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js";
import { getCapabilitiesForModel } from "../../../open-sse/providers/capabilities.js";
const RUN_REAL = process.env.RUN_REAL === "1";
const TIMEOUT_MS = 90000;
const CRED_ISSUE = [401, 402, 403, 429];
const NON_CHAT_KINDS = new Set(["embedding", "image", "imageToText", "tts", "stt", "video", "music", "webSearch"]);
const PROVIDER_FILTER = (process.env.REAL_PROVIDERS || "")
.split(",").map((s) => s.trim()).filter(Boolean);
// Valid 16x16 red PNG (1x1 is rejected as malformed by some strict providers).
const PNG_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAFklEQVR4nGO4I2JDEmIY1TCqYfhqAAAeBCwQ8YdREQAAAABJRU5ErkJggg==";
// Tiny silent WAV (44-byte header, no samples) — enough to probe audioInput acceptance.
const WAV_B64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
// Stable public image URL (probe remote-URL handling vs base64).
const IMAGE_REMOTE_URL = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
// Tiny valid PDF (probe file/document handling).
const PDF_B64 = "JVBERi0xLjEKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0+PmVuZG9iagp4cmVmCjAgNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1MiAwMDAwMCBuIAowMDAwMDAwMTAxIDAwMDAwIG4gCnRyYWlsZXI8PC9TaXplIDQvUm9vdCAxIDAgUj4+CnN0YXJ0eHJlZgoxNjYKJSVFT0Y=";
const PDF_DATA_URI = `data:application/pdf;base64,${PDF_B64}`;
// Capability probes: each sends one modality-specific content block. cap = capability flag tested.
const CAPABILITY_PROBES = {
vision: {
cap: "vision",
content: [
{ type: "text", text: "What is in this image? One word." },
{ type: "image_url", image_url: { url: PNG_DATA_URI } },
],
},
audio: {
cap: "audioInput",
content: [
{ type: "text", text: "Transcribe this audio. One word." },
{ type: "input_audio", input_audio: { data: WAV_B64, format: "wav" } },
],
},
imageUrl: {
cap: "vision",
content: [
{ type: "text", text: "What is in this image? One word." },
{ type: "image_url", image_url: { url: IMAGE_REMOTE_URL } },
],
},
file: {
cap: "pdf",
content: [
{ type: "text", text: "Summarize this document. One word." },
{ type: "file", file: { filename: "doc.pdf", file_data: PDF_DATA_URI } },
],
},
};
// Set PROBES=vision,audio (default vision only to limit live quota).
const ACTIVE_PROBES = (process.env.PROBES || "vision").split(",").map((s) => s.trim()).filter(Boolean);
// Status/message patterns that indicate account/credential issues (not a vision-capability signal).
const CRED_MSG_RE = /subscription|unauthorized|invalid api key|invalid access token|insufficient|credits|payment|spending|organization policy|disallowed|quota|exhausted|not supported when using|not available for integrator|requires a subscription|model.*not found|does not exist|not yet known|requires a role/i;
// Collected rows for the end-of-run summary table.
const results = [];
async function drainSSE(response) {
if (!response?.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let out = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
function chatModels(providerId) {
return getModelsByProviderId(providerId).filter((m) => !NON_CHAT_KINDS.has(m.kind || m.type || "llm"));
}
function targetProviders() {
try {
const Database = require("better-sqlite3");
const os = require("os");
const path = require("path");
const dbPath = process.env.DATA_DIR
? path.join(process.env.DATA_DIR, "db", "data.sqlite")
: path.join(os.homedir(), ".9router", "db", "data.sqlite");
const db = new Database(dbPath, { readonly: true });
const rows = db.prepare("SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1").all();
db.close();
let list = rows.map((r) => r.provider).sort();
if (PROVIDER_FILTER.length) list = list.filter((p) => PROVIDER_FILTER.includes(p));
return list;
} catch {
return [];
}
}
describe.skipIf(!RUN_REAL)("REAL vision capability survey", () => {
const providers = RUN_REAL ? targetProviders() : [];
it("has active providers in DB", () => {
expect(providers.length).toBeGreaterThan(0);
});
for (const providerId of providers) {
for (const m of (RUN_REAL ? chatModels(providerId) : [])) {
const model = m.id;
for (const probeName of ACTIVE_PROBES) {
const probe = CAPABILITY_PROBES[probeName];
it.concurrent(`${probeName} | ${providerId} / ${model}`, async () => {
const caps = getCapabilitiesForModel(providerId, model);
const capOn = !!caps[probe.cap];
const credentials = await getProviderCredentials(providerId, new Set(), model);
if (!credentials || credentials.allRateLimited) {
results.push({ probe: probeName, providerId, model, cap: capOn, status: "no-cred", verdict: "skip" });
return expect(true).toBe(true);
}
const refreshed = await checkAndRefreshToken(providerId, credentials);
const result = await handleChatCore({
body: {
model: `${providerId}/${model}`,
stream: true,
max_tokens: 64,
messages: [{ role: "user", content: probe.content }],
},
modelInfo: { provider: providerId, model },
credentials: refreshed,
connectionId: credentials.connectionId,
});
const status = Number(result.status) || (result.success ? 200 : 0);
const errMsg = result.success ? "" : String(result.error || "");
const credIssue = CRED_ISSUE.includes(status) || CRED_MSG_RE.test(errMsg);
const ok = result.success;
// Classify outcome vs declared capability (cred/account noise filtered out).
let verdict;
if (credIssue) verdict = "skip-cred";
else if (ok && capOn) verdict = `ok-${probeName}`;
else if (ok && !capOn) verdict = `ok-no${probeName}-tolerated`; // provider silently ignored modality
else if (!ok && !capOn) verdict = `FAIL-no${probeName}-needs-strip`; // target problem
else verdict = `FAIL-${probeName}-capdata-wrong`; // declared cap but rejected
if (ok) await drainSSE(result.response).catch(() => {});
results.push({ probe: probeName, providerId, model, cap: capOn, status, verdict, error: ok ? "" : String(result.error || "").slice(0, 80) });
// Survey never fails on capability outcome.
return expect(true).toBe(true);
}, TIMEOUT_MS);
}
}
}
afterAll(() => {
if (!results.length) return;
// FAIL groups first (most actionable), then ok/skip; alphabetical within rank.
const rank = (v) => (v.startsWith("FAIL") ? 0 : v.startsWith("ok") ? 1 : 2);
const groups = [...new Set(results.map((r) => r.verdict))]
.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
console.log("\n================ CAPABILITY SURVEY ================");
for (const g of groups) {
const rows = results.filter((r) => r.verdict === g);
if (!rows.length) continue;
console.log(`\n### ${g} (${rows.length})`);
for (const r of rows) {
console.log(` [${r.status}] ${r.probe} ${r.providerId}/${r.model} cap=${r.cap}${r.error ? ` :: ${r.error}` : ""}`);
}
}
console.log("\n================ END SURVEY ================\n");
});
});

View File

@@ -11,6 +11,7 @@ import "../../open-sse/translator/request/openai-to-kiro.js";
import "../../open-sse/translator/request/openai-to-cursor.js";
import "../../open-sse/translator/request/openai-to-ollama.js";
import "../../open-sse/translator/request/openai-to-commandcode.js";
import "../../open-sse/translator/request/claude-to-kiro.js";
import "../../open-sse/translator/response/claude-to-openai.js";
import "../../open-sse/translator/response/openai-to-claude.js";
import "../../open-sse/translator/response/gemini-to-openai.js";
@@ -20,3 +21,4 @@ import "../../open-sse/translator/response/kiro-to-openai.js";
import "../../open-sse/translator/response/cursor-to-openai.js";
import "../../open-sse/translator/response/ollama-to-openai.js";
import "../../open-sse/translator/response/commandcode-to-openai.js";
import "../../open-sse/translator/response/kiro-to-claude.js";

View File

@@ -0,0 +1,131 @@
// Unit tests for unified thinking normalization (thinkingUnified.js).
// Covers extract, suffix parse, and per-provider apply per MATRIX (.docs/thinking/plan.md).
import { describe, it, expect } from "vitest";
import {
parseSuffix,
extractThinking,
applyThinking,
} from "../../open-sse/translator/concerns/thinkingUnified.js";
import { extractReasoningText } from "../../open-sse/translator/concerns/reasoning.js";
const apply = (targetFormat, model, body, provider) => {
const b = JSON.parse(JSON.stringify(body));
applyThinking(targetFormat, model, b, provider);
return b;
};
describe("parseSuffix", () => {
it("parses level suffix", () => {
expect(parseSuffix("gpt-5(high)")).toEqual({ cleanModel: "gpt-5", override: { mode: "level", level: "high" } });
});
it("parses numeric budget suffix", () => {
expect(parseSuffix("model(8192)")).toEqual({ cleanModel: "model", override: { mode: "budget", budget: 8192 } });
});
it("parses auto / none", () => {
expect(parseSuffix("m(auto)").override).toEqual({ mode: "auto" });
expect(parseSuffix("m(none)").override).toEqual({ mode: "none" });
});
it("no suffix → passthrough", () => {
expect(parseSuffix("claude-opus-4.7")).toEqual({ cleanModel: "claude-opus-4.7", override: null });
});
});
describe("extractThinking", () => {
it("claude enabled+budget", () => {
expect(extractThinking({ thinking: { type: "enabled", budget_tokens: 4096 } })).toEqual({ mode: "budget", budget: 4096 });
});
it("claude disabled", () => {
expect(extractThinking({ thinking: { type: "disabled" } })).toEqual({ mode: "none" });
});
it("openai reasoning_effort", () => {
expect(extractThinking({ reasoning_effort: "high" })).toEqual({ mode: "level", level: "high" });
});
it("responses reasoning.effort none", () => {
expect(extractThinking({ reasoning: { effort: "none" } })).toEqual({ mode: "none" });
});
it("gemini thinkingBudget 0 → none", () => {
expect(extractThinking({ thinkingConfig: { thinkingBudget: 0 } })).toEqual({ mode: "none" });
});
it("qwen enable_thinking false", () => {
expect(extractThinking({ enable_thinking: false })).toEqual({ mode: "none" });
});
it("no intent → null", () => {
expect(extractThinking({ messages: [] })).toBeNull();
});
});
describe("applyThinking per provider format", () => {
it("claude 4.6+ → adaptive output_config (no budget_tokens)", () => {
const out = apply("claude", "claude-opus-4.7", { reasoning_effort: "high" }, "claude");
expect(out.output_config).toEqual({ effort: "high" });
expect(out.thinking).toBeUndefined();
});
it("claude haiku → enabled+budget", () => {
const out = apply("claude", "claude-haiku-4.5", { reasoning_effort: "high" }, "claude");
expect(out.thinking).toEqual({ type: "enabled", budget_tokens: 24576 });
});
it("gemini-3 → thinkingLevel", () => {
const out = apply("gemini", "gemini-3-pro", { reasoning_effort: "medium" }, "gemini");
expect(out.generationConfig.thinkingConfig.thinkingLevel).toBe("medium");
});
it("gemini-2.5 → thinkingBudget", () => {
const out = apply("gemini", "gemini-2.5-flash", { reasoning_effort: "high" }, "gemini");
expect(out.generationConfig.thinkingConfig.thinkingBudget).toBe(24576);
expect(out.generationConfig.thinkingConfig.thinkingLevel).toBeUndefined();
});
it("GLM off → enable_thinking:false (not thinking.disabled)", () => {
const out = apply("openai", "glm-4.6", { reasoning_effort: "none" }, "glm");
expect(out.enable_thinking).toBe(false);
expect(out.thinking).toBeUndefined();
});
it("Qwen on → enable_thinking + thinking_budget", () => {
const out = apply("openai", "qwen3-max", { reasoning_effort: "medium" }, "qwen");
expect(out.enable_thinking).toBe(true);
expect(out.thinking_budget).toBe(8192);
});
it("QwQ cannot disable → clamp minimal", () => {
const out = apply("openai", "qwq-32b", { reasoning_effort: "none" }, "qwen");
expect(out.enable_thinking).toBe(true);
});
it("DeepSeek → enabled + reasoning_effort high (low→high)", () => {
const out = apply("openai", "deepseek-v4-pro", { reasoning_effort: "low" }, "deepseek");
expect(out.thinking).toEqual({ type: "enabled" });
expect(out.reasoning_effort).toBe("high");
});
it("Kimi on → reasoning_effort", () => {
const out = apply("openai", "kimi-k2.6", { reasoning_effort: "high" }, "kimi");
expect(out.reasoning_effort).toBe("high");
});
it("MiniMax M3 → adaptive", () => {
const out = apply("claude", "MiniMax-M3", { reasoning_effort: "high" }, "minimax");
expect(out.thinking).toEqual({ type: "adaptive" });
});
it("non-reasoning model → strips thinking", () => {
const out = apply("openai", "gpt-4o", { reasoning_effort: "high" }, "openai");
expect(out.reasoning_effort).toBeUndefined();
});
it("aggregator (siliconflow) GLM model → forced openai reasoning_effort", () => {
const out = apply("openai", "zai-org/GLM-5", { reasoning_effort: "high" }, "siliconflow");
expect(out.reasoning_effort).toBe("high");
expect(out.enable_thinking).toBeUndefined();
});
it("suffix overrides body", () => {
const out = apply("openai", "gpt-5(low)", { reasoning_effort: "high" }, "openai");
expect(out.reasoning_effort).toBe("low");
});
});
describe("extractReasoningText (response shapes)", () => {
it("reasoning_content (GLM/Qwen/DeepSeek)", () => {
expect(extractReasoningText({ reasoning_content: "abc" })).toBe("abc");
});
it("reasoning fallback", () => {
expect(extractReasoningText({ reasoning: "xyz" })).toBe("xyz");
});
it("reasoning_details[] (MiniMax split)", () => {
expect(extractReasoningText({ reasoning_details: [{ text: "a" }, { content: "b" }, "c"] })).toBe("abc");
});
it("no reasoning → empty", () => {
expect(extractReasoningText({ content: "hello" })).toBe("");
});
});

View File

@@ -0,0 +1,50 @@
// Guards the deduped Antigravity OAuth client: same values across all 3 sources after refactor.
import { describe, it, expect } from "vitest";
const EXPECTED = {
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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",
};
const GOOGLE = {
clientId: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
clientSecret: "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",
};
describe("antigravity oauth client (deduped)", () => {
it("shared source holds the canonical credentials", async () => {
const { ANTIGRAVITY_OAUTH_CLIENT } = await import("../../open-sse/providers/shared.js");
expect(ANTIGRAVITY_OAUTH_CLIENT).toEqual(EXPECTED);
});
it("registry transport keeps clientId/clientSecret", async () => {
const ag = (await import("../../open-sse/providers/registry/antigravity.js")).default;
expect(ag.transport.clientId).toBe(EXPECTED.clientId);
expect(ag.transport.clientSecret).toBe(EXPECTED.clientSecret);
});
it("google client shared by gemini + gemini-cli", async () => {
const { GOOGLE_OAUTH_CLIENT } = await import("../../open-sse/providers/shared.js");
expect(GOOGLE_OAUTH_CLIENT).toEqual(GOOGLE);
const gemini = (await import("../../open-sse/providers/registry/gemini.js")).default;
const gc = (await import("../../open-sse/providers/registry/gemini-cli.js")).default;
expect(gemini.transport.clientSecret).toBe(GOOGLE.clientSecret);
expect(gc.transport.clientSecret).toBe(GOOGLE.clientSecret);
});
// Guard: oauth.js must spread shared clients + derive from registry (PROVIDER_OAUTH).
it("src oauth.js imports shared client + keeps full shape", async () => {
const { readFileSync } = await import("node:fs");
const { fileURLToPath } = await import("node:url");
const { dirname, join } = await import("node:path");
const here = dirname(fileURLToPath(import.meta.url));
const src = readFileSync(join(here, "../../src/lib/oauth/constants/oauth.js"), "utf8");
expect(src).toContain('import { ANTIGRAVITY_OAUTH_CLIENT, GOOGLE_OAUTH_CLIENT } from "open-sse/providers/shared.js"');
expect(src).toContain("...ANTIGRAVITY_OAUTH_CLIENT");
expect(src).toContain("...GOOGLE_OAUTH_CLIENT");
// authorizeUrl now lives in registry; oauth.js derives via PROVIDER_OAUTH spread
expect(src).toContain('PROVIDER_OAUTH["antigravity"]');
expect(src).toContain('PROVIDER_OAUTH["gemini-cli"]');
expect(src).not.toContain(EXPECTED.clientSecret); // antigravity secret no longer hardcoded here
expect(src).not.toContain(GOOGLE.clientSecret); // gemini secret no longer hardcoded here
});
});

View File

@@ -0,0 +1,44 @@
// Guards D3: antigravity 429/503 retry merged into base via computeRetryDelay hook.
import { describe, it, expect } from "vitest";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
const MAX = 10000;
function res(status, headers = {}, body = null) {
return {
status,
headers: { get: (k) => headers[k.toLowerCase()] ?? null },
clone: () => ({ text: async () => (body == null ? "" : JSON.stringify(body)) }),
};
}
describe("antigravity computeRetryDelay hook (D3)", () => {
const ag = new AntigravityExecutor();
it("uses Retry-After header (seconds → ms) when within cap", async () => {
expect(await ag.computeRetryDelay(res(429, { "retry-after": "5" }), 1)).toBe(5000);
});
it("vetoes (false) when Retry-After exceeds cap", async () => {
expect(await ag.computeRetryDelay(res(429, { "retry-after": "60" }), 1)).toBe(false);
});
it("parses retry time from error body when no header", async () => {
const r = res(429, {}, { error: { message: "quota will reset after 3s" } });
expect(await ag.computeRetryDelay(r, 1)).toBe(3000);
});
it("exponential backoff for 429 when no retry info", async () => {
expect(await ag.computeRetryDelay(res(429), 1)).toBe(Math.min(1000 * 2 ** 1, MAX));
expect(await ag.computeRetryDelay(res(429), 3)).toBe(Math.min(1000 * 2 ** 3, MAX));
});
it("503 without retry info → veto (no auto backoff)", async () => {
expect(await ag.computeRetryDelay(res(503), 1)).toBe(false);
});
it("buildHeaders includes cached session id after transformRequest", () => {
ag._lastSessionId = "sess-123";
const h = ag.buildHeaders({ accessToken: "tok" }, true);
expect(h["X-Machine-Session-Id"]).toBe("sess-123");
});
});

View File

@@ -0,0 +1,97 @@
// Locks BaseExecutor.execute retry/fallback behavior (docs 04 GAP #1, docs 11 §7).
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock the network layer so we can script upstream responses.
const fetchMock = vi.fn();
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: (...args) => fetchMock(...args),
}));
const { BaseExecutor } = await import("../../open-sse/executors/base.js");
function res(status) {
return { status, headers: { get: () => "" } };
}
function makeExec(config) {
const ex = new BaseExecutor("test", config);
// make headers trivial; credentials empty
return ex;
}
const creds = { apiKey: "k" };
beforeEach(() => fetchMock.mockReset());
describe("BaseExecutor.execute — retry by status (config-driven)", () => {
it("retries 502 `attempts` times then succeeds", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 3, delayMs: 0 } } });
fetchMock
.mockResolvedValueOnce(res(502))
.mockResolvedValueOnce(res(502))
.mockResolvedValueOnce(res(200));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it("stops after exhausting 502 attempts on a single url and throws", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 2, delayMs: 0 } } });
fetchMock.mockResolvedValue(res(502));
// single url: 1 initial + 2 retries = 3 calls, then returns the 502 response (no fallback url)
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(502);
expect(fetchMock).toHaveBeenCalledTimes(3);
});
});
describe("BaseExecutor.execute — baseUrls fallback", () => {
it("falls over to the next url on 429 (shouldRetry)", async () => {
const ex = makeExec({ baseUrls: ["https://a/api", "https://b/api"], retry: { 429: { attempts: 0 } } });
fetchMock
.mockResolvedValueOnce(res(429)) // url[0] → fallback
.mockResolvedValueOnce(res(200)); // url[1] ok
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(out.url).toBe("https://b/api");
expect(fetchMock).toHaveBeenCalledTimes(2);
});
});
describe("BaseExecutor.execute — network error retry/fallback", () => {
it("maps network exception to 502 retry config", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 1, delayMs: 0 } } });
fetchMock
.mockImplementationOnce(async () => { throw new Error("ECONNRESET"); })
.mockResolvedValueOnce(res(200));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("throws when the only url fails with network error and no retries left", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 0 } } });
// mockImplementationOnce (not persistent) avoids vitest flagging a reused rejection.
fetchMock.mockImplementationOnce(async () => { throw new Error("boom"); });
let thrown = null;
try {
await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
} catch (e) {
thrown = e;
}
expect(thrown?.message).toBe("boom");
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
describe("BaseExecutor.execute — computeRetryDelay hook veto", () => {
it("hook returning false skips retry (uses fallback path)", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 429: { attempts: 5, delayMs: 0 } } });
ex.computeRetryDelay = vi.fn().mockResolvedValue(false);
fetchMock.mockResolvedValueOnce(res(429));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
// hook vetoes retry → no fallback url → returns the 429 response as-is
expect(out.response.status).toBe(429);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { capabilitiesFromServiceKind } from "../../open-sse/providers/capabilities.js";
describe("capabilitiesFromServiceKind", () => {
it("maps imageToText custom models to vision-capable runtime models", () => {
expect(capabilitiesFromServiceKind("imageToText")).toMatchObject({ vision: true });
});
it("maps media output/input custom model kinds to runtime capabilities", () => {
expect(capabilitiesFromServiceKind("image")).toMatchObject({ imageOutput: true });
expect(capabilitiesFromServiceKind("stt")).toMatchObject({ audioInput: true });
expect(capabilitiesFromServiceKind("tts")).toMatchObject({ audioOutput: true });
});
});

View File

@@ -9,24 +9,40 @@
*/
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
// Mock DNS so the SSRF guard treats example.com as public.
vi.mock("node:dns/promises", () => ({ lookup: async () => ({ address: "93.184.216.34" }) }));
import { CodexExecutor } from "../../open-sse/executors/codex.js";
import * as proxyFetchModule from "../../open-sse/utils/proxyFetch.js";
const IMAGE_1MB_BYTES = 1024 * 1024;
const REMOTE_URL = "https://example.com/big.jpg";
const DATA_URI = "data:image/png;base64,iVBORw0KGgo=";
// JPEG magic bytes (FF D8 FF) so magic-byte verification passes.
const JPEG_MAGIC = [0xff, 0xd8, 0xff];
function makeImageBuffer(sizeBytes) {
const buf = new Uint8Array(sizeBytes);
for (let i = 0; i < sizeBytes; i++) buf[i] = i & 0xff;
return buf.buffer;
for (let i = 0; i < JPEG_MAGIC.length; i++) buf[i] = JPEG_MAGIC[i];
for (let i = JPEG_MAGIC.length; i < sizeBytes; i++) buf[i] = i & 0xff;
return buf;
}
function mockImageFetch(sizeBytes, mimeType = "image/jpeg") {
// Mock a streaming Response body (getReader) as the hardened fetcher expects.
function mockImageFetch(sizeBytes) {
const bytes = makeImageBuffer(sizeBytes);
return {
ok: true,
headers: { get: (k) => (k === "Content-Type" ? mimeType : null) },
arrayBuffer: async () => makeImageBuffer(sizeBytes),
body: {
getReader() {
let sent = false;
return {
read: async () => sent ? { done: true } : (sent = true, { done: false, value: bytes }),
cancel: async () => {},
};
},
},
};
}

View File

@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import { CodexExecutor } from "../../open-sse/executors/codex.js";
function normalizeTools(tools) {
const executor = new CodexExecutor();
const body = {
model: "gpt-5.5",
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "probe" }] }],
tools,
stream: true,
};
executor.transformRequest("gpt-5.5", body, true, {
connectionId: "test-codex-tools",
providerSpecificData: {},
});
return body.tools;
}
describe("CodexExecutor tool normalization", () => {
it("preserves Responses-native tool_search tools", () => {
const tools = normalizeTools([
{
type: "tool_search",
execution: "sync",
description: "Discover deferred tools",
parameters: { type: "object", properties: {} },
},
{
type: "namespace",
name: "codex_app",
description: "app tools",
tools: [
{
type: "function",
name: "automation_update",
description: "automation",
parameters: { type: "object", properties: {} },
defer_loading: true,
},
],
},
{
type: "function",
name: "plain_fn",
description: "plain",
parameters: { type: "object", properties: {} },
},
]);
expect(tools.map((tool) => `${tool.type}:${tool.name || ""}`)).toEqual([
"tool_search:",
"namespace:codex_app",
"function:plain_fn",
]);
});
it("preserves hosted Responses tools", () => {
const tools = normalizeTools([
{ type: "web_search", search_context_size: "medium" },
{ type: "image_generation", size: "1024x1024" },
{ type: "mcp", server_label: "docs", server_url: "https://example.com/mcp" },
{ type: "local_shell" },
{ type: "code_interpreter", container: { type: "auto" } },
{ type: "computer", display_width: 1024, display_height: 768, environment: "browser" },
]);
expect(tools.map((tool) => tool.type)).toEqual([
"web_search",
"image_generation",
"mcp",
"local_shell",
"code_interpreter",
"computer",
]);
});
it("preserves custom freeform tools with format payloads", () => {
const tools = normalizeTools([
{
type: "custom",
name: "apply_patch",
description: "patch",
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
},
]);
expect(tools).toEqual([
{
type: "custom",
name: "apply_patch",
description: "patch",
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
},
]);
});
});

View File

@@ -0,0 +1,78 @@
import { describe, it, expect } from "vitest";
import { detectRequiredCapabilities, reorderByCapabilities } from "../../open-sse/services/combo.js";
describe("detectRequiredCapabilities", () => {
it("text-only -> empty", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: "hi" }] });
expect(r.size).toBe(0);
});
it("openai image_url -> vision", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: [
{ type: "image_url", image_url: { url: "x" } },
] }] });
expect(r.has("vision")).toBe(true);
});
it("openai file -> pdf", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: [
{ type: "file", file: { file_data: "data:application/pdf;base64,x" } },
] }] });
expect(r.has("pdf")).toBe(true);
});
it("gemini inlineData image -> vision", () => {
const r = detectRequiredCapabilities({ contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] });
expect(r.has("vision")).toBe(true);
});
it("antigravity request.contents image -> vision", () => {
const r = detectRequiredCapabilities({ request: { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/jpeg", data: "x" } },
] }] } });
expect(r.has("vision")).toBe(true);
});
it("web_search tool -> search", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: "q" }], tools: [
{ type: "web_search" },
] });
expect(r.has("search")).toBe(true);
});
it("responses input_image -> vision", () => {
const r = detectRequiredCapabilities({ input: [{ role: "user", content: [
{ type: "input_image", image_url: "x" },
] }] });
expect(r.has("vision")).toBe(true);
});
});
describe("reorderByCapabilities", () => {
it("no required -> unchanged", () => {
const models = ["a/x", "b/y"];
expect(reorderByCapabilities(models, new Set())).toBe(models);
});
it("floats vision-capable model to front, keeps fallback", () => {
// deepseek-chat = no vision; claude-sonnet = vision
const models = ["deepseek/deepseek-chat", "anthropic/claude-sonnet-4.6"];
const out = reorderByCapabilities(models, new Set(["vision"]));
expect(out[0]).toBe("anthropic/claude-sonnet-4.6");
expect(out).toContain("deepseek/deepseek-chat"); // not dropped
expect(out).toHaveLength(2);
});
it("keeps order when no model matches", () => {
const models = ["deepseek/deepseek-chat", "deepseek/deepseek-reasoner"];
const out = reorderByCapabilities(models, new Set(["vision"]));
expect(out).toBe(models);
});
it("single model -> unchanged", () => {
const models = ["a/x"];
expect(reorderByCapabilities(models, new Set(["vision"]))).toBe(models);
});
});

View File

@@ -0,0 +1,216 @@
import { describe, it, expect, vi } from "vitest";
import { handleFusionChat } from "../../open-sse/services/combo.js";
const log = { info: () => {}, warn: () => {}, debug: () => {} };
// Minimal OpenAI-chat Response stub with the .ok + .clone().json() surface the engine uses.
function okResponse(content, { delayMs = 0 } = {}) {
const json = { choices: [{ message: { role: "assistant", content } }] };
const make = () => ({ ok: true, status: 200, clone: make, json: async () => json });
const res = make();
return delayMs > 0 ? new Promise((r) => setTimeout(() => r(res), delayMs)) : res;
}
function errResponse(status = 500) {
const make = () => ({ ok: false, status, clone: make, json: async () => ({ error: { message: "boom" } }) });
return make();
}
describe("fusion combo", () => {
it("answers directly with a single-model panel (nothing to fuse)", async () => {
const handleSingleModel = vi.fn(async () => okResponse("solo"));
await handleFusionChat({
body: { messages: [{ role: "user", content: "hi" }] },
models: ["p/only"],
handleSingleModel,
log,
});
expect(handleSingleModel).toHaveBeenCalledTimes(1);
expect(handleSingleModel.mock.calls[0][1]).toBe("p/only");
});
it("fans out to the panel then routes a synthesis turn to the judge", async () => {
const seen = [];
const handleSingleModel = vi.fn(async (body, model, isPanel) => {
seen.push(model);
if (model === "p/judge") return okResponse("FINAL");
return okResponse(`ans-${model}`);
});
const res = await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }], stream: true, tools: [{ name: "x" }] },
models: ["p/a", "p/b", "p/c"],
handleSingleModel,
log,
judgeModel: "p/judge",
});
// 3 panel calls + 1 judge call.
expect(handleSingleModel).toHaveBeenCalledTimes(4);
expect(seen.slice(0, 3).sort()).toEqual(["p/a", "p/b", "p/c"]);
expect(seen[3]).toBe("p/judge");
// Panel calls are non-streaming with tools stripped.
for (const [body, model, isPanel] of handleSingleModel.mock.calls.filter(([, m]) => m !== "p/judge")) {
expect(body.stream).toBe(false);
expect(body.tools).toBeUndefined();
expect(isPanel).toBe(true);
}
// Judge call carries every panel answer + keeps the client's stream flag.
const [judgeBody, , isPanel] = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
const judgeText = judgeBody.messages.at(-1).content;
expect(judgeText).toContain("ans-p/a");
expect(judgeText).toContain("ans-p/b");
expect(judgeText).toContain("ans-p/c");
expect(judgeText).toContain("Source 1");
expect(judgeBody.stream).toBe(true);
expect(isPanel).toBeUndefined();
expect(res.ok).toBe(true);
});
it("defaults the judge to the first panel model when none is set", async () => {
const seen = [];
const handleSingleModel = vi.fn(async (_body, model) => { seen.push(model); return okResponse(`ans-${model}`); });
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/first", "p/second"],
handleSingleModel,
log,
});
// Last call is the judge; defaults to panel[0].
expect(seen.at(-1)).toBe("p/first");
});
it("proceeds on quorum without waiting for a straggler (grace window)", async () => {
const handleSingleModel = vi.fn(async (_body, model) => {
if (model === "p/slow") return okResponse("slow", { delayMs: 5000 });
if (model === "p/judge") return okResponse("FINAL");
return okResponse(`fast-${model}`);
});
const t0 = Date.now();
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/x", "p/y", "p/slow"],
handleSingleModel,
log,
judgeModel: "p/judge",
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 10000 },
});
const elapsed = Date.now() - t0;
// Two fast answers reach quorum; grace is 50ms, so we never wait ~5s for p/slow.
expect(elapsed).toBeLessThan(2000);
const judgeCall = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
const judgeText = judgeCall[0].messages.at(-1).content;
expect(judgeText).toContain("fast-p/x");
expect(judgeText).toContain("fast-p/y");
expect(judgeText).not.toContain("slow");
});
it("returns the lone survivor directly when only one panel model succeeds", async () => {
const handleSingleModel = vi.fn(async (_body, model) => {
if (model === "p/ok") return okResponse("lone");
return errResponse(500);
});
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/ok", "p/bad"],
handleSingleModel,
log,
judgeModel: "p/judge",
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 5000 },
});
// No judge call — single answer means there is nothing to fuse.
const judged = handleSingleModel.mock.calls.some(([, m]) => m === "p/judge");
expect(judged).toBe(false);
});
it("returns 503 when the whole panel fails", async () => {
const handleSingleModel = vi.fn(async () => errResponse(500));
const res = await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/a", "p/b"],
handleSingleModel,
log,
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 5000 },
});
expect(res.status).toBe(503);
});
it("flattens previous tool history and assistant tool_calls into prose for panel calls", async () => {
const handleSingleModel = vi.fn(async () => okResponse("ans"));
await handleFusionChat({
body: {
messages: [
{ role: "user", content: "find files" },
{ role: "assistant", content: "", tool_calls: [{ id: "c1", type: "function", function: { name: "find" } }] },
{ role: "tool", tool_call_id: "c1", content: "['a.js']" },
{ role: "user", content: "describe it" }
],
tools: [{ type: "function" }]
},
models: ["p/a", "p/b"],
handleSingleModel,
log,
judgeModel: "p/judge"
});
// Panel calls keep every turn but tool turns are flattened to assistant prose.
const panelCalls = handleSingleModel.mock.calls.filter(([,, isPanel]) => isPanel === true);
expect(panelCalls.length).toBe(2);
for (const [panelBody] of panelCalls) {
expect(panelBody.tools).toBeUndefined();
expect(panelBody.messages.length).toBe(4);
expect(panelBody.messages[0]).toEqual({ role: "user", content: "find files" });
expect(panelBody.messages[1].tool_calls).toBeUndefined();
expect(panelBody.messages[1].content).toContain("find");
expect(panelBody.messages[2].role).toBe("assistant");
expect(panelBody.messages[2].content).toContain("['a.js']");
expect(panelBody.messages[3]).toEqual({ role: "user", content: "describe it" });
}
// Judge call still receives the unmodified history + synthesis prompt.
const judgeCall = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
expect(judgeCall).toBeDefined();
const judgeBody = judgeCall[0];
expect(judgeBody.messages.length).toBe(5); // original 4 + judge prompt turn
expect(judgeBody.messages[1].tool_calls).toBeDefined();
expect(judgeBody.messages[2].role).toBe("tool");
});
it("flattens Anthropic-style tool_use and tool_result blocks in arrays", async () => {
const handleSingleModel = vi.fn(async () => okResponse("ans"));
await handleFusionChat({
body: {
messages: [
{ role: "user", content: "do it" },
{ role: "assistant", content: [{ type: "text", text: "ok" }, { type: "tool_use", id: "t1", name: "run" }] },
{ role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "done" }] }
],
tools: [{ name: "run", description: "d" }]
},
models: ["p/a", "p/b"],
handleSingleModel,
log,
judgeModel: "p/judge"
});
const panelCalls = handleSingleModel.mock.calls.filter(([,, isPanel]) => isPanel === true);
expect(panelCalls.length).toBe(2);
const panelBody = panelCalls[0][0];
expect(panelBody.tools).toBeUndefined();
expect(panelBody.messages.length).toBe(3);
// Flattened tool_use
expect(panelBody.messages[1].content).toBe("ok\n[Called tools: run]");
// Flattened tool_result
expect(panelBody.messages[2].content).toBe("[Tool result: done]");
});
});

View File

@@ -9,13 +9,13 @@
*/
import { describe, it, expect } from "vitest";
import { convertCommandCodeToOpenAI } from "../../open-sse/translator/response/commandcode-to-openai.js";
import { commandCodeToOpenAIResponse } from "../../open-sse/translator/response/commandcode-to-openai.js";
function feed(events) {
const state = {};
const all = [];
for (const e of events) {
const out = convertCommandCodeToOpenAI(JSON.stringify(e), state);
const out = commandCodeToOpenAIResponse(JSON.stringify(e), state);
if (out) for (const c of out) all.push(c);
}
return { state, chunks: all };

View File

@@ -61,6 +61,26 @@ describe("dashboard guard public LLM API access", () => {
expect(mocks.validateApiKey).not.toHaveBeenCalled();
});
it("rejects remote Host-spoof when real peer IP is non-loopback", async () => {
const response = await proxy(request("/v1/chat/completions", {
host: "localhost",
"x-9r-real-ip": "10.204.111.34",
}));
expect(response.status).toBe(401);
expect(response.body.error).toBe("API key required for remote API access");
});
it("allows loopback peer IP regardless of Host", async () => {
const response = await proxy(request("/v1/chat/completions", {
host: "localhost:20128",
"x-9r-real-ip": "127.0.0.1",
}));
expect(response).toBe(mocks.nextResponse);
expect(mocks.validateApiKey).not.toHaveBeenCalled();
});
it("rejects remote rewritten public LLM API without API key", async () => {
const response = await proxy(request("/api/v1/chat/completions", { host: "router.example.com" }));
@@ -89,6 +109,25 @@ describe("dashboard guard public LLM API access", () => {
expect(response.body.error).toBe("API key required for remote API access");
});
it("rejects remote codex rewrite without API key", async () => {
const response = await proxy(request("/codex/x", { host: "router.example.com" }));
expect(response.status).toBe(401);
expect(response.body.error).toBe("API key required for remote API access");
});
it("allows remote codex rewrite with valid API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);
const response = await proxy(request("/codex/x", {
host: "router.example.com",
authorization: "Bearer sk-valid",
}));
expect(response).toBe(mocks.nextResponse);
expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid");
});
it("allows remote public LLM API with valid bearer API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);

View File

@@ -0,0 +1,48 @@
// A5 (cases #7/#9/#10): lock hardcode->config no-op values.
import { describe, it, expect } from "vitest";
import {
OPENAI_COMPAT_BASE,
ANTHROPIC_COMPAT_BASE,
ANTHROPIC_API_VERSION,
} from "../../open-sse/providers/shared.js";
import { DEFAULT_MAX_TOKENS, DEFAULT_MIN_TOKENS } from "../../open-sse/config/runtimeConfig.js";
import mimoFree from "../../open-sse/providers/registry/mimo-free.js";
import opencode from "../../open-sse/providers/registry/opencode.js";
import antigravity from "../../open-sse/providers/registry/antigravity.js";
describe("compat base URLs / version", () => {
it("OPENAI_COMPAT_BASE", () => {
expect(OPENAI_COMPAT_BASE).toBe("https://api.openai.com/v1");
});
it("ANTHROPIC_COMPAT_BASE", () => {
expect(ANTHROPIC_COMPAT_BASE).toBe("https://api.anthropic.com/v1");
});
it("ANTHROPIC_API_VERSION", () => {
expect(ANTHROPIC_API_VERSION).toBe("2023-06-01");
});
});
describe("default token limits", () => {
it("max/min", () => {
expect(DEFAULT_MAX_TOKENS).toBe(64000);
expect(DEFAULT_MIN_TOKENS).toBe(32000);
});
});
describe("provider baseUrl const (full path, no trailing slash)", () => {
it("mimo-free full path", () => {
expect(mimoFree.transport.baseUrl).toBe("https://api.xiaomimimo.com/api/free-ai/openai/chat");
});
it("opencode no trailing slash", () => {
expect(opencode.transport.baseUrl).toBe("https://opencode.ai");
});
});
describe("antigravity retry (intentional change: 429=6, 503=3)", () => {
it("429 attempts = 6", () => {
expect(antigravity.transport.retry["429"].attempts).toBe(6);
});
it("503 attempts = 3", () => {
expect(antigravity.transport.retry["503"].attempts).toBe(3);
});
});

View File

@@ -0,0 +1,57 @@
import { describe, it, expect } from "vitest";
import { convertOpenAIContentToParts } from "../../open-sse/translator/formats/gemini.js";
import { openaiToClaudeRequest } from "../../open-sse/translator/request/openai-to-claude.js";
import { VALID_OPENAI_CONTENT_TYPES, OPENAI_BLOCK, CLAUDE_BLOCK } from "../../open-sse/translator/schema/index.js";
const PDF_DATA = "data:application/pdf;base64,JVBERi0xLjE=";
const PNG_DATA = "data:image/png;base64,iVBORw0KGgo=";
describe("file/document block support", () => {
it("schema: file is a valid openai content type", () => {
expect(VALID_OPENAI_CONTENT_TYPES).toContain(OPENAI_BLOCK.FILE);
expect(OPENAI_BLOCK.FILE).toBe("file");
expect(CLAUDE_BLOCK.DOCUMENT).toBe("document");
});
it("gemini: openai file block -> inlineData", () => {
const parts = convertOpenAIContentToParts([
{ type: "text", text: "read this" },
{ type: "file", file: { filename: "d.pdf", file_data: PDF_DATA } },
]);
const inline = parts.find((p) => p.inlineData);
expect(inline).toBeTruthy();
expect(inline.inlineData.mime_type).toBe("application/pdf");
expect(inline.inlineData.data).toBe("JVBERi0xLjE=");
});
it("gemini: ignores non-data-uri file", () => {
const parts = convertOpenAIContentToParts([
{ type: "file", file: { filename: "d.pdf", file_data: "https://x/d.pdf" } },
]);
expect(parts.some((p) => p.inlineData)).toBe(false);
});
it("claude: openai file (pdf) -> document block", () => {
const out = openaiToClaudeRequest("claude-x", {
messages: [{ role: "user", content: [
{ type: "text", text: "read" },
{ type: "file", file: { filename: "d.pdf", file_data: PDF_DATA } },
] }],
}, false);
const blocks = out.messages[0].content;
const doc = blocks.find((b) => b.type === "document");
expect(doc).toBeTruthy();
expect(doc.source.media_type).toBe("application/pdf");
});
it("claude: non-pdf file is dropped (not a document)", () => {
const out = openaiToClaudeRequest("claude-x", {
messages: [{ role: "user", content: [
{ type: "text", text: "read" },
{ type: "file", file: { filename: "i.png", file_data: PNG_DATA } },
] }],
}, false);
const blocks = out.messages[0].content;
expect(blocks.some((b) => b.type === "document")).toBe(false);
});
});

View File

@@ -0,0 +1,83 @@
// A1: locks toOpenAIFinish/fromOpenAIFinish behavior changes vs open-sse.old.
import { describe, it, expect } from "vitest";
import { toOpenAIFinish, fromOpenAIFinish } from "../../open-sse/translator/concerns/finishReason.js";
import { OPENAI_FINISH, CLAUDE_STOP, GEMINI_FINISH } from "../../open-sse/translator/schema/finishReasons.js";
describe("toOpenAIFinish - gemini", () => {
it.each([
["SAFETY", "content_filter"],
["RECITATION", "content_filter"],
["BLOCKLIST", "content_filter"],
["PROHIBITED_CONTENT", "content_filter"],
["OTHER", "stop"],
["UNKNOWN_XYZ", "stop"],
["STOP", "stop"],
["MAX_TOKENS", "length"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "gemini")).toBe(expected);
});
});
describe("toOpenAIFinish - ollama", () => {
it.each([
["length", "length"],
["max_tokens", "length"],
["tool_calls", "tool_calls"],
["unknown_xyz", "stop"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "ollama")).toBe(expected);
});
});
describe("toOpenAIFinish - kiro", () => {
it("tool_use -> tool_calls", () => {
expect(toOpenAIFinish("tool_use", "kiro")).toBe("tool_calls");
});
});
describe("toOpenAIFinish - claude", () => {
it.each([
["end_turn", "stop"],
["max_tokens", "length"],
["tool_use", "tool_calls"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "claude")).toBe(expected);
});
});
describe("toOpenAIFinish - commandcode", () => {
it("tool-calls -> tool_calls", () => {
expect(toOpenAIFinish("tool-calls", "commandcode")).toBe("tool_calls");
});
it("unknown passthrough", () => {
expect(toOpenAIFinish("xyz", "commandcode")).toBe("xyz");
});
});
describe("fromOpenAIFinish round-trip - claude", () => {
it("tool_calls -> tool_use", () => {
expect(fromOpenAIFinish("tool_calls", "claude")).toBe("tool_use");
});
it("length -> max_tokens", () => {
expect(fromOpenAIFinish("length", "claude")).toBe("max_tokens");
});
});
describe("enum literals (catch drift)", () => {
it("OPENAI_FINISH literals", () => {
expect(OPENAI_FINISH.STOP).toBe("stop");
expect(OPENAI_FINISH.LENGTH).toBe("length");
expect(OPENAI_FINISH.TOOL_CALLS).toBe("tool_calls");
expect(OPENAI_FINISH.CONTENT_FILTER).toBe("content_filter");
});
it("CLAUDE_STOP literals", () => {
expect(CLAUDE_STOP.END_TURN).toBe("end_turn");
expect(CLAUDE_STOP.MAX_TOKENS).toBe("max_tokens");
expect(CLAUDE_STOP.TOOL_USE).toBe("tool_use");
});
it("GEMINI_FINISH literals", () => {
expect(GEMINI_FINISH.STOP).toBe("STOP");
expect(GEMINI_FINISH.MAX_TOKENS).toBe("MAX_TOKENS");
expect(GEMINI_FINISH.SAFETY).toBe("SAFETY");
});
});

View File

@@ -0,0 +1,17 @@
// Guards forceStream moved from chatCore hardcode → PROVIDERS schema (#5).
import { describe, it, expect } from "vitest";
const FORCED = ["openai", "codex", "commandcode"];
describe("forceStream provider config", () => {
it("only openai/codex/commandcode force streaming", async () => {
const { PROVIDERS } = await import("../../open-sse/config/providers.js");
for (const id of FORCED) {
expect(PROVIDERS[id]?.forceStream, `${id} forced`).toBe(true);
}
// a sample of others must NOT force
for (const id of ["deepseek", "claude", "gemini", "openrouter"]) {
expect(PROVIDERS[id]?.forceStream, `${id} not forced`).not.toBe(true);
}
});
});

View File

@@ -0,0 +1,36 @@
import { describe, it, expect, vi, afterEach } from "vitest";
const mocks = vi.hoisted(() => ({
execSync: vi.fn(() => { throw new Error("not found"); }),
}));
vi.mock("child_process", () => ({
execSync: mocks.execSync,
}));
import { getHeadroomStatus, isLoopbackHeadroomUrl } from "../../src/lib/headroom/detect.js";
afterEach(() => {
vi.clearAllMocks();
});
describe("headroom detect", () => {
it("treats a reachable external proxy as running without local CLI", async () => {
global.fetch = vi.fn(async () => new Response("ok", { status: 200 }));
const status = await getHeadroomStatus("http://headroom:8787");
expect(status.installed).toBe(false);
expect(status.running).toBe(true);
expect(status.localUrl).toBe(false);
expect(status.canStart).toBe(false);
expect(global.fetch).toHaveBeenCalledWith("http://headroom:8787/health", expect.any(Object));
});
it("recognizes loopback URLs for managed local mode", () => {
expect(isLoopbackHeadroomUrl("http://localhost:8787")).toBe(true);
expect(isLoopbackHeadroomUrl("http://127.0.0.1:8787")).toBe(true);
expect(isLoopbackHeadroomUrl("http://headroom:8787")).toBe(false);
expect(isLoopbackHeadroomUrl("not-a-url")).toBe(false);
});
});

View File

@@ -0,0 +1,73 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { compressWithHeadroom, formatHeadroomLog } from "../../open-sse/rtk/headroom.js";
afterEach(() => {
vi.restoreAllMocks();
});
describe("compressWithHeadroom", () => {
it("no-ops when disabled", async () => {
global.fetch = vi.fn();
const body = { messages: [{ role: "user", content: "hello" }] };
const stats = await compressWithHeadroom(body, { enabled: false, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(global.fetch).not.toHaveBeenCalled();
expect(body.messages[0].content).toBe("hello");
});
it("compresses messages in-place", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({
messages: [{ role: "user", content: "short" }],
tokens_before: 100,
tokens_after: 20,
tokens_saved: 80,
}), { status: 200 }));
const body = { messages: [{ role: "user", content: "long" }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://headroom:8787/", model: "gpt-4o" });
expect(body.messages[0].content).toBe("short");
expect(stats.tokens_saved).toBe(80);
expect(global.fetch).toHaveBeenCalledWith("http://headroom:8787/v1/compress", expect.objectContaining({ method: "POST" }));
});
it("compresses responses input in-place", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({
messages: [{ role: "user", content: "short" }],
}), { status: 200 }));
const body = { input: [{ role: "user", content: "long" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(body.input[0].content).toBe("short");
});
it("fails open on bad response", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({ error: "bad" }), { status: 500 }));
const body = { messages: [{ role: "user", content: "long" }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(body.messages[0].content).toBe("long");
});
it("skips unknown shapes", async () => {
global.fetch = vi.fn();
const body = { contents: [{ parts: [{ text: "long" }] }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(global.fetch).not.toHaveBeenCalled();
});
});
describe("formatHeadroomLog", () => {
it("formats savings", () => {
expect(formatHeadroomLog({ tokens_before: 100, tokens_after: 25, tokens_saved: 75 }))
.toBe("saved 75 tokens / 100 (75.0%) after=25");
});
});

View File

@@ -0,0 +1,77 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Mock DNS lookup so we control which host resolves to what IP.
const lookupMock = vi.fn();
vi.mock("node:dns/promises", () => ({ lookup: (...a) => lookupMock(...a) }));
import { fetchImageAsBase64 } from "../../open-sse/translator/concerns/image.js";
const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
function mockFetchOnce(bytes, ok = true) {
const body = {
getReader() {
let sent = false;
return {
read: async () => sent ? { done: true } : (sent = true, { done: false, value: new Uint8Array(bytes) }),
cancel: async () => {},
};
},
};
globalThis.fetch = vi.fn(async () => ({ ok, body }));
}
beforeEach(() => {
lookupMock.mockReset();
lookupMock.mockResolvedValue({ address: "93.184.216.34" }); // public by default
});
afterEach(() => { vi.restoreAllMocks(); });
describe("fetchImageAsBase64 hardening", () => {
it("rejects non-http url", async () => {
expect(await fetchImageAsBase64("ftp://x/y.png")).toBeNull();
expect(await fetchImageAsBase64("data:image/png;base64,xx")).toBeNull();
});
it("SSRF: rejects private IP (10.x)", async () => {
lookupMock.mockResolvedValue({ address: "10.0.0.5" });
expect(await fetchImageAsBase64("http://internal.example/x.png")).toBeNull();
});
it("SSRF: rejects cloud metadata 169.254.169.254", async () => {
lookupMock.mockResolvedValue({ address: "169.254.169.254" });
expect(await fetchImageAsBase64("http://metadata/x.png")).toBeNull();
});
it("SSRF: rejects blocked hostname localhost", async () => {
expect(await fetchImageAsBase64("http://localhost/x.png")).toBeNull();
});
it("SSRF: rejects IPv6 loopback", async () => {
lookupMock.mockResolvedValue({ address: "::1" });
expect(await fetchImageAsBase64("http://x/y.png")).toBeNull();
});
it("accepts valid PNG from public host", async () => {
mockFetchOnce(PNG);
const r = await fetchImageAsBase64("https://example.com/a.png");
expect(r).not.toBeNull();
expect(r.mimeType).toBe("image/png");
expect(r.url.startsWith("data:image/png;base64,")).toBe(true);
});
it("rejects disguised non-image payload (magic byte mismatch)", async () => {
mockFetchOnce(Buffer.from("<?php system($_GET[c]); ?>"));
expect(await fetchImageAsBase64("https://example.com/evil.png")).toBeNull();
});
it("rejects payload over size cap", async () => {
mockFetchOnce(Buffer.alloc(1024));
expect(await fetchImageAsBase64("https://example.com/big.png", { maxBytes: 100 })).toBeNull();
});
it("returns null when fetch not ok", async () => {
mockFetchOnce(PNG, false);
expect(await fetchImageAsBase64("https://example.com/404.png")).toBeNull();
});
});

View File

@@ -0,0 +1,63 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { KiroService } from "../../src/lib/oauth/services/kiro.js";
/**
* Regression tests for Kiro API-key auth.
*
* KiroService.validateApiKey resolves a profileArn with the key (via
* CodeWhisperer ListAvailableProfiles) and returns a credential shaped for
* persistence with authMethod="api_key". The response profile field name
* varies (`arn` vs `profileArn`) — both are accepted by listAvailableProfiles.
*
* Note: OAuth (Builder ID / IDC) profileArn resolution is handled upstream by
* fetchKiroProfileArn in providers.js and is covered there — not here.
*/
describe("kiro API-key auth (KiroService.validateApiKey)", () => {
beforeEach(() => vi.restoreAllMocks());
afterEach(() => vi.restoreAllMocks());
it("validates an API key and resolves a credential with profileArn", async () => {
const expectedArn = "arn:aws:codewhisperer:us-east-1:444:profile/KEY";
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ profiles: [{ arn: expectedArn }] }),
});
const svc = new KiroService();
const cred = await svc.validateApiKey(" my-secret-key ");
expect(cred).toEqual({
accessToken: "my-secret-key",
refreshToken: null,
profileArn: expectedArn,
region: "us-east-1",
authMethod: "api_key",
});
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe("https://codewhisperer.us-east-1.amazonaws.com");
expect(init.headers.Authorization).toBe("Bearer my-secret-key");
expect(init.headers["x-amz-target"]).toBe(
"AmazonCodeWhispererService.ListAvailableProfiles"
);
});
it("rejects an empty API key without a network call", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch");
const svc = new KiroService();
await expect(svc.validateApiKey(" ")).rejects.toThrow("API key is required");
expect(fetchMock).not.toHaveBeenCalled();
});
it("surfaces a validation error when the key is rejected", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: false,
status: 401,
text: async () => "Unauthorized",
});
const svc = new KiroService();
await expect(svc.validateApiKey("bad-key")).rejects.toThrow(
/API key validation failed/
);
});
});

View File

@@ -0,0 +1,60 @@
/**
* Live repro for issue #1933: MiMo Code Free returns HTTP 502 "MiMo bootstrap failed: 403".
* Root cause: upstream gates on Chrome-like User-Agent. Without UA → 403 "Illegal access".
* Hits real endpoints — no mocks. Free provider, safe to call.
*/
import { describe, it, expect } from "vitest";
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { __test__ } from "../../open-sse/executors/mimo-free.js";
const { BOOTSTRAP_URL, CHAT_URL, generateFingerprint, MIMO_SYSTEM_MARKER } = __test__;
const CHROME_UA =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
async function bootstrapWith(ua) {
const headers = { "Content-Type": "application/json" };
if (ua) headers["User-Agent"] = ua;
const r = await proxyAwareFetch(BOOTSTRAP_URL, {
method: "POST",
headers,
body: JSON.stringify({ client: generateFingerprint() }),
});
const data = await r.json();
return { status: r.status, jwt: data.jwt };
}
async function chatWith(jwt, ua) {
const headers = {
"Content-Type": "application/json",
"X-Mimo-Source": "mimocode-cli-free",
Authorization: `Bearer ${jwt}`,
Accept: "application/json",
};
if (ua) headers["User-Agent"] = ua;
const body = {
model: "mimo-auto",
messages: [
{ role: "system", content: MIMO_SYSTEM_MARKER },
{ role: "user", content: "hi" },
],
stream: false,
};
return proxyAwareFetch(CHAT_URL, { method: "POST", headers, body: JSON.stringify(body) });
}
describe("MiMo Free bootstrap (live)", () => {
it("bootstrap returns 200 with JWT", async () => {
const { status, jwt } = await bootstrapWith(CHROME_UA);
expect(status).toBe(200);
expect(jwt).toBeTruthy();
});
});
describe("MiMo Free anti-abuse gate (live)", () => {
it("chat WITH Chrome User-Agent → 200", async () => {
const { jwt } = await bootstrapWith(CHROME_UA);
const r = await chatWith(jwt, CHROME_UA);
expect(r.status).toBe(200);
});
});

View File

@@ -0,0 +1,108 @@
import { describe, it, expect } from "vitest";
import { stripUnsupportedModalities } from "../../open-sse/translator/concerns/modality.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const NO_VISION = { vision: false, audioInput: true, pdf: true };
const NO_AUDIO = { vision: true, audioInput: false, pdf: true };
const NO_PDF = { vision: true, audioInput: true, pdf: false };
const ALL = { vision: true, audioInput: true, pdf: true };
describe("stripUnsupportedModalities", () => {
it("fast-exits when model supports all modalities", () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "x" } }] }] };
expect(stripUnsupportedModalities(body, FORMATS.OPENAI, ALL)).toBe(false);
expect(body.messages[0].content).toHaveLength(1);
});
it("openai: strips image when vision:false, leaves placeholder", () => {
const body = { messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "image_url", image_url: { url: "data:image/png;base64,xx" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_VISION);
const types = body.messages[0].content.map((b) => b.type);
expect(types).toContain("text");
expect(types).not.toContain("image_url");
expect(body.messages[0].content.some((b) => b.type === "text" && /image omitted/.test(b.text))).toBe(true);
});
it("openai: strips input_audio when audioInput:false", () => {
const body = { messages: [{ role: "user", content: [
{ type: "input_audio", input_audio: { data: "x", format: "wav" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_AUDIO);
expect(body.messages[0].content.some((b) => b.type === "input_audio")).toBe(false);
expect(body.messages[0].content.some((b) => /audio omitted/.test(b.text || ""))).toBe(true);
});
it("openai: strips file when pdf:false", () => {
const body = { messages: [{ role: "user", content: [
{ type: "file", file: { filename: "d.pdf", file_data: "data:application/pdf;base64,x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_PDF);
expect(body.messages[0].content.some((b) => b.type === "file")).toBe(false);
});
it("openai: keeps image when vision:true", () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "x" } }] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_AUDIO);
expect(body.messages[0].content.some((b) => b.type === "image_url")).toBe(true);
});
it("claude: strips image + document by capability", () => {
const body = { messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "image", source: { type: "base64", media_type: "image/png", data: "x" } },
{ type: "document", source: { type: "base64", media_type: "application/pdf", data: "x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.CLAUDE, { vision: false, audioInput: true, pdf: false });
const types = body.messages[0].content.map((b) => b.type);
expect(types).not.toContain("image");
expect(types).not.toContain("document");
expect(types).toContain("text");
});
it("gemini: strips inlineData image by mime when vision:false", () => {
const body = { contents: [{ role: "user", parts: [
{ text: "hi" },
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.GEMINI, NO_VISION);
expect(body.contents[0].parts.some((p) => p.inlineData)).toBe(false);
expect(body.contents[0].parts.some((p) => /image omitted/.test(p.text || ""))).toBe(true);
});
it("gemini: keeps inlineData pdf when pdf:true, strips image when vision:false", () => {
const body = { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
{ inlineData: { mimeType: "application/pdf", data: "y" } },
] }] };
stripUnsupportedModalities(body, FORMATS.GEMINI, NO_VISION);
const mimes = body.contents[0].parts.filter((p) => p.inlineData).map((p) => p.inlineData.mimeType);
expect(mimes).toEqual(["application/pdf"]);
});
it("antigravity: strips inside request.contents", () => {
const body = { request: { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] } };
stripUnsupportedModalities(body, FORMATS.ANTIGRAVITY, NO_VISION);
expect(body.request.contents[0].parts.some((p) => p.inlineData)).toBe(false);
});
it("responses: strips input_image when vision:false", () => {
const body = { input: [{ role: "user", content: [
{ type: "input_text", text: "hi" },
{ type: "input_image", image_url: "data:image/png;base64,x" },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI_RESPONSES, NO_VISION);
expect(body.input[0].content.some((b) => b.type === "input_image")).toBe(false);
expect(body.input[0].content.some((b) => b.type === "input_text" && /image omitted/.test(b.text))).toBe(true);
});
it("handles missing/empty body safely", () => {
expect(stripUnsupportedModalities(null, FORMATS.OPENAI, NO_VISION)).toBe(false);
expect(stripUnsupportedModalities({}, FORMATS.OPENAI, null)).toBe(false);
expect(stripUnsupportedModalities({ messages: [] }, FORMATS.OPENAI, NO_VISION)).toBe(true);
});
});

View File

@@ -0,0 +1,28 @@
// Guards C2: regex name fallback (no catalog). Terse entries derive name; existing names untouched.
import { describe, it, expect } from "vitest";
import { deriveModelName } from "../../open-sse/providers/models/namePatterns.js";
import { normalizeModel } from "../../open-sse/providers/models/schema.js";
describe("model name regex fallback (C2)", () => {
it("derives display name from id per family", () => {
expect(deriveModelName("kimi-k2.5")).toBe("Kimi K2.5");
expect(deriveModelName("glm-4.6v")).toBe("GLM 4.6V (Vision)");
expect(deriveModelName("minimax-m2.7")).toBe("MiniMax M2.7");
expect(deriveModelName("gpt-5.4-mini")).toBe("GPT 5.4 Mini");
expect(deriveModelName("grok-4")).toBe("Grok 4");
});
it("falls back to id verbatim when no pattern matches", () => {
expect(deriveModelName("some-unknown-model")).toBe("some-unknown-model");
});
it("normalizeModel: explicit name always wins over regex", () => {
expect(normalizeModel({ id: "kimi-k2.5", name: "Custom" }).name).toBe("Custom");
});
it("normalizeModel: terse string id becomes object with derived name", () => {
const m = normalizeModel("glm-5");
expect(m.id).toBe("glm-5");
expect(m.name).toBe("GLM 5");
});
});

View File

@@ -0,0 +1,80 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
const originalDataDir = process.env.DATA_DIR;
async function setupDb() {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-model-routing-"));
process.env.DATA_DIR = tempDir;
vi.resetModules();
const { createProviderNode } = await import("@/models/index.js");
const { getModelInfo } = await import("@/sse/services/model.js");
return {
createProviderNode,
getModelInfo,
cleanup() {
fs.rmSync(tempDir, { recursive: true, force: true });
},
};
}
describe("model routing", () => {
let cleanup = () => {};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetModules();
vi.clearAllMocks();
cleanup();
cleanup = () => {};
if (originalDataDir === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = originalDataDir;
});
it("keeps built-in provider aliases ahead of compatible node prefixes", async () => {
const ctx = await setupDb();
cleanup = ctx.cleanup;
await ctx.createProviderNode({
id: "openai-compatible-chat-test",
type: "openai-compatible",
name: "Compatible CF Collision",
prefix: "cf",
apiType: "chat",
baseUrl: "https://compatible.test/v1",
});
await expect(ctx.getModelInfo("cf/@cf/black-forest-labs/flux-2-klein-9b"))
.resolves.toEqual({
provider: "cloudflare-ai",
model: "@cf/black-forest-labs/flux-2-klein-9b",
});
});
it("still routes non-reserved compatible node prefixes", async () => {
const ctx = await setupDb();
cleanup = ctx.cleanup;
await ctx.createProviderNode({
id: "openai-compatible-chat-test",
type: "openai-compatible",
name: "Compatible OCT",
prefix: "oct",
apiType: "chat",
baseUrl: "https://compatible.test/v1",
});
await expect(ctx.getModelInfo("oct/gpt-image-1"))
.resolves.toEqual({
provider: "openai-compatible-chat-test",
model: "gpt-image-1",
});
});
});

View File

@@ -0,0 +1,58 @@
// Locks multimodal quirks flagged in docs 11 §4: image_url.detail drop + input_audio per-format.
import { describe, it, expect } from "vitest";
import { openaiToClaudeRequest } from "../../open-sse/translator/request/openai-to-claude.js";
import { convertOpenAIContentToParts } from "../../open-sse/translator/formats/gemini.js";
function userImage(detail) {
return {
model: "claude-sonnet-4-6",
messages: [{
role: "user",
content: [
{ type: "text", text: "look" },
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAB", detail } },
],
}],
};
}
describe("openai→claude: image_url.detail is dropped (docs 11 §4)", () => {
it("converts image to base64 source WITHOUT a detail field", () => {
const out = openaiToClaudeRequest("claude-sonnet-4-6", userImage("high"), false);
const imgBlock = out.messages[0].content.find((b) => b.type === "image");
expect(imgBlock).toBeTruthy();
expect(imgBlock.source).toEqual({ type: "base64", media_type: "image/png", data: "AAAB" });
expect("detail" in imgBlock).toBe(false);
expect("detail" in imgBlock.source).toBe(false);
});
it("drops input_audio entirely (claude has no audio block)", () => {
const body = {
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "input_audio", input_audio: { data: "ZZZ", format: "wav" } },
] }],
};
const out = openaiToClaudeRequest("claude-sonnet-4-6", body, false);
const blocks = out.messages[0].content;
expect(blocks.some((b) => b.type === "audio" || b.type === "input_audio")).toBe(false);
});
});
describe("openai→gemini: input_audio is mapped to inlineData (docs 11 §4)", () => {
it("maps wav → audio/wav inlineData", () => {
const parts = convertOpenAIContentToParts([{ type: "input_audio", input_audio: { data: "ZZZ", format: "wav" } }]);
expect(parts).toEqual([{ inlineData: { mime_type: "audio/wav", data: "ZZZ" } }]);
});
it("maps mp3 → audio/mpeg inlineData", () => {
const parts = convertOpenAIContentToParts([{ type: "input_audio", input_audio: { data: "ZZZ", format: "mp3" } }]);
expect(parts[0].inlineData.mime_type).toBe("audio/mpeg");
});
it("drops image_url.detail (not carried into inlineData)", () => {
const parts = convertOpenAIContentToParts([{ type: "image_url", image_url: { url: "data:image/png;base64,AAAB", detail: "high" } }]);
expect(parts).toEqual([{ inlineData: { mime_type: "image/png", data: "AAAB" } }]);
});
});

View File

@@ -9,13 +9,13 @@
*/
import { describe, it, expect } from "vitest";
import { openaiToCommandCode } from "../../open-sse/translator/request/openai-to-commandcode.js";
import { openaiToCommandCodeRequest } from "../../open-sse/translator/request/openai-to-commandcode.js";
const MODEL = "moonshotai/Kimi-K2.6";
describe("openaiToCommandCode — basic envelope", () => {
describe("openaiToCommandCodeRequest — basic envelope", () => {
it("returns the expected top-level envelope shape", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
@@ -28,9 +28,9 @@ describe("openaiToCommandCode — basic envelope", () => {
});
});
describe("openaiToCommandCode — system handling", () => {
describe("openaiToCommandCodeRequest — system handling", () => {
it("hoists system messages to params.system (string), not messages[]", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "hi" },
@@ -44,7 +44,7 @@ describe("openaiToCommandCode — system handling", () => {
});
it("joins multiple system messages with blank line", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "system", content: "A" },
{ role: "system", content: "B" },
@@ -56,16 +56,16 @@ describe("openaiToCommandCode — system handling", () => {
});
it("omits params.system when no system messages", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
expect(out.params.system).toBeUndefined();
});
});
describe("openaiToCommandCode — content shape", () => {
describe("openaiToCommandCodeRequest — content shape", () => {
it("MUST always emit content as Array (never string) for user", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hello" }],
}, true);
@@ -75,7 +75,7 @@ describe("openaiToCommandCode — content shape", () => {
});
it("MUST always emit content as Array for assistant", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "a" },
{ role: "assistant", content: "b" },
@@ -87,9 +87,9 @@ describe("openaiToCommandCode — content shape", () => {
});
});
describe("openaiToCommandCode — tool role / tool-result (AI SDK)", () => {
describe("openaiToCommandCodeRequest — tool role / tool-result (AI SDK)", () => {
it("converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "run X" },
{
@@ -113,9 +113,9 @@ describe("openaiToCommandCode — tool role / tool-result (AI SDK)", () => {
});
});
describe("openaiToCommandCode — assistant tool_calls / tool-call", () => {
describe("openaiToCommandCodeRequest — assistant tool_calls / tool-call", () => {
it("converts assistant.tool_calls[] into content blocks of type tool-call", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "go" },
{
@@ -138,9 +138,9 @@ describe("openaiToCommandCode — assistant tool_calls / tool-call", () => {
});
});
describe("openaiToCommandCode — tools schema conversion", () => {
describe("openaiToCommandCodeRequest — tools schema conversion", () => {
it("converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
tools: [
{
@@ -163,7 +163,7 @@ describe("openaiToCommandCode — tools schema conversion", () => {
});
it("preserves description on converted tool", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
tools: [
{ type: "function", function: { name: "ping", description: "Ping the server", parameters: { type: "object" } } },
@@ -173,7 +173,7 @@ describe("openaiToCommandCode — tools schema conversion", () => {
});
it("does not include tools field when input has none", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
expect(out.params.tools).toBeUndefined();

View File

@@ -2,21 +2,24 @@
* Unit tests for open-sse/translator/request/openai-to-kiro.js
*
* Tests cover:
* - buildKiroPayload() - basic message conversion
* - openaiToKiroRequest() - basic message conversion
* - Image forwarding fix: images in currentMessage must be included in payload
*/
import { describe, it, expect } from "vitest";
import { buildKiroPayload } from "../../open-sse/translator/request/openai-to-kiro.js";
import { openaiToKiroRequest } from "../../open-sse/translator/request/openai-to-kiro.js";
describe("buildKiroPayload", () => {
const contentOf = (result) =>
result.conversationState.currentMessage.userInputMessage.content;
describe("openaiToKiroRequest", () => {
describe("basic message conversion", () => {
it("should convert a simple text message", () => {
const body = {
messages: [{ role: "user", content: "Hello" }]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.content).toContain("Hello");
@@ -29,7 +32,7 @@ describe("buildKiroPayload", () => {
messages: [{ role: "user", content: "No images here" }]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeUndefined();
@@ -51,7 +54,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeDefined();
@@ -75,7 +78,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toHaveLength(2);
@@ -95,7 +98,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeUndefined();
@@ -115,7 +118,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.content).toContain("What is in this image?");
@@ -135,7 +138,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
// HTTP URLs are not supported by Kiro — converted to text placeholder
@@ -166,7 +169,7 @@ describe("buildKiroPayload", () => {
// note: no `tools`
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
// No structured tool content anywhere
@@ -201,7 +204,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
const allJson = JSON.stringify(cs);
@@ -233,7 +236,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
// Structured tool spec carried on currentMessage
@@ -270,7 +273,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
const allJson = JSON.stringify(cs);
@@ -280,4 +283,83 @@ describe("buildKiroPayload", () => {
expect(allJson).toContain("[Tool result: important orphaned output]");
});
});
describe("thinking budget", () => {
it("maps reasoning_effort low to max_thinking_length 1024", () => {
const body = {
reasoning_effort: "low",
messages: [{ role: "user", content: "Think lightly" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>1024</max_thinking_length>");
});
it("maps reasoning_effort high to max_thinking_length 24576", () => {
const body = {
reasoning_effort: "high",
messages: [{ role: "user", content: "Think deeply" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>24576</max_thinking_length>");
});
it("clamps reasoning_effort max to Kiro max_thinking_length 32000", () => {
const body = {
reasoning_effort: "max",
messages: [{ role: "user", content: "Think as much as possible" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>32000</max_thinking_length>");
});
it("clamps OpenAI Responses reasoning.effort xhigh to max_thinking_length 32000", () => {
const body = {
reasoning: { effort: "xhigh" },
messages: [{ role: "user", content: "Think extra deeply" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>32000</max_thinking_length>");
});
it("uses Claude thinking.budget_tokens as max_thinking_length", () => {
const body = {
thinking: { type: "enabled", budget_tokens: 4096 },
messages: [{ role: "user", content: "Use a fixed budget" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>4096</max_thinking_length>");
});
it("uses the default budget for synthetic -thinking models with no explicit config", () => {
const body = {
messages: [{ role: "user", content: "Think by model suffix" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6-thinking", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>16000</max_thinking_length>");
});
it("does not inject thinking prefix for reasoning_effort none", () => {
const body = {
reasoning_effort: "none",
messages: [{ role: "user", content: "Do not think" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).not.toContain("<thinking_mode>enabled</thinking_mode>");
expect(contentOf(result)).not.toContain("<max_thinking_length>");
});
});
});

View File

@@ -0,0 +1,30 @@
// A4 (case #10): malformed tool_calls args must not throw -> safeParseJSON returns {}.
import { describe, it, expect } from "vitest";
import { openaiToOllamaRequest } from "../../open-sse/translator/request/openai-to-ollama.js";
function reqWith(args) {
return {
messages: [
{
role: "assistant",
content: "",
tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: args } }],
},
],
};
}
describe("openaiToOllamaRequest - tool_calls arguments parsing", () => {
it("malformed JSON args -> {} (no throw)", () => {
let out;
expect(() => {
out = openaiToOllamaRequest("m", reqWith("{invalid json"), true);
}).not.toThrow();
expect(out.messages[0].tool_calls[0].function.arguments).toEqual({});
});
it("valid JSON args -> parsed object", () => {
const out = openaiToOllamaRequest("m", reqWith('{"a":1}'), true);
expect(out.messages[0].tool_calls[0].function.arguments).toEqual({ a: 1 });
});
});

View File

@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { PROVIDER_MODELS, getModelTargetFormat } from "../../open-sse/config/providerModels.js";
import { OpenCodeGoExecutor } from "../../open-sse/executors/opencode-go.js";
const CHAT_MODELS = [
"glm-5.2",
"glm-5.1",
// OpenCode Go docs' endpoint table currently says kimi-k2.7, but its
// config example and the live API use kimi-k2.7-code.
"kimi-k2.7-code",
"kimi-k2.6",
"deepseek-v4-pro",
"deepseek-v4-flash",
"mimo-v2.5",
"mimo-v2.5-pro",
];
const MESSAGES_MODELS = [
"minimax-m3",
"minimax-m2.7",
"minimax-m2.5",
"qwen3.7-max",
"qwen3.7-plus",
"qwen3.6-plus",
];
describe("OpenCode Go official model catalog", () => {
it("matches the documented OpenCode Go model IDs", () => {
const ids = (PROVIDER_MODELS["opencode-go"] || []).map((model) => model.id);
expect(ids).toEqual([...CHAT_MODELS, ...MESSAGES_MODELS]);
});
it("marks documented Qwen and MiniMax models as Anthropic messages format", () => {
for (const model of MESSAGES_MODELS) {
expect(getModelTargetFormat("opencode-go", model)).toBe("claude");
}
});
it("keeps GLM, Kimi, DeepSeek, and MiMo on OpenAI-compatible chat format", () => {
for (const model of CHAT_MODELS) {
expect(getModelTargetFormat("opencode-go", model)).toBeNull();
}
});
});
describe("OpenCode Go endpoint routing", () => {
it("routes Qwen and MiniMax models to the messages endpoint with x-api-key auth", () => {
const executor = new OpenCodeGoExecutor();
for (const model of MESSAGES_MODELS) {
expect(executor.buildUrl(model)).toBe("https://opencode.ai/zen/go/v1/messages");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, false);
expect(headers["x-api-key"]).toBe("sk-test");
expect(headers["anthropic-version"]).toBeDefined();
expect(headers.Authorization).toBeUndefined();
}
});
it("routes GLM, Kimi, DeepSeek, and MiMo models to chat/completions with bearer auth", () => {
const executor = new OpenCodeGoExecutor();
for (const model of CHAT_MODELS) {
expect(executor.buildUrl(model)).toBe("https://opencode.ai/zen/go/v1/chat/completions");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, false);
expect(headers.Authorization).toBe("Bearer sk-test");
expect(headers["x-api-key"]).toBeUndefined();
expect(headers["anthropic-version"]).toBeUndefined();
}
});
});

View File

@@ -0,0 +1,58 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../../open-sse/translator/concerns/image.js", async (orig) => {
const actual = await orig();
return {
...actual,
fetchImageAsBase64: vi.fn(async () => ({ url: "data:image/png;base64,QUJD", mimeType: "image/png" })),
};
});
import { prefetchRemoteImages } from "../../open-sse/translator/concerns/prefetch.js";
import { fetchImageAsBase64 } from "../../open-sse/translator/concerns/image.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
beforeEach(() => { fetchImageAsBase64.mockClear(); });
afterEach(() => { vi.restoreAllMocks(); });
describe("prefetchRemoteImages", () => {
it("no-op for targets that accept remote URLs (openai)", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "https://x/a.png" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OPENAI);
expect(n).toBe(0);
expect(body.messages[0].content[0].image_url.url).toBe("https://x/a.png");
});
it("openai source -> ollama target: converts remote URL to base64", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "https://x/a.png" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OLLAMA);
expect(n).toBe(1);
expect(body.messages[0].content[0].image_url.url.startsWith("data:image/png;base64,")).toBe(true);
});
it("skips data URI (already inline)", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "data:image/png;base64,xx" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OLLAMA);
expect(n).toBe(0);
expect(fetchImageAsBase64).not.toHaveBeenCalled();
});
it("gemini source -> gemini target: fileData URL -> inlineData base64", async () => {
const body = { contents: [{ role: "user", parts: [
{ fileData: { mimeType: "image/png", fileUri: "https://x/a.png" } },
] }] };
const n = await prefetchRemoteImages(body, FORMATS.GEMINI, FORMATS.GEMINI);
expect(n).toBe(1);
expect(body.contents[0].parts[0].inlineData).toBeTruthy();
expect(body.contents[0].parts[0].fileData).toBeUndefined();
});
it("claude source -> kiro target: source.url -> base64", async () => {
const body = { messages: [{ role: "user", content: [
{ type: "image", source: { type: "url", url: "https://x/a.png" } },
] }] };
const n = await prefetchRemoteImages(body, FORMATS.CLAUDE, FORMATS.KIRO);
expect(n).toBe(1);
expect(body.messages[0].content[0].source.type).toBe("base64");
});
});

View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from "vitest";
import { getProviderCustomModelRows } from "@/shared/utils/providerCustomModels.js";
describe("provider custom model rows", () => {
it("keeps identical model IDs separate per provider", () => {
const customModels = [
{ providerAlias: "ollama", id: "minimax-m2.5", type: "llm", name: "MiniMax M2.5" },
{ providerAlias: "opencode-go", id: "minimax-m2.5", type: "llm", name: "MiniMax M2.5" },
];
expect(getProviderCustomModelRows({ customModels, providerAlias: "ollama" })).toEqual([
{
id: "minimax-m2.5",
name: "MiniMax M2.5",
fullModel: "ollama/minimax-m2.5",
source: "custom",
type: "llm",
},
]);
expect(getProviderCustomModelRows({ customModels, providerAlias: "opencode-go" })).toEqual([
{
id: "minimax-m2.5",
name: "MiniMax M2.5",
fullModel: "opencode-go/minimax-m2.5",
source: "custom",
type: "llm",
},
]);
});
it("keeps legacy alias-backed models visible without duplicating custom models", () => {
const rows = getProviderCustomModelRows({
customModels: [
{ providerAlias: "ollama", id: "custom-a", type: "llm", name: "Custom A" },
],
modelAliases: {
"custom-a": "ollama/custom-a",
"legacy-b": "ollama/legacy-b",
"other-provider": "opencode-go/legacy-b",
},
providerAlias: "ollama",
});
expect(rows).toEqual([
{
id: "custom-a",
name: "Custom A",
fullModel: "ollama/custom-a",
source: "custom",
type: "llm",
},
{
id: "legacy-b",
alias: "legacy-b",
fullModel: "ollama/legacy-b",
source: "legacyAlias",
type: "llm",
},
]);
});
it("filters built-in models and typed custom models", () => {
const rows = getProviderCustomModelRows({
customModels: [
{ providerAlias: "ollama", id: "llama3", type: "llm", name: "Llama 3" },
{ providerAlias: "ollama", id: "custom-image", type: "image", name: "Custom Image" },
{ providerAlias: "ollama", id: "custom-llm", type: "llm", name: "Custom LLM" },
],
providerAlias: "ollama",
builtInModels: [{ id: "llama3" }],
type: "llm",
});
expect(rows).toEqual([
{
id: "custom-llm",
name: "Custom LLM",
fullModel: "ollama/custom-llm",
source: "custom",
type: "llm",
},
]);
});
});

View File

@@ -0,0 +1,34 @@
// Guards E1: display fields live in providersDisplay.js, merged back into AI_PROVIDERS (shape unchanged).
import { describe, it, expect } from "vitest";
const DISPLAY_FIELDS = ["name", "icon", "color"];
describe("provider display split (E1)", () => {
it("AI_PROVIDERS entries still carry merged display + transport", async () => {
const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.js");
const kiro = AI_PROVIDERS.kiro;
// display merged
expect(kiro.name).toBe("Kiro AI");
expect(kiro.icon).toBe("psychology_alt");
// transport kept
expect(kiro.id).toBe("kiro");
expect(kiro.alias).toBe("kr");
// transport-heavy provider keeps its config
expect(AI_PROVIDERS.gemini.serviceKinds).toContain("tts");
expect(AI_PROVIDERS.gemini.ttsConfig).toBeTruthy();
});
it("display fields source from providersDisplay.js", async () => {
const { PROVIDER_DISPLAY } = await import("../../src/shared/constants/providersDisplay.js");
const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.js");
for (const f of DISPLAY_FIELDS) {
expect(PROVIDER_DISPLAY.kiro[f]).toBe(AI_PROVIDERS.kiro[f]);
}
});
it("helpers still work after split", async () => {
const m = await import("../../src/shared/constants/providers.js");
expect(m.ALIAS_TO_ID.kr).toBe("kiro");
expect(m.getProvidersByKind("tts").length).toBeGreaterThan(0);
});
});

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { MODEL_PRICING } from "../../src/shared/constants/pricing.js";
import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
describe("MiniMax-M3 pricing", () => {
it("includes MiniMax-M3 in MODEL_PRICING", () => {

View File

@@ -95,9 +95,9 @@ describe("RTK filters", () => {
const input = makeFindOutput();
const out = find(input);
expect(out).toContain("55 files in 3 dirs:");
expect(out).toContain("./src/a/ (30):");
expect(out).toContain("./src/b/ (20):");
expect(out).toContain("./ (5):");
expect(out).toContain("./src/a/ (30)");
expect(out).toContain("./src/b/ (20)");
expect(out).toContain("./ (5)");
expect(out.length).toBeLessThan(input.length);
});

View File

@@ -0,0 +1,62 @@
// A2: locks resolveSessionId priority/stickiness (codex/kiro/antigravity centralization).
import { describe, it, expect, beforeEach } from "vitest";
import { resolveSessionId, deriveSessionId, clearSessionStore } from "../../open-sse/utils/sessionManager.js";
// Assistant text must reach ASSISTANT_MIN_LEN (80) to use assistant anchor; else first user message.
const longAssistant = "x".repeat(80);
const bodyWithAssistant = { messages: [{ role: "assistant", content: longAssistant }] };
const bodyWithUserOnly = { messages: [{ role: "user", content: "hello from first user message anchor" }] };
beforeEach(() => clearSessionStore());
describe("resolveSessionId", () => {
it("stickiness: same body+connectionId+scope -> same id", () => {
const opts = { body: bodyWithAssistant, connectionId: "conn1", scope: "codex" };
expect(resolveSessionId(opts)).toBe(resolveSessionId(opts));
});
it("different connectionId -> different id", () => {
const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "connA", scope: "codex" });
const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "connB", scope: "codex" });
expect(a).not.toBe(b);
});
it("different scope -> different id", () => {
const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "codex" });
const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "kiro" });
expect(a).not.toBe(b);
});
it("first user message anchor when assistant text below cap", () => {
const opts = { body: bodyWithUserOnly, connectionId: "conn1", scope: "codex" };
expect(resolveSessionId(opts)).toBe(resolveSessionId(opts));
});
it("assistant anchor wins once assistant text reaches cap", () => {
const shortAssistant = { messages: [{ role: "user", content: "same user" }, { role: "assistant", content: "y".repeat(80) }] };
const a = resolveSessionId({ body: shortAssistant, connectionId: "conn1", scope: "codex" });
const b = resolveSessionId({ body: shortAssistant, connectionId: "conn1", scope: "codex" });
expect(a).toBe(b);
});
it("fallback: empty body+no header+no workspaceId -> deriveSessionId(connectionId)", () => {
const got = resolveSessionId({ body: {}, connectionId: "connFallback" });
expect(got).toBe(deriveSessionId("connFallback"));
});
it("client override: x-session-id header wins, skips later steps", () => {
const got = resolveSessionId({
headers: { "x-session-id": "client-sess-123" },
body: bodyWithAssistant,
connectionId: "conn1",
workspaceId: "ws1",
scope: "codex",
});
expect(got).toBe("client-sess-123");
});
it("workspaceId path: empty body + workspaceId set -> normalized workspaceId", () => {
const got = resolveSessionId({ body: {}, connectionId: "conn1", workspaceId: "ws-abc" });
expect(got).toBe("ws-abc");
});
});

View File

@@ -0,0 +1,22 @@
// Guards the refactored REFRESH_HANDLERS dispatch: null-guards + the two different defaults.
import { describe, it, expect } from "vitest";
const load = () => import("../../open-sse/services/tokenRefresh.js");
describe("tokenRefresh dispatch", () => {
it("getAccessToken returns null for missing/invalid refreshToken", async () => {
const mod = await load();
expect(await mod.getAccessToken("claude", {}, null)).toBeNull();
expect(await mod.getAccessToken("claude", { refreshToken: 123 }, null)).toBeNull();
});
it("getAccessToken default: unsupported provider → null", async () => {
const mod = await load();
expect(await mod.getAccessToken("totally-unknown", { refreshToken: "x" }, null)).toBeNull();
});
it("refreshTokenByProvider returns null without refreshToken", async () => {
const mod = await load();
expect(await mod.refreshTokenByProvider("claude", {}, null)).toBeNull();
});
});

View File

@@ -0,0 +1,47 @@
// Locks edge cases flagged in docs 11 §1/§4 that were only covered indirectly.
import { describe, it, expect } from "vitest";
import { normalizeClaudePassthrough } from "../../open-sse/translator/formats/claude.js";
import { parseDataUri, encodeDataUri } from "../../open-sse/translator/concerns/image.js";
describe("normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)", () => {
it("downgrades adaptive thinking to enabled+budget for haiku models", () => {
const out = normalizeClaudePassthrough({ thinking: { type: "adaptive" } }, "claude-haiku-4-5");
expect(out.thinking).toEqual({ type: "enabled", budget_tokens: 10000 });
});
it("keeps adaptive thinking for sonnet/opus", () => {
const out = normalizeClaudePassthrough({ thinking: { type: "adaptive" } }, "claude-sonnet-4-6");
expect(out.thinking).toEqual({ type: "adaptive" });
});
it("hoists mid-conversation system messages into top-level system", () => {
const out = normalizeClaudePassthrough({
messages: [
{ role: "user", content: "hi" },
{ role: "system", content: "be brief" },
],
});
expect(out.system).toEqual([{ type: "text", text: "be brief" }]);
expect(out.messages.every((m) => m.role !== "system")).toBe(true);
});
});
describe("parseDataUri / encodeDataUri (docs 11 §4)", () => {
it("parses a base64 data uri", () => {
expect(parseDataUri("data:image/png;base64,AAAB")).toEqual({ mimeType: "image/png", base64: "AAAB" });
});
it("tolerates newlines inside base64 payload", () => {
expect(parseDataUri("data:image/jpeg;base64,AA\nBB")?.base64).toBe("AA\nBB");
});
it("returns null for http urls and non-strings", () => {
expect(parseDataUri("https://x/y.png")).toBeNull();
expect(parseDataUri(null)).toBeNull();
});
it("encode/parse roundtrip", () => {
const uri = encodeDataUri("image/webp", "ZZZ");
expect(parseDataUri(uri)).toEqual({ mimeType: "image/webp", base64: "ZZZ" });
});
});

View File

@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { translateRequest } from "../../open-sse/translator/index.js";
import { claudeToOpenAIRequest } from "../../open-sse/translator/request/claude-to-openai.js";
import { filterToOpenAIFormat } from "../../open-sse/translator/helpers/openaiHelper.js";
import { filterToOpenAIFormat } from "../../open-sse/translator/formats/openai.js";
import { parseSSELine } from "../../open-sse/utils/streamHelpers.js";
describe("request normalization", () => {

View File

@@ -0,0 +1,69 @@
// A3: locks toOpenAIUsage per-provider token math (claude/gemini/kiro/ollama/commandcode).
import { describe, it, expect } from "vitest";
import { toOpenAIUsage } from "../../open-sse/translator/concerns/usage.js";
describe("toOpenAIUsage", () => {
it("claude: folds cache read+create into prompt, exposes details", () => {
const u = toOpenAIUsage(
{ input_tokens: 100, output_tokens: 20, cache_read_input_tokens: 30, cache_creation_input_tokens: 10 },
"claude"
);
expect(u.prompt_tokens).toBe(140);
expect(u.completion_tokens).toBe(20);
expect(u.total_tokens).toBe(160);
expect(u.prompt_tokens_details.cached_tokens).toBe(30);
expect(u.prompt_tokens_details.cache_creation_tokens).toBe(10);
});
it("claude: no cache -> no prompt_tokens_details", () => {
const u = toOpenAIUsage({ input_tokens: 50, output_tokens: 5 }, "claude");
expect(u.prompt_tokens).toBe(50);
expect(u.prompt_tokens_details).toBeUndefined();
});
it("gemini: full fields, completion = candidates + thoughts", () => {
const u = toOpenAIUsage(
{ promptTokenCount: 100, candidatesTokenCount: 40, thoughtsTokenCount: 10, totalTokenCount: 150 },
"gemini"
);
expect(u.prompt_tokens).toBe(100);
expect(u.completion_tokens).toBe(50);
expect(u.total_tokens).toBe(150);
expect(u.completion_tokens_details.reasoning_tokens).toBe(10);
});
it("gemini fallback: candidates=0 -> derive from total - prompt - thoughts", () => {
const u = toOpenAIUsage(
{ promptTokenCount: 100, candidatesTokenCount: 0, thoughtsTokenCount: 10, totalTokenCount: 150 },
"gemini"
);
// candidates derived = 150 - 100 - 10 = 40 ; completion = 40 + 10
expect(u.completion_tokens).toBe(50);
});
it("kiro: input/output straight", () => {
const u = toOpenAIUsage({ inputTokens: 12, outputTokens: 3 }, "kiro");
expect(u.prompt_tokens).toBe(12);
expect(u.completion_tokens).toBe(3);
expect(u.total_tokens).toBe(15);
});
it("ollama: prompt_eval_count/eval_count", () => {
const u = toOpenAIUsage({ prompt_eval_count: 7, eval_count: 4 }, "ollama");
expect(u.prompt_tokens).toBe(7);
expect(u.completion_tokens).toBe(4);
expect(u.total_tokens).toBe(11);
});
it("commandcode: keeps totalTokens fallback", () => {
const u = toOpenAIUsage({ inputTokens: 8, outputTokens: 2, totalTokens: 99 }, "commandcode");
expect(u.prompt_tokens).toBe(8);
expect(u.completion_tokens).toBe(2);
expect(u.total_tokens).toBe(99);
});
it("unknown kind / null raw -> null", () => {
expect(toOpenAIUsage({}, "nope")).toBeNull();
expect(toOpenAIUsage(null, "claude")).toBeNull();
});
});

View File

@@ -0,0 +1,39 @@
// Guards the refactored USAGE_HANDLERS dispatch: unsupported → message, supported → routed.
import { describe, it, expect, vi, beforeEach } from "vitest";
// Stub network so handlers don't hit real APIs; each call resolves an empty 200.
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(async () => ({
ok: true,
status: 200,
json: async () => ({}),
text: async () => "{}",
})),
}));
const load = () => import("../../open-sse/services/usage.js");
const SUPPORTED = [
"github", "gemini-cli", "antigravity", "claude", "codex", "kiro",
"qoder", "qwen", "iflow", "ollama", "glm", "glm-cn",
"minimax", "minimax-cn", "vercel-ai-gateway",
];
describe("usage dispatch", () => {
beforeEach(() => vi.clearAllMocks());
it("unsupported provider → not-implemented message", async () => {
const { getUsageForProvider } = await load();
const res = await getUsageForProvider({ provider: "totally-unknown" });
expect(res).toEqual({ message: "Usage API not implemented for totally-unknown" });
});
it("every supported provider routes to its handler (no fallback message)", async () => {
const { getUsageForProvider } = await load();
for (const provider of SUPPORTED) {
const res = await getUsageForProvider({ provider, accessToken: "t", apiKey: "k" });
// Routed handler must return an object and never the unsupported fallback
expect(res, `${provider} routed`).toBeTypeOf("object");
expect(res?.message).not.toBe(`Usage API not implemented for ${provider}`);
}
});
});

View File

@@ -9,6 +9,10 @@ export default defineConfig({
environment: "node",
globals: true,
include: ["**/*.test.js"],
// Don't scan into git worktrees nested under .claude/ — they carry their
// own copies of the test files but lack an installed node_modules (open-sse,
// etc.), which makes provider imports fail during collection.
exclude: ["**/node_modules/**", "**/.claude/**", "**/dist/**"],
// Allow many it.concurrent cases (real provider smoke runs ~50 providers in parallel)
maxConcurrency: 60,
// Suppress noisy console output from handlers under test