- {/* Waiting + Manual Input combined (non-device-code) */}
- {(step === "waiting" || step === "input") && !isDeviceCode && (
+ {/* Trae/Windsurf: browser OAuth (proxy) + paste-token fallback */}
+ {PROXY_OAUTH_PROVIDERS.has(provider) && (step === "waiting" || step === "input" || step === "error") && (
+ <>
+
+
+ {authMode === "browser" && (
+ <>
+ {step === "waiting" && (
+
+ )}
+ >
+ )}
+
+ {authMode === "paste-token" && (
+
+ )}
+ >
+ )}
+
+ {/* Waiting + Manual Input combined (non-device-code, non-proxy) */}
+ {(step === "waiting" || step === "input") && !isDeviceCode && !PROXY_OAUTH_PROVIDERS.has(provider) && (
<>
{/* Option A: Auto via popup */}
diff --git a/src/shared/constants/cliTools.js b/src/shared/constants/cliTools.js
index 29488b24..ddbfc7a9 100644
--- a/src/shared/constants/cliTools.js
+++ b/src/shared/constants/cliTools.js
@@ -8,9 +8,12 @@ export const MITM_TOOLS = {
description: "Google Antigravity IDE with MITM",
configType: "mitm",
mitmDomain: "daily-cloudcode-pa.googleapis.com",
- modelAliases: ["gemini-3.5-flash-low", "gemini-3-flash-agent", "gemini-3.5-flash-extra-low", "gemini-3.1-pro-low", "gemini-pro-agent", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
+ modelAliases: ["gemini-3.6-flash-high", "gemini-3.6-flash-medium", "gemini-3.6-flash-low", "gemini-3.5-flash-low", "gemini-3-flash-agent", "gemini-3.5-flash-extra-low", "gemini-3.1-pro-low", "gemini-pro-agent", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
defaultModels: [
- { id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium) / Default", alias: "gemini-3.5-flash-low" },
+ { id: "gemini-3.6-flash-high", name: "Gemini 3.6 Flash (High)", alias: "gemini-3.6-flash-high" },
+ { id: "gemini-3.6-flash-medium", name: "Gemini 3.6 Flash (Medium)", alias: "gemini-3.6-flash-medium" },
+ { id: "gemini-3.6-flash-low", name: "Gemini 3.6 Flash (Low)", alias: "gemini-3.6-flash-low" },
+ { id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium) / Default", alias: "gemini-3.5-flash-low", mandatory: true },
{ id: "gemini-3-flash-agent", name: "Gemini 3.5 Flash (High)", alias: "gemini-3-flash-agent" },
{ id: "gemini-3.5-flash-extra-low", name: "Gemini 3.5 Flash (Low)", alias: "gemini-3.5-flash-extra-low" },
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro (Low)", alias: "gemini-3.1-pro-low" },
@@ -105,7 +108,7 @@ export const CLI_TOOLS = {
settingsFile: "~/.claude/settings.json",
defaultModels: [
{ id: "fable", name: "Claude Fable", alias: "fable", envKey: "ANTHROPIC_DEFAULT_FABLE_MODEL", defaultValue: "cc/claude-fable-5" },
- { id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-8" },
+ { id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-5" },
{ id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-5" },
{ id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
],
@@ -164,6 +167,182 @@ export const CLI_TOOLS = {
description: "GitHub Copilot in VS Code via custom models",
configType: "custom",
},
+ kilo: {
+ id: "kilo",
+ name: "Kilo Code",
+ image: "/providers/kilocode.png",
+ color: "#FF6B6B",
+ description: "Kilo Code AI Assistant",
+ configType: "custom",
+ },
+ roo: {
+ id: "roo",
+ name: "Roo",
+ image: "/providers/roo.png",
+ color: "#FF6B6B",
+ description: "Roo AI Assistant",
+ configType: "guide",
+ guideSteps: [
+ { step: 1, title: "Open Settings", desc: "Go to Roo Settings panel" },
+ { step: 2, title: "Select Provider", desc: "Choose API Provider → Ollama" },
+ { step: 3, title: "Base URL", value: "{{baseUrl}}", copyable: true },
+ { step: 4, title: "API Key", type: "apiKeySelector" },
+ { step: 5, title: "Select Model", type: "modelSelector" },
+ ],
+ },
+ continue: {
+ id: "continue",
+ name: "Continue",
+ image: "/providers/continue.png",
+ color: "#7C3AED",
+ description: "Continue AI Assistant",
+ configType: "guide",
+ guideSteps: [
+ { step: 1, title: "Open Config", desc: "Open Continue configuration file" },
+ { step: 2, title: "API Key", type: "apiKeySelector" },
+ { step: 3, title: "Select Model", type: "modelSelector" },
+ { step: 4, title: "Add Model Config", desc: "Add the following configuration to your models array:" },
+ ],
+ codeBlock: {
+ language: "json",
+ code: `{
+ "apiBase": "{{baseUrl}}",
+ "title": "{{model}}",
+ "model": "{{model}}",
+ "provider": "openai",
+ "apiKey": "{{apiKey}}"
+}`,
+ },
+ },
+ amp: {
+ id: "amp",
+ name: "Amp CLI",
+ image: "/providers/amp.png",
+ color: "#F97316",
+ description: "Sourcegraph Amp coding assistant CLI",
+ docsUrl: "/docs?section=cli-tools&tool=amp",
+ configType: "guide",
+ defaultCommand: "amp",
+ modelAliases: ["g25p", "g25f", "cs45", "g54"],
+ notes: [
+ { type: "info", text: "Use 9Router model aliases to keep Amp shorthand mappings stable across provider updates." },
+ { type: "warning", text: "Suggested shorthand examples: g25p → gemini/gemini-2.5-pro, g25f → gemini/gemini-2.5-flash, cs45 → cc/claude-sonnet-4-5-20250929." },
+ ],
+ guideSteps: [
+ { step: 1, title: "Install Amp", desc: "Install the Amp CLI using the package manager supported by your environment." },
+ { step: 2, title: "API Key", type: "apiKeySelector" },
+ { step: 3, title: "Base URL", value: "{{baseUrl}}", copyable: true },
+ { step: 4, title: "Select Model", type: "modelSelector" },
+ { step: 5, title: "Add Shorthands", desc: "Map Amp shorthand names such as g25p or cs45 to 9Router aliases in your local config." },
+ ],
+ codeBlock: {
+ language: "bash",
+ code: `export OPENAI_API_KEY="{{apiKey}}"
+export OPENAI_BASE_URL="{{baseUrl}}"
+amp --model "{{model}}"
+# Example shorthand aliases you can map locally:
+# g25p -> gemini/gemini-2.5-pro
+# cs45 -> cc/claude-sonnet-4-5-20250929`,
+ },
+ },
+ qwen: {
+ id: "qwen",
+ name: "Qwen Code",
+ image: "/providers/qwen.png",
+ color: "#10B981",
+ description: "Alibaba Qwen Code CLI — supports OpenAI, Anthropic & Gemini providers via 9Router",
+ docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/configuration/model-providers/",
+ configType: "guide",
+ defaultCommand: "qwen",
+ notes: [
+ { type: "info", text: "Qwen Code supports multiple provider types (openai, anthropic, gemini) via modelProviders in settings.json. 9Router works as an OpenAI-compatible endpoint." },
+ { type: "info", text: "Any model available in 9Router can be used — not just Qwen models. Select from Qwen, Claude, Gemini, GPT, and more." },
+ { type: "warning", text: "Config path: Linux/macOS ~/.qwen/settings.json • Windows %USERPROFILE%\\.qwen\\settings.json" },
+ { type: "error", text: "Qwen OAuth free tier was discontinued on 2026-04-15. Use 9Router with alicode/openrouter/anthropic/gemini providers instead." },
+ ],
+ modelAliases: ["coder-model", "qwen3-coder-plus", "qwen3-coder-flash", "vision-model", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gemini-3-flash", "gemini-3.1-pro-high"],
+ defaultModels: [
+ { id: "coder-model", name: "Coder Model (Qwen 3.6 Plus)", alias: "coder-model", envKey: "OPENAI_MODEL", defaultValue: "coder-model", isTopLevel: true },
+ { id: "qwen3-coder-plus", name: "Qwen 3 Coder Plus", alias: "qwen3-coder-plus", envKey: "OPENAI_MODEL", defaultValue: "qwen3-coder-plus" },
+ { id: "qwen3-coder-flash", name: "Qwen 3 Coder Flash", alias: "qwen3-coder-flash", envKey: "OPENAI_MODEL", defaultValue: "qwen3-coder-flash" },
+ { id: "vision-model", name: "Vision Model (Multimodal)", alias: "vision-model", envKey: "OPENAI_MODEL", defaultValue: "vision-model" },
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", alias: "claude-sonnet-4-6", envKey: "OPENAI_MODEL", defaultValue: "claude-sonnet-4-6" },
+ { id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking", alias: "claude-opus-4-6-thinking", envKey: "OPENAI_MODEL", defaultValue: "claude-opus-4-6-thinking" },
+ { id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High", alias: "gemini-3.1-pro-high", envKey: "OPENAI_MODEL", defaultValue: "gemini-3.1-pro-high" },
+ { id: "gemini-3-flash", name: "Gemini 3 Flash", alias: "gemini-3-flash", envKey: "OPENAI_MODEL", defaultValue: "gemini-3-flash" },
+ ],
+ guideSteps: [
+ { step: 1, title: "Install Qwen Code", desc: "npm install -g @qwen-code/qwen-code" },
+ { step: 2, title: "API Key", type: "apiKeySelector" },
+ { step: 3, title: "Base URL", value: "{{baseUrl}}", copyable: true },
+ { step: 4, title: "Select Model", type: "modelSelector" },
+ { step: 5, title: "Save Config", desc: "Copy the JSON below to your ~/.qwen/settings.json file." },
+ ],
+ codeBlock: {
+ language: "json",
+ code: `{
+ "security": {
+ "auth": {
+ "selectedType": "openai",
+ "apiKey": "{{apiKey}}",
+ "baseUrl": "{{baseUrl}}"
+ }
+ },
+ "model": {
+ "name": "{{model}}"
+ }
+}`,
+ },
+ },
+ "deepseek-tui": {
+ id: "deepseek-tui",
+ name: "DeepSeek TUI",
+ image: "/providers/deepseek-tui.png",
+ color: "#4D6BFE",
+ description: "DeepSeek Terminal Coding Agent (Rust TUI)",
+ docsUrl: "https://github.com/DeepSeek-TUI/DeepSeek-TUI",
+ configType: "custom",
+ defaultCommand: "deepseek",
+ modelAliases: ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-chat", "deepseek-reasoner"],
+ defaultModels: [
+ { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", alias: "deepseek-v4-pro" },
+ { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", alias: "deepseek-v4-flash" },
+ { id: "deepseek-chat", name: "DeepSeek V3 Chat", alias: "deepseek-chat" },
+ ],
+ notes: [
+ { type: "info", text: "DeepSeek TUI uses ~/.deepseek/config.toml for configuration. 9Router will update the provider to 'openai' mode with your base_url, api_key, and model." },
+ { type: "warning", text: "Config path: Linux/macOS ~/.deepseek/config.toml • Windows %USERPROFILE%\\.deepseek\\config.toml" },
+ ],
+ },
+ jcode: {
+ id: "jcode",
+ name: "jcode",
+ image: "/providers/jcode.png",
+ color: "#FF6B35",
+ description: "High-performance Rust-based coding agent harness",
+ configType: "custom",
+ docsUrl: "https://github.com/1jehuang/jcode",
+ notes: [
+ {
+ type: "info",
+ text: "jcode is a Rust-based coding agent with semantic memory, multi-agent swarms, and extreme performance (27.8 MB RAM, 14ms boot)."
+ },
+ {
+ type: "info",
+ text: "Configure 9router as an OpenAI-compatible provider to route all jcode requests through 9router's optimization layer."
+ },
+ {
+ type: "warning",
+ text: "Requires jcode installed. Install via: curl -fsSL https://raw.githubusercontent.com/1jehuang/jcode/master/scripts/install.sh | bash"
+ },
+ ],
+ defaultModels: [
+ { id: "claude-opus-5", name: "Claude Opus 5", alias: "opus", defaultValue: "cc/claude-opus-5" },
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", alias: "sonnet", defaultValue: "cc/claude-sonnet-4-6" },
+ { id: "gpt-5.5", name: "GPT 5.5", alias: "gpt5", defaultValue: "cx/gpt-5.5" },
+ { id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", alias: "gemini", defaultValue: "gemini/gemini-3.1-pro" },
+ ],
+ },
"grok-build": {
id: "grok-build",
name: "Grok Build",
@@ -178,6 +357,32 @@ export const CLI_TOOLS = {
{ type: "warning", text: "Config path: Linux/macOS ~/.grok/config.toml • Windows %USERPROFILE%\\.grok\\config.toml" },
],
},
+ devin: {
+ id: "devin",
+ name: "Devin CLI",
+ image: "/providers/devin-cli.png",
+ color: "#6366F1",
+ description: "Cognition Devin CLI — local binary called by the Devin CLI provider via ACP/stdio",
+ configType: "guide",
+ installUrl: "https://cli.devin.ai",
+ notes: [
+ { type: "info", text: "This is a local dependency, not a routed CLI. The Devin CLI provider spawns `devin acp --agent-type summarizer` and relays its output." },
+ { type: "warning", text: "Install the Devin CLI and run `devin auth login` — without it, the provider returns a spawn error on first request." },
+ ],
+ guideSteps: [
+ { step: 1, title: "Install Devin CLI", desc: "Install via the official installer at cli.devin.ai.", docsUrl: "https://cli.devin.ai" },
+ { step: 2, title: "Authenticate", desc: "Log in once so the binary stores its own credentials." },
+ { step: 3, title: "Use the provider", desc: "Pick any Devin CLI model under the Providers tab — no API key field needed." },
+ ],
+ codeBlock: {
+ language: "bash",
+ code: `# Install Devin CLI (see https://cli.devin.ai for options)
+devin auth login
+
+# Verify detection (optional)
+devin --version`,
+ },
+ },
// HIDDEN: gemini-cli
// "gemini-cli": {
// id: "gemini-cli",
diff --git a/src/sse/handlers/chat.js b/src/sse/handlers/chat.js
index de8d7f47..96373f9a 100644
--- a/src/sse/handlers/chat.js
+++ b/src/sse/handlers/chat.js
@@ -253,7 +253,7 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
// Ensure real project ID is available for providers that need it (P0 fix: cold miss)
if ((provider === "antigravity" || provider === "gemini-cli") && !refreshedCredentials.projectId) {
- const pid = await getProjectIdForConnection(credentials.connectionId, refreshedCredentials.accessToken);
+ const pid = await getProjectIdForConnection(credentials.connectionId, refreshedCredentials.accessToken, provider);
if (pid) {
refreshedCredentials.projectId = pid;
// Persist to DB in background so subsequent requests have it immediately
diff --git a/src/sse/handlers/embeddings.js b/src/sse/handlers/embeddings.js
index 16ca5da7..f55534de 100644
--- a/src/sse/handlers/embeddings.js
+++ b/src/sse/handlers/embeddings.js
@@ -14,6 +14,16 @@ import { HTTP_STATUS } from "open-sse/config/runtimeConfig.js";
import * as log from "../utils/logger.js";
import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js";
import { getDeletedModelResponse } from "../services/deletedModels.js";
+import { saveRequestUsage } from "@/lib/usageDb.js";
+
+function exactEmbeddingUsage(raw) {
+ if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.estimated === true) return null;
+ const promptTokens = raw.prompt_tokens ?? raw.input_tokens;
+ const completionTokens = raw.completion_tokens ?? raw.output_tokens ?? 0;
+ const totalTokens = raw.total_tokens;
+ if (!Number.isSafeInteger(promptTokens) || promptTokens <= 0 || completionTokens !== 0 || totalTokens !== promptTokens) return null;
+ return { prompt_tokens: promptTokens, completion_tokens: 0, total_tokens: totalTokens };
+}
/**
* Handle embeddings request for the SSE/Next.js server.
@@ -130,7 +140,21 @@ export async function handleEmbeddings(request) {
}
});
- if (result.success) return result.response;
+ if (result.success) {
+ const usage = exactEmbeddingUsage(result.usage);
+ if (usage) {
+ saveRequestUsage({
+ provider,
+ model,
+ connectionId: credentials.connectionId,
+ apiKey,
+ endpoint: url.pathname,
+ tokens: usage,
+ status: "success",
+ }).catch(() => {});
+ }
+ return result.response;
+ }
const { shouldFallback } = await markAccountUnavailable(credentials.connectionId, result.status, result.error, provider, model);
diff --git a/src/sse/handlers/fetch.js b/src/sse/handlers/fetch.js
index 6144580c..7edbedb6 100644
--- a/src/sse/handlers/fetch.js
+++ b/src/sse/handlers/fetch.js
@@ -101,7 +101,7 @@ export async function handleFetch(request) {
return handleComboChat({
body,
models: comboModels,
- handleSingleModel: (b, m) => handleSingleProviderFetch(b, m, request, apiKey, settings),
+ handleSingleModel: (b, m) => handleSingleProviderFetch(b, m, request, apiKey, settings, ownerId),
log,
comboName: providerInput,
comboId: combo.id,
@@ -110,10 +110,10 @@ export async function handleFetch(request) {
});
}
- return handleSingleProviderFetch(body, providerInput, request, apiKey, settings);
+ return handleSingleProviderFetch(body, providerInput, request, apiKey, settings, ownerId);
}
-async function handleSingleProviderFetch(body, providerInput, request, apiKey, settings) {
+async function handleSingleProviderFetch(body, providerInput, request, apiKey, settings, ownerId) {
const targetUrl = body.url;
const format = body.format;
const maxCharacters = body.max_characters;
@@ -199,13 +199,11 @@ async function handleSingleProviderFetch(body, providerInput, request, apiKey, s
providerSpecificData: newCreds.providerSpecificData,
testStatus: "active"
});
- },
- onRequestSuccess: async () => {
- await clearAccountError(credentials.connectionId, credentials);
}
});
if (result.success) {
+ await clearAccountError(credentials.connectionId, credentials);
return new Response(JSON.stringify(result.data), {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
});
diff --git a/src/sse/services/auth.js b/src/sse/services/auth.js
index 7fdea2e4..db862767 100644
--- a/src/sse/services/auth.js
+++ b/src/sse/services/auth.js
@@ -289,7 +289,13 @@ export async function clearAccountError(connectionId, currentConnection, model =
// Only reset error state if no active locks remain
if (remainingActiveLocks.length === 0) {
- Object.assign(clearObj, { testStatus: "active", lastError: null, lastErrorAt: null, backoffLevel: 0 });
+ Object.assign(clearObj, {
+ testStatus: "active",
+ lastError: null,
+ errorCode: null,
+ lastErrorAt: null,
+ backoffLevel: 0
+ });
}
await updateProviderConnection(connectionId, clearObj);
diff --git a/tests/__baseline__/alias-baseline.json b/tests/__baseline__/alias-baseline.json
index 6e189878..5aa2e0f3 100644
--- a/tests/__baseline__/alias-baseline.json
+++ b/tests/__baseline__/alias-baseline.json
@@ -93,7 +93,30 @@
"polly": "aws-polly",
"aws-polly": "aws-polly",
"bb": "blackbox",
- "blackbox": "blackbox"
+ "blackbox": "blackbox",
+ "af": "api-airforce",
+ "airforce": "api-airforce",
+ "api-airforce": "api-airforce",
+ "llm7": "llm7",
+ "llm-7": "llm7",
+ "samba": "sambanova",
+ "sambanova": "sambanova",
+ "bm": "bluesminds",
+ "bluesminds": "bluesminds",
+ "bzl": "bazaarlink",
+ "bazaarlink": "bazaarlink",
+ "kgw": "kilo-gateway",
+ "kilo-gateway": "kilo-gateway",
+ "hunyuan": "tencent",
+ "tencent": "tencent",
+ "qianfan": "baidu",
+ "baidu": "baidu",
+ "ernie": "baidu",
+ "dv": "dv",
+ "devin": "devin",
+ "devin-cli": "devin-cli",
+ "morph": "morph",
+ "morphllm": "morph"
},
"idToAlias": {
"alicode": "alicode",
@@ -101,9 +124,13 @@
"alims-intl": "alims-intl",
"anthropic": "anthropic",
"antigravity": "ag",
+ "api-airforce": "af",
"assemblyai": "assemblyai",
"azure": "azure",
+ "baidu": "qianfan",
+ "bazaarlink": "bzl",
"blackbox": "blackbox",
+ "bluesminds": "bm",
"byteplus": "byteplus",
"cerebras": "cerebras",
"chutes": "chutes",
@@ -112,6 +139,7 @@
"clinepass": "clinepass",
"cloudflare-ai": "cloudflare-ai",
"codebuddy-cn": "cbcn",
+ "codebuddy-intl": "cbai",
"codex": "cx",
"cohere": "cohere",
"commandcode": "commandcode",
@@ -131,15 +159,18 @@
"groq": "groq",
"hyperbolic": "hyperbolic",
"iflow": "if",
+ "kilo-gateway": "kgw",
"kilocode": "kc",
"kimchi": "kimchi",
"kimi": "kimi",
"kiro": "kr",
+ "llm7": "llm7",
"mimo-free": "mmf",
"minimax": "minimax",
"minimax-cn": "minimax-cn",
"mistral": "mistral",
"mmf": "mmf",
+ "morph": "morph",
"nanobanana": "nanobanana",
"nebius": "nebius",
"nvidia": "nvidia",
@@ -149,12 +180,16 @@
"opencode": "oc",
"opencode-go": "opencode-go",
"openrouter": "openrouter",
+ "orbit-provider": "orbit",
"perplexity": "perplexity",
"perplexity-agent": "perplexity-agent",
"perplexity-web": "perplexity-web",
+ "poolside": "poolside",
"qoder": "qd",
"qwen": "qw",
+ "sambanova": "samba",
"siliconflow": "siliconflow",
+ "tencent": "hunyuan",
"together": "together",
"venice": "venice",
"vercel-ai-gateway": "vercel-ai-gateway",
@@ -163,9 +198,11 @@
"volcengine-ark": "volcengine-ark",
"xai": "xai",
"xiaomi-mimo": "xiaomi-mimo",
- "xiaomi-tokenplan": "xiaomi-tokenplan"
+ "xiaomi-tokenplan": "xiaomi-tokenplan",
+ "zed": "zd"
},
"modelKeys": [
+ "af",
"ag",
"alicode",
"alicode-intl",
@@ -174,7 +211,10 @@
"assemblyai",
"black-forest-labs",
"blackbox",
+ "bm",
"byteplus",
+ "bzl",
+ "cbai",
"cbcn",
"cc",
"cerebras",
@@ -205,17 +245,21 @@
"grok-web",
"groq",
"huggingface",
+ "hunyuan",
"hyperbolic",
"if",
"kc",
+ "kgw",
"kimchi",
"kimi",
"kr",
+ "llm7",
"local-device",
"minimax",
"minimax-cn",
"mistral",
"mmf",
+ "morph",
"nanobanana",
"nebius",
"nvidia",
@@ -228,13 +272,17 @@
"openrouter",
"openrouter-tts-models",
"openrouter-tts-voices",
+ "orbit",
"perplexity",
"perplexity-agent",
"perplexity-web",
+ "poolside",
"qd",
+ "qianfan",
"qw",
"recraft",
"runwayml",
+ "samba",
"sdwebui",
"siliconflow",
"stability-ai",
@@ -246,6 +294,7 @@
"voyage-ai",
"xai",
"xiaomi-mimo",
- "xiaomi-tokenplan"
+ "xiaomi-tokenplan",
+ "zd"
]
}
\ No newline at end of file
diff --git a/tests/__baseline__/known-fails.txt b/tests/__baseline__/known-fails.txt
index 9ffd63f9..49fd6a11 100644
--- a/tests/__baseline__/known-fails.txt
+++ b/tests/__baseline__/known-fails.txt
@@ -1,4 +1,3 @@
-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/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
diff --git a/tests/__baseline__/providers-baseline.json b/tests/__baseline__/providers-baseline.json
index 232c5d59..6637bce6 100644
--- a/tests/__baseline__/providers-baseline.json
+++ b/tests/__baseline__/providers-baseline.json
@@ -1,14 +1,22 @@
{
- "alicode-intl": {
- "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
+ "alicode": {
+ "baseUrl": "https://coding.dashscope.aliyuncs.com/v1/chat/completions",
"headers": {},
"quirks": {
"preserveCacheControl": true
},
"format": "openai"
},
- "alicode": {
- "baseUrl": "https://coding.dashscope.aliyuncs.com/v1/chat/completions",
+ "alicode-intl": {
+ "baseUrl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions",
+ "headers": {},
+ "quirks": {
+ "preserveCacheControl": true
+ },
+ "format": "openai"
+ },
+ "alims-intl": {
+ "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
"headers": {},
"quirks": {
"preserveCacheControl": true
@@ -25,7 +33,7 @@
},
"antigravity": {
"baseUrls": [
- "https://cloudcode-pa.googleapis.com"
+ "https://daily-cloudcode-pa.googleapis.com"
],
"format": "antigravity",
"headers": {
@@ -51,6 +59,15 @@
"clientSecret": "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",
"tokenUrl": "https://oauth2.googleapis.com/token"
},
+ "api-airforce": {
+ "baseUrl": "https://api.airforce/v1/chat/completions",
+ "validateUrl": "https://api.airforce/v1/models",
+ "headers": {
+ "HTTP-Referer": "https://endpoint-proxy.local",
+ "X-Title": "Endpoint Proxy"
+ },
+ "format": "openai"
+ },
"assemblyai": {
"baseUrl": "https://api.assemblyai.com/v1/audio/transcriptions",
"validateUrl": "https://api.assemblyai.com/v1/account",
@@ -61,11 +78,26 @@
"headers": {},
"format": "openai"
},
+ "baidu": {
+ "baseUrl": "https://qianfan.baidubce.com/v2/chat/completions",
+ "validateUrl": "https://qianfan.baidubce.com/v2/models",
+ "format": "openai"
+ },
+ "bazaarlink": {
+ "baseUrl": "https://bazaarlink.ai/api/v1/chat/completions",
+ "validateUrl": "https://bazaarlink.ai/api/v1/models",
+ "format": "openai"
+ },
"blackbox": {
"baseUrl": "https://api.blackbox.ai/v1/chat/completions",
"thinkingFormat": "openai",
"format": "openai"
},
+ "bluesminds": {
+ "baseUrl": "https://api.bluesminds.com/v1/chat/completions",
+ "validateUrl": "https://api.bluesminds.com/v1/models",
+ "format": "openai"
+ },
"byteplus": {
"baseUrl": "https://ark.ap-southeast.bytepluses.com/api/coding/v3/chat/completions",
"headers": {},
@@ -191,6 +223,26 @@
"format": "openai",
"tokenUrl": "https://copilot.tencent.com/v2/plugin/auth/token"
},
+ "codebuddy-intl": {
+ "baseUrl": "https://www.codebuddy.ai/v2/chat/completions",
+ "forceStream": true,
+ "thinkingFormat": "openai",
+ "headers": {
+ "User-Agent": "IDE/2.108.1 CodeBuddy/2.108.1",
+ "X-Product": "SaaS",
+ "X-IDE-Type": "IDE",
+ "X-IDE-Name": "IDE",
+ "x-requested-with": "XMLHttpRequest",
+ "x-codebuddy-request": "1"
+ },
+ "auth": {
+ "combined": true,
+ "header": "Authorization",
+ "scheme": "bearer"
+ },
+ "format": "openai",
+ "tokenUrl": "https://www.codebuddy.ai/v2/plugin/auth/token"
+ },
"codex": {
"baseUrl": "https://chatgpt.com/backend-api/codex/responses",
"format": "openai-responses",
@@ -279,19 +331,6 @@
"validateUrl": "https://api.fireworks.ai/inference/v1/models",
"format": "openai"
},
- "gemini-cli": {
- "baseUrl": "https://cloudcode-pa.googleapis.com/v1internal",
- "format": "gemini-cli",
- "cliVersion": "0.34.0",
- "apiClient": "google-genai-sdk/1.41.0 gl-node/v22.19.0",
- "usage": {
- "quotaUrl": "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota",
- "loadCodeAssistUrl": "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
- },
- "clientId": "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
- "clientSecret": "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",
- "tokenUrl": "https://oauth2.googleapis.com/token"
- },
"gemini": {
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/models",
"format": "gemini",
@@ -308,6 +347,19 @@
}
}
},
+ "gemini-cli": {
+ "baseUrl": "https://cloudcode-pa.googleapis.com/v1internal",
+ "format": "gemini-cli",
+ "cliVersion": "0.34.0",
+ "apiClient": "google-genai-sdk/1.41.0 gl-node/v22.19.0",
+ "usage": {
+ "quotaUrl": "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota",
+ "loadCodeAssistUrl": "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
+ },
+ "clientId": "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
+ "clientSecret": "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",
+ "tokenUrl": "https://oauth2.googleapis.com/token"
+ },
"github": {
"baseUrl": "https://api.githubcopilot.com/chat/completions",
"responsesUrl": "https://api.githubcopilot.com/responses",
@@ -346,14 +398,6 @@
},
"format": "openai"
},
- "glm-cn": {
- "baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
- "headers": {},
- "usage": {
- "url": "https://open.bigmodel.cn/api/monitor/usage/quota/limit"
- },
- "format": "openai"
- },
"glm": {
"baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
"format": "claude",
@@ -396,6 +440,14 @@
}
]
},
+ "glm-cn": {
+ "baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
+ "headers": {},
+ "usage": {
+ "url": "https://open.bigmodel.cn/api/monitor/usage/quota/limit"
+ },
+ "format": "openai"
+ },
"grok-cli": {
"baseUrl": "https://cli-chat-proxy.grok.com/v1/responses",
"format": "openai-responses",
@@ -458,6 +510,11 @@
"clientSecret": "4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW",
"tokenUrl": "https://iflow.cn/oauth/token"
},
+ "kilo-gateway": {
+ "baseUrl": "https://api.kilo.ai/api/gateway/chat/completions",
+ "validateUrl": "https://api.kilo.ai/api/gateway/models",
+ "format": "openai"
+ },
"kilocode": {
"baseUrl": "https://api.kilo.ai/api/openrouter/chat/completions",
"headers": {},
@@ -548,7 +605,6 @@
"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"
},
@@ -560,62 +616,16 @@
"limitsPath": "/getUsageLimits"
}
},
+ "llm7": {
+ "baseUrl": "https://api.llm7.io/v1/chat/completions",
+ "validateUrl": "https://api.llm7.io/v1/models",
+ "format": "openai"
+ },
"mimo-free": {
"baseUrl": "https://api.xiaomimimo.com/api/free-ai/openai/chat",
"noAuth": true,
"format": "openai"
},
- "minimax-cn": {
- "baseUrl": "https://api.minimaxi.com/anthropic/v1/messages",
- "format": "claude",
- "urlSuffix": "?beta=true",
- "headers": {
- "Anthropic-Version": "2023-06-01",
- "Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
- },
- "quirks": {
- "dropOutputConfig": true
- },
- "reasoningInject": {
- "scope": "all"
- },
- "auth": {
- "combined": true,
- "header": "x-api-key",
- "scheme": "raw"
- },
- "usage": {
- "urls": [
- "https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains",
- "https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains"
- ]
- },
- "transports": [
- {
- "format": "openai",
- "baseUrl": "https://api.minimaxi.com/v1/chat/completions",
- "auth": {
- "combined": true,
- "header": "Authorization",
- "scheme": "bearer"
- }
- },
- {
- "format": "claude",
- "baseUrl": "https://api.minimaxi.com/anthropic/v1/messages",
- "urlSuffix": "?beta=true",
- "headers": {
- "Anthropic-Version": "2023-06-01",
- "Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
- },
- "auth": {
- "combined": true,
- "header": "x-api-key",
- "scheme": "raw"
- }
- }
- ]
- },
"minimax": {
"baseUrl": "https://api.minimax.io/anthropic/v1/messages",
"format": "claude",
@@ -667,6 +677,57 @@
}
]
},
+ "minimax-cn": {
+ "baseUrl": "https://api.minimaxi.com/anthropic/v1/messages",
+ "format": "claude",
+ "urlSuffix": "?beta=true",
+ "headers": {
+ "Anthropic-Version": "2023-06-01",
+ "Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
+ },
+ "quirks": {
+ "dropOutputConfig": true
+ },
+ "reasoningInject": {
+ "scope": "all"
+ },
+ "auth": {
+ "combined": true,
+ "header": "x-api-key",
+ "scheme": "raw"
+ },
+ "usage": {
+ "urls": [
+ "https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains",
+ "https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains"
+ ]
+ },
+ "transports": [
+ {
+ "format": "openai",
+ "baseUrl": "https://api.minimaxi.com/v1/chat/completions",
+ "auth": {
+ "combined": true,
+ "header": "Authorization",
+ "scheme": "bearer"
+ }
+ },
+ {
+ "format": "claude",
+ "baseUrl": "https://api.minimaxi.com/anthropic/v1/messages",
+ "urlSuffix": "?beta=true",
+ "headers": {
+ "Anthropic-Version": "2023-06-01",
+ "Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
+ },
+ "auth": {
+ "combined": true,
+ "header": "x-api-key",
+ "scheme": "raw"
+ }
+ }
+ ]
+ },
"mistral": {
"baseUrl": "https://api.mistral.ai/v1/chat/completions",
"validateUrl": "https://api.mistral.ai/v1/models",
@@ -680,6 +741,11 @@
"noAuth": true,
"format": "openai"
},
+ "morph": {
+ "baseUrl": "https://api.morphllm.com/v1/chat/completions",
+ "validateUrl": "https://api.morphllm.com/v1/models",
+ "format": "openai"
+ },
"nanobanana": {
"baseUrl": "https://api.nanobananaapi.ai/v1/chat/completions",
"validateUrl": "https://api.nanobananaapi.ai/v1/models",
@@ -695,25 +761,20 @@
"validateUrl": "https://integrate.api.nvidia.com/v1/models",
"format": "openai"
},
- "ollama-local": {
- "baseUrl": "http://localhost:11434/api/chat",
- "format": "ollama"
- },
"ollama": {
"baseUrl": "https://ollama.com/api/chat",
"validateUrl": "https://ollama.com/api/tags",
"format": "ollama"
},
+ "ollama-local": {
+ "baseUrl": "http://localhost:11434/api/chat",
+ "format": "ollama"
+ },
"openai": {
"baseUrl": "https://api.openai.com/v1/chat/completions",
"forceStream": true,
"format": "openai"
},
- "opencode-go": {
- "baseUrl": "https://opencode.ai/zen/go/v1/chat/completions",
- "headers": {},
- "format": "openai"
- },
"opencode": {
"baseUrl": "https://opencode.ai",
"headers": {
@@ -722,6 +783,11 @@
"noAuth": true,
"format": "openai"
},
+ "opencode-go": {
+ "baseUrl": "https://opencode.ai/zen/go/v1/chat/completions",
+ "headers": {},
+ "format": "openai"
+ },
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1/chat/completions",
"thinkingFormat": "openai",
@@ -731,10 +797,15 @@
},
"format": "openai"
},
- "perplexity-web": {
- "baseUrl": "https://www.perplexity.ai/rest/sse/perplexity_ask",
- "format": "perplexity-web",
- "authType": "cookie"
+ "orbit-provider": {
+ "baseUrl": "https://api.orbit-provider.com/anthropic/v1/messages",
+ "format": "claude",
+ "headers": {
+ "anthropic-version": "2023-06-01"
+ },
+ "usage": {
+ "url": "https://api.orbit-provider.com/v1/usage"
+ }
},
"perplexity": {
"baseUrl": "https://api.perplexity.ai/chat/completions",
@@ -746,6 +817,16 @@
"validateUrl": "https://api.perplexity.ai/v1/models",
"format": "openai-responses"
},
+ "perplexity-web": {
+ "baseUrl": "https://www.perplexity.ai/rest/sse/perplexity_ask",
+ "format": "perplexity-web",
+ "authType": "cookie"
+ },
+ "poolside": {
+ "baseUrl": "https://inference.poolside.ai/v1/chat/completions",
+ "validateUrl": "https://inference.poolside.ai/v1/models",
+ "format": "openai"
+ },
"qoder": {
"baseUrl": "https://api3.qoder.sh/algo/api/v2/service/pro/sse/agent_chat_generation",
"headers": {},
@@ -762,12 +843,22 @@
"clientId": "f0304373b74a44d2b584a3fb70ca9e56",
"tokenUrl": "https://chat.qwen.ai/api/v1/oauth2/token"
},
+ "sambanova": {
+ "baseUrl": "https://api.sambanova.ai/v1/chat/completions",
+ "validateUrl": "https://api.sambanova.ai/v1/models",
+ "format": "openai"
+ },
"siliconflow": {
"baseUrl": "https://api.siliconflow.com/v1/chat/completions",
"validateUrl": "https://api.siliconflow.com/v1/models",
"thinkingFormat": "openai",
"format": "openai"
},
+ "tencent": {
+ "baseUrl": "https://api.hunyuan.cloud.tencent.com/v1/chat/completions",
+ "validateUrl": "https://api.hunyuan.cloud.tencent.com/v1/models",
+ "format": "openai"
+ },
"together": {
"baseUrl": "https://api.together.xyz/v1/chat/completions",
"validateUrl": "https://api.together.xyz/v1/models",
@@ -790,14 +881,14 @@
},
"format": "openai"
},
- "vertex-partner": {
- "baseUrl": "https://aiplatform.googleapis.com",
- "format": "openai"
- },
"vertex": {
"baseUrl": "https://aiplatform.googleapis.com",
"format": "vertex"
},
+ "vertex-partner": {
+ "baseUrl": "https://aiplatform.googleapis.com",
+ "format": "openai"
+ },
"volcengine-ark": {
"baseUrl": "https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
"headers": {},
@@ -873,12 +964,21 @@
}
]
},
- "alims-intl": {
- "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
- "headers": {},
- "quirks": {
- "preserveCacheControl": true
+ "zed": {
+ "baseUrl": "https://cloud.zed.dev/completions",
+ "format": "openai",
+ "forceStream": true,
+ "headers": {
+ "content-type": "application/json"
},
- "format": "openai"
+ "auth": {
+ "combined": true,
+ "header": "Authorization",
+ "scheme": "
"
+ },
+ "usage": {
+ "url": "https://cloud.zed.dev/client/users/me"
+ },
+ "modelsUrl": "https://cloud.zed.dev/models"
}
}
\ No newline at end of file
diff --git a/tests/__baseline__/verify-alias.mjs b/tests/__baseline__/verify-alias.mjs
index 56caca53..a3fc1f91 100644
--- a/tests/__baseline__/verify-alias.mjs
+++ b/tests/__baseline__/verify-alias.mjs
@@ -20,6 +20,9 @@ const ALIAS_TOKENS = [
"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",
+ "af","airforce","api-airforce","llm7","llm-7","samba","sambanova","bm","bluesminds",
+ "bzl","bazaarlink","kgw","kilo-gateway","hunyuan","tencent","qianfan","baidu","ernie",
+ "dv","devin","devin-cli","morph","morphllm",
];
// Sort idToAlias by key — runtime accesses by key, order is irrelevant (content-based)
diff --git a/tests/__baseline__/verify-no-regression.mjs b/tests/__baseline__/verify-no-regression.mjs
index 26466c27..21bdca45 100644
--- a/tests/__baseline__/verify-no-regression.mjs
+++ b/tests/__baseline__/verify-no-regression.mjs
@@ -1,28 +1,34 @@
-// Gate: so kết quả test hiện tại với baseline known-fails.
+// Gate: so kết quả test hiện tại với baseline kết quả đã commit.
// 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
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 normalizeTestPath = (filePath) => {
+ const normalized = String(filePath || "").replaceAll("\\", "/");
+ const testsIndex = normalized.lastIndexOf("/tests/");
+ return testsIndex >= 0 ? normalized.slice(testsIndex + 1) : normalized;
+};
+const assertionStatuses = (results) => new Map(results.testResults.flatMap(f =>
+ f.assertionResults.map(a => [normalizeTestPath(f.name) + " :: " + a.fullName, a.status])
+));
+
+const baseline = JSON.parse(readFileSync(new URL("./baseline-results.json", import.meta.url), "utf8"));
+const baselineStatuses = assertionStatuses(baseline);
const nowFails = r.testResults.flatMap(f =>
f.assertionResults.filter(a => a.status === "failed")
- .map(a => f.name.split("/app/")[1] + " :: " + a.fullName)
+ .map(a => normalizeTestPath(f.name) + " :: " + a.fullName)
);
-// Regression = fail bây giờ NHƯNG không có trong baseline known-fails
-const regressions = nowFails.filter(f => !knownFails.has(f));
+// Regression = test từng pass trong baseline nhưng fail bây giờ.
+const regressions = nowFails.filter(name => baselineStatuses.get(name) === "passed");
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)`);
+console.log(`✅ No regression. (now fails=${nowFails.length}, baseline assertions=${baselineStatuses.size})`);
diff --git a/tests/translator/__snapshots__/golden-url-header.test.js.snap b/tests/translator/__snapshots__/golden-url-header.test.js.snap
index c9a0db21..2f1e352b 100644
--- a/tests/translator/__snapshots__/golden-url-header.test.js.snap
+++ b/tests/translator/__snapshots__/golden-url-header.test.js.snap
@@ -38,6 +38,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > alicode-intl → hea
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > alims-intl → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > anthropic → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -66,6 +85,31 @@ exports[`GOLDEN buildHeaders (default executor providers) > anthropic → header
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > api-airforce → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "HTTP-Referer": "https://endpoint-proxy.local",
+ "X-Title": "Endpoint Proxy",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "HTTP-Referer": "https://endpoint-proxy.local",
+ "X-Title": "Endpoint Proxy",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "HTTP-Referer": "https://endpoint-proxy.local",
+ "X-Title": "Endpoint Proxy",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > assemblyai → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -85,6 +129,44 @@ exports[`GOLDEN buildHeaders (default executor providers) > assemblyai → heade
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > baidu → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
+exports[`GOLDEN buildHeaders (default executor providers) > bazaarlink → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > blackbox → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -104,6 +186,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > blackbox → headers
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > bluesminds → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > byteplus → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -370,6 +471,43 @@ exports[`GOLDEN buildHeaders (default executor providers) > codebuddy-cn → hea
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > codebuddy-intl → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "User-Agent": "IDE/2.108.1 CodeBuddy/2.108.1",
+ "X-IDE-Name": "IDE",
+ "X-IDE-Type": "IDE",
+ "X-Product": "SaaS",
+ "x-codebuddy-request": "1",
+ "x-requested-with": "XMLHttpRequest",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "User-Agent": "IDE/2.108.1 CodeBuddy/2.108.1",
+ "X-IDE-Name": "IDE",
+ "X-IDE-Type": "IDE",
+ "X-Product": "SaaS",
+ "x-codebuddy-request": "1",
+ "x-requested-with": "XMLHttpRequest",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ "User-Agent": "IDE/2.108.1 CodeBuddy/2.108.1",
+ "X-IDE-Name": "IDE",
+ "X-IDE-Type": "IDE",
+ "X-Product": "SaaS",
+ "x-codebuddy-request": "1",
+ "x-requested-with": "XMLHttpRequest",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > cohere → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -619,6 +757,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > hyperbolic → heade
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > kilo-gateway → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > kilocode → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -722,6 +879,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > kimi-coding → head
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > llm7 → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > minimax → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -810,6 +986,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > mmf → headers (api
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > morph → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > nanobanana → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -930,6 +1125,28 @@ exports[`GOLDEN buildHeaders (default executor providers) > openrouter → heade
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > orbit-provider → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Content-Type": "application/json",
+ "anthropic-version": "2023-06-01",
+ "x-api-key": "",
+ },
+ "nonStream": {
+ "Content-Type": "application/json",
+ "anthropic-version": "2023-06-01",
+ "x-api-key": "",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Content-Type": "application/json",
+ "anthropic-version": "2023-06-01",
+ "x-api-key": "",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > perplexity → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -968,6 +1185,44 @@ exports[`GOLDEN buildHeaders (default executor providers) > perplexity-agent →
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > poolside → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
+exports[`GOLDEN buildHeaders (default executor providers) > sambanova → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > siliconflow → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -987,6 +1242,25 @@ exports[`GOLDEN buildHeaders (default executor providers) > siliconflow → head
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > tencent → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildHeaders (default executor providers) > together → headers (apiKey / oauth) 1`] = `
{
"apiKey": {
@@ -1101,6 +1375,28 @@ exports[`GOLDEN buildHeaders (default executor providers) > xiaomi-mimo → head
}
`;
+exports[`GOLDEN buildHeaders (default executor providers) > zed → headers (apiKey / oauth) 1`] = `
+{
+ "apiKey": {
+ "Accept": "text/event-stream",
+ "Authorization": "",
+ "Content-Type": "application/json",
+ "content-type": "application/json",
+ },
+ "nonStream": {
+ "Authorization": "",
+ "Content-Type": "application/json",
+ "content-type": "application/json",
+ },
+ "oauth": {
+ "Accept": "text/event-stream",
+ "Authorization": "",
+ "Content-Type": "application/json",
+ "content-type": "application/json",
+ },
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > alicode → url (stream + non-stream) 1`] = `
{
"nonStream": "https://coding.dashscope.aliyuncs.com/v1/chat/completions",
@@ -1115,6 +1411,13 @@ exports[`GOLDEN buildUrl (default executor providers) > alicode-intl → url (st
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > alims-intl → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
+ "stream": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > anthropic → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.anthropic.com/v1/messages",
@@ -1122,6 +1425,13 @@ exports[`GOLDEN buildUrl (default executor providers) > anthropic → url (strea
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > api-airforce → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.airforce/v1/chat/completions",
+ "stream": "https://api.airforce/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > assemblyai → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.assemblyai.com/v1/audio/transcriptions",
@@ -1129,6 +1439,20 @@ exports[`GOLDEN buildUrl (default executor providers) > assemblyai → url (stre
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > baidu → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://qianfan.baidubce.com/v2/chat/completions",
+ "stream": "https://qianfan.baidubce.com/v2/chat/completions",
+}
+`;
+
+exports[`GOLDEN buildUrl (default executor providers) > bazaarlink → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://bazaarlink.ai/api/v1/chat/completions",
+ "stream": "https://bazaarlink.ai/api/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > blackbox → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.blackbox.ai/chat/completions",
@@ -1136,6 +1460,13 @@ exports[`GOLDEN buildUrl (default executor providers) > blackbox → url (stream
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > bluesminds → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.bluesminds.com/v1/chat/completions",
+ "stream": "https://api.bluesminds.com/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > byteplus → url (stream + non-stream) 1`] = `
{
"nonStream": "https://ark.ap-southeast.bytepluses.com/api/coding/v3/chat/completions",
@@ -1192,6 +1523,13 @@ exports[`GOLDEN buildUrl (default executor providers) > codebuddy-cn → url (st
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > codebuddy-intl → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://www.codebuddy.ai/v2/chat/completions",
+ "stream": "https://www.codebuddy.ai/v2/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > cohere → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.cohere.ai/v1/chat/completions",
@@ -1276,6 +1614,13 @@ exports[`GOLDEN buildUrl (default executor providers) > hyperbolic → url (stre
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > kilo-gateway → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.kilo.ai/api/gateway/chat/completions",
+ "stream": "https://api.kilo.ai/api/gateway/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > kilocode → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.kilo.ai/api/openrouter/chat/completions",
@@ -1304,6 +1649,13 @@ exports[`GOLDEN buildUrl (default executor providers) > kimi-coding → url (str
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > llm7 → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.llm7.io/v1/chat/completions",
+ "stream": "https://api.llm7.io/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > minimax → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.minimax.io/anthropic/v1/messages?beta=true",
@@ -1332,6 +1684,13 @@ exports[`GOLDEN buildUrl (default executor providers) > mmf → url (stream + no
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > morph → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.morphllm.com/v1/chat/completions",
+ "stream": "https://api.morphllm.com/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > nanobanana → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.nanobananaapi.ai/v1/chat/completions",
@@ -1374,6 +1733,13 @@ exports[`GOLDEN buildUrl (default executor providers) > openrouter → url (stre
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > orbit-provider → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.orbit-provider.com/anthropic/v1/messages",
+ "stream": "https://api.orbit-provider.com/anthropic/v1/messages",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > perplexity → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.perplexity.ai/chat/completions",
@@ -1388,6 +1754,20 @@ exports[`GOLDEN buildUrl (default executor providers) > perplexity-agent → url
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > poolside → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://inference.poolside.ai/v1/chat/completions",
+ "stream": "https://inference.poolside.ai/v1/chat/completions",
+}
+`;
+
+exports[`GOLDEN buildUrl (default executor providers) > sambanova → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.sambanova.ai/v1/chat/completions",
+ "stream": "https://api.sambanova.ai/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > siliconflow → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.siliconflow.com/v1/chat/completions",
@@ -1395,6 +1775,13 @@ exports[`GOLDEN buildUrl (default executor providers) > siliconflow → url (str
}
`;
+exports[`GOLDEN buildUrl (default executor providers) > tencent → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://api.hunyuan.cloud.tencent.com/v1/chat/completions",
+ "stream": "https://api.hunyuan.cloud.tencent.com/v1/chat/completions",
+}
+`;
+
exports[`GOLDEN buildUrl (default executor providers) > together → url (stream + non-stream) 1`] = `
{
"nonStream": "https://api.together.xyz/v1/chat/completions",
@@ -1436,3 +1823,10 @@ exports[`GOLDEN buildUrl (default executor providers) > xiaomi-mimo → url (str
"stream": "https://api.xiaomimimo.com/v1/chat/completions",
}
`;
+
+exports[`GOLDEN buildUrl (default executor providers) > zed → url (stream + non-stream) 1`] = `
+{
+ "nonStream": "https://cloud.zed.dev/completions",
+ "stream": "https://cloud.zed.dev/completions",
+}
+`;
diff --git a/tests/translator/claude-kiro-direct.test.js b/tests/translator/claude-kiro-direct.test.js
index 3c2d964b..3fca7be1 100644
--- a/tests/translator/claude-kiro-direct.test.js
+++ b/tests/translator/claude-kiro-direct.test.js
@@ -98,6 +98,18 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("24576");
});
+ it("normalizes an unsupported Kiro intensity suffix while preserving agentic behavior", () => {
+ const out = C2K(
+ { messages: [{ role: "user", content: "hello" }] },
+ null,
+ "claude-sonnet-4.5-thinking-agentic(high)",
+ );
+
+ expect(out.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-4.5");
+ expect(out.additionalModelRequestFields).toBeUndefined();
+ expect(out.systemPrompt).toContain("CHUNKED WRITE PROTOCOL");
+ });
+
it("maps output_config.effort high to Kiro CLI-style additionalModelRequestFields for effort models", () => {
const out = C2K({
output_config: { effort: "high" },
diff --git a/tests/unit/antigravity-retry-hook.test.js b/tests/unit/antigravity-retry-hook.test.js
index 989dd88f..adb8bd25 100644
--- a/tests/unit/antigravity-retry-hook.test.js
+++ b/tests/unit/antigravity-retry-hook.test.js
@@ -67,8 +67,8 @@ describe("antigravity computeRetryDelay hook (D3)", () => {
expect(out.request.tools[0].functionDeclarations.map(fn => fn.name)).toEqual(["read_file"]);
});
- it("registry uses the official IDE cloudcode host and user agent", () => {
- expect(antigravity.transport.baseUrls).toEqual(["https://cloudcode-pa.googleapis.com"]);
+ it("registry uses the daily IDE cloudcode host and user agent", () => {
+ expect(antigravity.transport.baseUrls).toEqual(["https://daily-cloudcode-pa.googleapis.com"]);
expect(antigravity.transport.headers["User-Agent"]).toBe("antigravity/ide/2.1.1 darwin/arm64");
});
diff --git a/tests/unit/antigravity-stream-options.test.js b/tests/unit/antigravity-stream-options.test.js
new file mode 100644
index 00000000..47da8769
--- /dev/null
+++ b/tests/unit/antigravity-stream-options.test.js
@@ -0,0 +1,45 @@
+import { describe, expect, it } from "vitest";
+import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
+
+const credentials = {
+ projectId: "synthetic-project",
+ connectionId: "synthetic-connection",
+};
+
+function requestBody(stream) {
+ return {
+ stream,
+ stream_options: { include_usage: true },
+ request: {
+ contents: [{ role: "user", parts: [{ text: "Reply only OK" }] }],
+ },
+ };
+}
+
+describe("AntigravityExecutor stream_options normalization", () => {
+ it("removes stream_options from a non-streaming request", () => {
+ const executor = new AntigravityExecutor();
+ const output = executor.transformRequest(
+ "gpt-oss-120b-medium",
+ requestBody(false),
+ false,
+ credentials,
+ );
+
+ expect(output.stream).toBe(false);
+ expect(output.stream_options).toBeUndefined();
+ });
+
+ it("preserves stream_options for a streaming request", () => {
+ const executor = new AntigravityExecutor();
+ const output = executor.transformRequest(
+ "gpt-oss-120b-medium",
+ requestBody(true),
+ true,
+ credentials,
+ );
+
+ expect(output.stream).toBe(true);
+ expect(output.stream_options).toEqual({ include_usage: true });
+ });
+});
diff --git a/tests/unit/capabilities-opus-context.test.js b/tests/unit/capabilities-opus-context.test.js
index 9bb7518f..2ef9f956 100644
--- a/tests/unit/capabilities-opus-context.test.js
+++ b/tests/unit/capabilities-opus-context.test.js
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js";
// Claude Opus 4.6+ ships a 1M-token context window (GA, standard pricing).
-// The registry exposes dashed ids (claude-opus-4-8, claude-opus-4-7), which
+// The registry exposes dashed ids (claude-opus-5, claude-opus-4-8, claude-opus-4-7), which
// must resolve to the 1M context + adaptive thinking caps rather than falling
// through to the generic *claude*opus* pattern (200k / budget thinking).
describe("Claude Opus 1M context capabilities", () => {
@@ -17,6 +17,10 @@ describe("Claude Opus 1M context capabilities", () => {
};
for (const model of [
+ "claude-opus-5",
+ "claude-opus-5-thinking",
+ "claude-opus-5-agentic",
+ "claude-opus-5-thinking-agentic",
"claude-opus-4-8",
"claude-opus-4.8",
"claude-opus-4-7",
diff --git a/tests/unit/capabilities.test.js b/tests/unit/capabilities.test.js
index ccfddfa5..a5c7b03d 100644
--- a/tests/unit/capabilities.test.js
+++ b/tests/unit/capabilities.test.js
@@ -20,6 +20,18 @@ describe("getCapabilitiesForModel", () => {
search: true,
};
+ it("reports Kiro Claude Opus 5 variants as 1M adaptive-thinking models", () => {
+ for (const model of [
+ "claude-opus-5",
+ "anthropic/claude-opus-5",
+ "claude-opus-5-thinking",
+ "claude-opus-5-agentic",
+ "claude-opus-5-thinking-agentic",
+ ]) {
+ expect(getCapabilitiesForModel("kiro", model)).toMatchObject(claudeSonnet5Expected);
+ }
+ });
+
it("reports Kiro Claude Opus 4.8 as a 1M context model", () => {
expect(getCapabilitiesForModel("kiro", "claude-opus-4.8").contextWindow).toBe(1000000);
expect(getCapabilitiesForModel("kiro", "anthropic/claude-opus-4.8").contextWindow).toBe(1000000);
diff --git a/tests/unit/codex-image-fetch.test.js b/tests/unit/codex-image-fetch.test.js
index d50614a2..0bd113b3 100644
--- a/tests/unit/codex-image-fetch.test.js
+++ b/tests/unit/codex-image-fetch.test.js
@@ -11,7 +11,7 @@
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" }) }));
+vi.mock("node:dns/promises", () => ({ lookup: async () => [{ address: "93.184.216.34", family: 4 }] }));
import { CodexExecutor } from "../../open-sse/executors/codex.js";
import * as proxyFetchModule from "../../open-sse/utils/proxyFetch.js";
diff --git a/tests/unit/cursor-agent-exec-request.test.js b/tests/unit/cursor-agent-exec-request.test.js
new file mode 100644
index 00000000..347e159f
--- /dev/null
+++ b/tests/unit/cursor-agent-exec-request.test.js
@@ -0,0 +1,114 @@
+import { describe, it, expect } from "vitest";
+
+import { CursorExecutor } from "../../open-sse/executors/cursor.js";
+import { encodeField, wrapConnectRPCFrame } from "../../open-sse/utils/cursorProtobuf.js";
+
+const LEN = 2;
+
+// agent.v1.AgentServerMessage.exec_request (field 2) carrying one ExecServerMessage variant.
+function execRequestFrame(execField) {
+ const execServerMessage = Buffer.from(encodeField(execField, LEN, new Uint8Array()));
+ return Buffer.from(wrapConnectRPCFrame(encodeField(2, LEN, execServerMessage)));
+}
+
+// agent.v1.AgentServerMessage.interaction_update (field 1) → text delta.
+function textFrame(text) {
+ const textPart = Buffer.from(encodeField(1, LEN, text));
+ const update = Buffer.from(encodeField(1, LEN, textPart));
+ return Buffer.from(wrapConnectRPCFrame(encodeField(1, LEN, update)));
+}
+
+function stubAgentSession(executor, frames) {
+ const written = [];
+ const queue = [...frames];
+ executor.openAgentHttp2Stream = () => ({
+ responseHeaders: Promise.resolve({ ":status": 200 }),
+ write: (frame) => written.push(Buffer.from(frame)),
+ end() {},
+ close() {},
+ async read() {
+ if (!queue.length) return { value: undefined, done: true };
+ return { value: queue.shift(), done: false };
+ },
+ });
+ return written;
+}
+
+const credentials = {
+ accessToken: "test-token",
+ providerSpecificData: { machineId: "a".repeat(64) },
+};
+
+function parseSSE(text) {
+ return text
+ .split("\n\n")
+ .filter((chunk) => chunk.startsWith("data: "))
+ .map((chunk) => chunk.slice("data: ".length))
+ .filter((data) => data !== "[DONE]")
+ .map((data) => JSON.parse(data));
+}
+
+async function runAgent({ frames, stream }) {
+ const executor = new CursorExecutor();
+ const written = stubAgentSession(executor, frames);
+ const result = await executor.executeAgent({
+ model: "gpt-5.2",
+ body: { messages: [{ role: "user", content: "hi" }] },
+ stream,
+ credentials,
+ });
+ return { result, written };
+}
+
+describe("CursorExecutor AgentService exec_request handling", () => {
+ it("acknowledges a request-context exec request without ending the turn", async () => {
+ const { result, written } = await runAgent({
+ frames: [execRequestFrame(10), textFrame("hello")],
+ stream: true,
+ });
+
+ expect(written.length).toBe(2); // run frame + request-context reply
+ const events = parseSSE(await result.response.text());
+ const content = events.map((e) => e.choices?.[0]?.delta?.content || "").join("");
+ expect(content).toBe("hello");
+ });
+
+ it("does not render an unsupported exec request as assistant content", async () => {
+ const { result } = await runAgent({
+ frames: [textFrame("partial answer"), execRequestFrame(2)],
+ stream: true,
+ });
+
+ const body = await result.response.text();
+ expect(body).not.toContain("unsupported IDE tool\\n");
+ const events = parseSSE(body);
+ const content = events.map((e) => e.choices?.[0]?.delta?.content || "").join("");
+ expect(content).toBe("partial answer");
+
+ const errorEvent = events.find((e) => e.error);
+ expect(errorEvent?.error?.message).toContain("unsupported IDE tool");
+ expect(events.some((e) => e.choices?.[0]?.finish_reason === "stop")).toBe(false);
+ });
+
+ it("drops frames batched behind an unsupported exec request in the same read", async () => {
+ const { result } = await runAgent({
+ frames: [Buffer.concat([execRequestFrame(2), textFrame("late")])],
+ stream: true,
+ });
+
+ const body = await result.response.text();
+ expect(body).toContain("unsupported IDE tool");
+ expect(body).not.toContain("late");
+ });
+
+ it("returns a non-200 error body for an unsupported exec request when not streaming", async () => {
+ const { result } = await runAgent({
+ frames: [execRequestFrame(11)],
+ stream: false,
+ });
+
+ expect(result.response.status).not.toBe(200);
+ const payload = await result.response.json();
+ expect(payload.error.message).toContain("unsupported IDE tool");
+ });
+});
diff --git a/tests/unit/db-concurrent.test.js b/tests/unit/db-concurrent.test.js
index c00b77b9..cfbe80d9 100644
--- a/tests/unit/db-concurrent.test.js
+++ b/tests/unit/db-concurrent.test.js
@@ -8,6 +8,7 @@ import { describe, it, expect, beforeAll, afterAll, vi } from "vitest";
const originalDataDir = process.env.DATA_DIR;
let tempDir;
let db;
+let adminOwnerId;
beforeAll(async () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-concurrent-"));
@@ -15,6 +16,8 @@ beforeAll(async () => {
vi.resetModules();
db = await import("@/lib/db/index.js");
await db.initDb();
+ const admin = await db.createUser({ username: "concurrency-admin", password: "password", role: "admin" });
+ adminOwnerId = admin.id;
});
afterAll(() => {
@@ -102,6 +105,7 @@ describe("DB Concurrency — atomic safety", () => {
const conn = await db.createProviderConnection({
provider: "oauth-test", authType: "oauth", email: "x@y.com",
accessToken: "initial", refreshToken: "rt-initial",
+ ownerId: adminOwnerId,
});
// 20 parallel updates each with a unique field
diff --git a/tests/unit/db-sqlite-vs-lowdb.test.js b/tests/unit/db-sqlite-vs-lowdb.test.js
index c7326ad9..65b52c71 100644
--- a/tests/unit/db-sqlite-vs-lowdb.test.js
+++ b/tests/unit/db-sqlite-vs-lowdb.test.js
@@ -8,6 +8,7 @@ import { describe, it, expect, beforeAll, afterAll, vi } from "vitest";
const originalDataDir = process.env.DATA_DIR;
let tempDir;
let sqliteDb;
+let adminOwnerId;
beforeAll(async () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-db-compare-"));
@@ -15,6 +16,8 @@ beforeAll(async () => {
vi.resetModules();
sqliteDb = await import("@/lib/db/index.js");
await sqliteDb.initDb();
+ const admin = await sqliteDb.createUser({ username: "db-parity-admin", password: "password", role: "admin" });
+ adminOwnerId = admin.id;
});
afterAll(() => {
@@ -76,9 +79,9 @@ describe("DB SQLite layer — public API parity", () => {
});
it("providerConnections: CRUD + reorder by priority", async () => {
- const c1 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "a", apiKey: "k1" });
- const c2 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "b", apiKey: "k2" });
- const c3 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "c", apiKey: "k3" });
+ const c1 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "a", apiKey: "k1", ownerId: adminOwnerId });
+ const c2 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "b", apiKey: "k2", ownerId: adminOwnerId });
+ const c3 = await sqliteDb.createProviderConnection({ provider: "test", authType: "apikey", name: "c", apiKey: "k3", ownerId: adminOwnerId });
const list = await sqliteDb.getProviderConnections({ provider: "test" });
expect(list).toHaveLength(3);
@@ -103,6 +106,7 @@ describe("DB SQLite layer — public API parity", () => {
provider: "p2", authType: "oauth", email: "x@y.com",
accessToken: "tok", refreshToken: "rtok", expiresAt: 12345,
providerSpecificData: { foo: "bar" },
+ ownerId: adminOwnerId,
});
const back = await sqliteDb.getProviderConnectionById(c.id);
expect(back.accessToken).toBe("tok");
@@ -112,8 +116,8 @@ describe("DB SQLite layer — public API parity", () => {
});
it("providerConnections: scopes retrieval and lookup to connection owner", async () => {
- const ownerOne = await sqliteDb.createUser({ username: "connection-owner-one", password: "password", role: "user" });
- const ownerTwo = await sqliteDb.createUser({ username: "connection-owner-two", password: "password", role: "user" });
+ const ownerOne = await sqliteDb.createUser({ username: "connection-owner-one", password: "password", role: "admin" });
+ const ownerTwo = await sqliteDb.createUser({ username: "connection-owner-two", password: "password", role: "admin" });
const firstConnection = await sqliteDb.createProviderConnection({
provider: "owner-test-one",
authType: "apikey",
@@ -200,6 +204,7 @@ describe("DB SQLite layer — public API parity", () => {
authType: "oauth",
accessToken: "tok",
providerSpecificData: { githubLogin: "octocat" },
+ ownerId: adminOwnerId,
});
expect(c.name).toBe("octocat");
diff --git a/tests/unit/deepseek-usage.test.js b/tests/unit/deepseek-usage.test.js
new file mode 100644
index 00000000..0be26edc
--- /dev/null
+++ b/tests/unit/deepseek-usage.test.js
@@ -0,0 +1,149 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
+ proxyAwareFetch: vi.fn(),
+}));
+
+import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
+import { getUsageForProvider } from "../../open-sse/services/usage.js";
+import {
+ USAGE_SUPPORTED_PROVIDERS,
+ USAGE_APIKEY_PROVIDERS,
+} from "../../src/shared/constants/providers.js";
+import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
+
+const BALANCE_URL = "https://api.deepseek.com/user/balance";
+
+function jsonResponse(body, status = 200) {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "Content-Type": "application/json" },
+ });
+}
+
+const ACTIVE_BALANCE = {
+ is_available: true,
+ balance_infos: [
+ {
+ currency: "USD",
+ total_balance: "12.50",
+ granted_balance: "2.50",
+ topped_up_balance: "10.00",
+ },
+ {
+ currency: "CNY",
+ total_balance: "0.00",
+ granted_balance: "0.00",
+ topped_up_balance: "0.00",
+ },
+ ],
+};
+
+describe("deepseek registry usage flags", () => {
+ it("is listed for apikey quota dashboard", () => {
+ expect(USAGE_SUPPORTED_PROVIDERS).toContain("deepseek");
+ expect(USAGE_APIKEY_PROVIDERS).toContain("deepseek");
+ });
+});
+
+describe("getUsageForProvider(deepseek)", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("GETs /user/balance with Bearer apiKey", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_BALANCE));
+
+ const usage = await getUsageForProvider({
+ provider: "deepseek",
+ apiKey: "sk-ds-test",
+ });
+
+ expect(usage.message).toBeUndefined();
+ expect(usage.plan).toBe("DeepSeek");
+ expect(proxyAwareFetch).toHaveBeenCalledTimes(1);
+ const [url, opts] = proxyAwareFetch.mock.calls[0];
+ expect(url).toBe(BALANCE_URL);
+ expect(opts.method).toBe("GET");
+ expect(opts.headers.Authorization).toBe("Bearer sk-ds-test");
+ });
+
+ it("maps balances without absolute remaining (UI treats remaining as %)", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_BALANCE));
+
+ const usage = await getUsageForProvider({
+ provider: "deepseek",
+ apiKey: "sk-ds-test",
+ });
+
+ expect(usage.quotas["Balance (USD)"]).toMatchObject({
+ used: 0,
+ total: 12.5,
+ remainingPercentage: 100,
+ });
+ expect(usage.quotas["Balance (USD)"].remaining).toBeUndefined();
+ // Zero CNY still listed so user sees currency row
+ expect(usage.quotas["Balance (CNY)"]).toMatchObject({
+ used: 0,
+ total: 0,
+ remainingPercentage: 0,
+ });
+ });
+
+ it("marks plan unavailable when is_available false", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(
+ jsonResponse({
+ is_available: false,
+ balance_infos: [
+ {
+ currency: "USD",
+ total_balance: "0",
+ granted_balance: "0",
+ topped_up_balance: "0",
+ },
+ ],
+ }),
+ );
+
+ const usage = await getUsageForProvider({
+ provider: "deepseek",
+ apiKey: "sk-ds-test",
+ });
+
+ expect(usage.plan).toMatch(/insufficient|unavailable/i);
+ expect(usage.quotas["Balance (USD)"].remainingPercentage).toBe(0);
+ });
+
+ it("returns message on missing key / 401", async () => {
+ const missing = await getUsageForProvider({ provider: "deepseek" });
+ expect(missing.message).toMatch(/api key/i);
+ expect(proxyAwareFetch).not.toHaveBeenCalled();
+
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "no" }, 401));
+ const auth = await getUsageForProvider({
+ provider: "deepseek",
+ apiKey: "bad",
+ });
+ expect(auth.message).toMatch(/auth|key|401/i);
+ });
+});
+
+describe("parseQuotaData(deepseek)", () => {
+ it("forwards remainingPercentage for balance rows", () => {
+ const rows = parseQuotaData("deepseek", {
+ plan: "DeepSeek",
+ quotas: {
+ "Balance (USD)": {
+ used: 0,
+ total: 12.5,
+ remainingPercentage: 100,
+ },
+ },
+ });
+ expect(rows[0]).toMatchObject({
+ name: "Balance (USD)",
+ total: 12.5,
+ remainingPercentage: 100,
+ });
+ });
+});
diff --git a/tests/unit/devin-cli-executor.test.js b/tests/unit/devin-cli-executor.test.js
new file mode 100644
index 00000000..5fe37a4a
--- /dev/null
+++ b/tests/unit/devin-cli-executor.test.js
@@ -0,0 +1,437 @@
+import { describe, it, expect, vi } from "vitest";
+import { EventEmitter } from "node:events";
+import os from "node:os";
+
+// `vi.hoisted` runs before the mocked module is evaluated, so the factory can
+// safely reference the mock fn.
+const { spawnMock } = vi.hoisted(() => ({ spawnMock: vi.fn() }));
+
+vi.mock("node:child_process", () => ({
+ spawn: (...args) => spawnMock(...args),
+}));
+
+const { default: DevinCliExecutor } = await import("open-sse/executors/devin-cli.js");
+
+// Fake devin ACP subprocess. Mirrors the real CLI's session/new validation:
+// it requires `mcpServers` to be an array, otherwise returns -32602 — this is
+// the exact error the dashboard "test" button hit ("Invalid params").
+function makeFakeChild() {
+ const child = new EventEmitter();
+ child.writes = [];
+ child.stdin = new EventEmitter();
+ child.stdin.destroyed = false;
+ child.stdin.write = (data) => {
+ child.writes.push(String(data));
+ try {
+ const msg = JSON.parse(String(data).trim());
+ handle(msg);
+ } catch {
+ /* ignore */
+ }
+ return true;
+ };
+ child.stdin.end = () => {
+ child.stdin.destroyed = true;
+ };
+ child.stdout = new EventEmitter();
+ child.stderr = new EventEmitter();
+ child.killed = false;
+ child.kill = () => {
+ child.killed = true;
+ };
+
+ const send = (obj) =>
+ child.stdout.emit("data", Buffer.from(JSON.stringify(obj) + "\n"));
+
+ function handle(msg) {
+ if (msg.method === "initialize") {
+ send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: 1 } });
+ } else if (msg.method === "session/new") {
+ // Mirror devin 3000.2.x: `mcpServers` is a required sequence.
+ if (Array.isArray(msg.params && msg.params.mcpServers)) {
+ send({ jsonrpc: "2.0", id: msg.id, result: { sessionId: "fake-session" } });
+ } else if (!msg.params || msg.params.mcpServers === undefined) {
+ send({
+ jsonrpc: "2.0",
+ id: msg.id,
+ error: { code: -32602, message: "Invalid params", data: { error: "missing field `mcpServers`" } },
+ });
+ } else {
+ send({
+ jsonrpc: "2.0",
+ id: msg.id,
+ error: { code: -32602, message: "Invalid params", data: { error: "invalid type: map, expected a sequence" } },
+ });
+ }
+ } else if (msg.method === "session/prompt") {
+ // devin 3000.2.x requires `prompt` (a sequence), not `content`.
+ if (Array.isArray(msg.params && msg.params.prompt)) {
+ // Agent requests permission to run a tool before replying.
+ send({
+ jsonrpc: "2.0",
+ id: 777,
+ method: "session/request_permission",
+ params: {
+ sessionId: "fake-session",
+ options: [
+ { optionId: "allow-once", name: "Allow once", kind: "allow_once" },
+ { optionId: "reject-once", name: "Reject", kind: "reject_once" },
+ ],
+ },
+ });
+ // New ACP shape: streaming via session/update with params.update.sessionUpdate.
+ send({
+ jsonrpc: "2.0",
+ method: "session/update",
+ params: { sessionId: "fake-session", update: { sessionUpdate: "agent_thought_chunk", content: { type: "text", text: "(thinking)" } } },
+ });
+ send({
+ jsonrpc: "2.0",
+ method: "session/update",
+ params: { sessionId: "fake-session", update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "hello world" } } },
+ });
+ // Stop signal: _cognition.ai/agent_stopped notification.
+ send({ jsonrpc: "2.0", method: "_cognition.ai/agent_stopped", params: { cause: "complete" } });
+ } else {
+ send({
+ jsonrpc: "2.0",
+ id: msg.id,
+ error: { code: -32602, message: "Invalid params", data: { error: "missing field `prompt`" } },
+ });
+ }
+ }
+ }
+
+ return child;
+}
+
+async function runExecute(credentials = {}) {
+ const child = makeFakeChild();
+ spawnMock.mockImplementation((bin, args, opts) => {
+ child.bin = bin;
+ child.args = args;
+ child.opts = opts;
+ return child;
+ });
+ const exec = new DevinCliExecutor();
+ const { response } = await exec.execute({
+ model: "swe-1.6-fast",
+ body: { messages: [{ role: "user", content: "hi" }] },
+ credentials,
+ log: { info() {}, debug() {} },
+ });
+ const reader = response.body.getReader();
+ let acc = "";
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ acc += new TextDecoder().decode(value);
+ }
+ return { acc, child };
+}
+
+describe("DevinCliExecutor ACP session/new", () => {
+ it("sends session/new with mcpServers as an array", async () => {
+ const { child } = await runExecute();
+ const writes = child.writes.map((w) => JSON.parse(w.trim()));
+ const newMsg = writes.find((m) => m.method === "session/new");
+ expect(newMsg).toBeTruthy();
+ expect(Array.isArray(newMsg.params.mcpServers)).toBe(true);
+ });
+
+ it("defaults session/new cwd to os.tmpdir when request has no workspace cwd", async () => {
+ const { child } = await runExecute();
+ const writes = child.writes.map((w) => JSON.parse(w.trim()));
+ const newMsg = writes.find((m) => m.method === "session/new");
+ expect(newMsg.params.cwd).toBe(os.tmpdir());
+ });
+
+ it("uses client env context for session/new and spawn", async () => {
+ const child = makeFakeChild();
+ spawnMock.mockImplementation((bin, args, opts) => {
+ child.args = args;
+ child.opts = opts;
+ return child;
+ });
+ const workspace = os.tmpdir(); // known existing absolute dir
+ const exec = new DevinCliExecutor();
+ const { response } = await exec.execute({
+ model: "swe-1.6-fast",
+ body: {
+ messages: [
+ {
+ role: "user",
+ content: `\n ${workspace}\n\nhi`,
+ },
+ ],
+ },
+ credentials: {},
+ log: { info() {}, debug() {} },
+ });
+ const reader = response.body.getReader();
+ while (true) {
+ const { done } = await reader.read();
+ if (done) break;
+ }
+ expect(child.opts.cwd).toBe(workspace);
+ const writes = child.writes.map((w) => JSON.parse(w.trim()));
+ const newMsg = writes.find((m) => m.method === "session/new");
+ expect(newMsg.params.cwd).toBe(workspace);
+ });
+
+ it("sends session/prompt with prompt (not content) as an array", async () => {
+ const { child } = await runExecute();
+ const writes = child.writes.map((w) => JSON.parse(w.trim()));
+ const promptMsg = writes.find((m) => m.method === "session/prompt");
+ expect(promptMsg).toBeTruthy();
+ expect(Array.isArray(promptMsg.params.prompt)).toBe(true);
+ expect(promptMsg.params.content).toBeUndefined();
+ });
+
+ it("completes the prompt without a -32602 Invalid params error", async () => {
+ const { acc } = await runExecute();
+ expect(acc).not.toContain("-32602");
+ expect(acc).not.toContain("Invalid params");
+ expect(acc.toLowerCase()).toContain("hello world");
+ });
+
+ it("emits agent_message_chunk content and skips agent_thought_chunk", async () => {
+ // devin 3000.2.x streams via params.update.sessionUpdate.
+ const { acc } = await runExecute();
+ // Reply text is delivered, finish chunk present, thinking is not surfaced.
+ expect(acc.toLowerCase()).toContain("hello world");
+ expect(acc).toContain("finish_reason");
+ expect(acc.toLowerCase()).not.toContain("(thinking)");
+ expect(acc).toContain("[DONE]");
+ });
+
+ it("spawns the default agent (with built-in tools) by default", async () => {
+ const { child } = await runExecute();
+ expect(child.args).toEqual(["acp"]);
+ });
+
+ it("seeds MCP with tool_result from prior client round-trip", async () => {
+ const fs = await import("node:fs");
+ const child = makeFakeChild();
+ let capturedCfg = null;
+ let capturedPrompt = null;
+ spawnMock.mockImplementation((bin, args, opts) => {
+ child.args = args;
+ child.opts = opts;
+ // Capture config at spawn time (finish() cleans the temp dir).
+ if (opts?.env?.XDG_CONFIG_HOME) {
+ capturedCfg = JSON.parse(
+ fs.readFileSync(opts.env.XDG_CONFIG_HOME + "/devin/config.json", "utf8")
+ );
+ }
+ return child;
+ });
+ const origWrite = child.stdin.write;
+ child.stdin.write = (data) => {
+ const s = String(data);
+ try {
+ const msg = JSON.parse(s.trim());
+ if (msg.method === "session/prompt") {
+ capturedPrompt = msg.params.prompt[0].text;
+ }
+ } catch {
+ /* ignore */
+ }
+ return origWrite.call(child.stdin, data);
+ };
+ const exec = new DevinCliExecutor();
+ const { response } = await exec.execute({
+ model: "swe-1.6-fast",
+ body: {
+ messages: [
+ { role: "user", content: "weather?" },
+ {
+ role: "assistant",
+ content: null,
+ tool_calls: [
+ {
+ id: "call_1",
+ type: "function",
+ function: { name: "get_weather", arguments: '{"city":"Paris"}' },
+ },
+ ],
+ },
+ { role: "tool", tool_call_id: "call_1", content: "28C sunny" },
+ ],
+ tools: [
+ {
+ type: "function",
+ function: {
+ name: "get_weather",
+ parameters: { type: "object", properties: { city: { type: "string" } } },
+ },
+ },
+ ],
+ },
+ credentials: {},
+ log: { info() {}, debug() {} },
+ });
+ const reader = response.body.getReader();
+ while (true) {
+ const { done } = await reader.read();
+ if (done) break;
+ }
+ expect(capturedCfg).toBeTruthy();
+ const results = JSON.parse(capturedCfg.mcpServers.clientTools.env.DEVIN_MCP_RESULTS);
+ expect(results.mcp_get_weather).toBe("28C sunny");
+ expect(capturedPrompt).toContain("get_weather");
+ expect(capturedPrompt).toContain("28C sunny");
+ });
+
+ it("bridges a client-tool MCP call to an OpenAI tool_use", async () => {
+ // Custom fake: on session/prompt, report devin calling our exposed MCP tool.
+ const child = new EventEmitter();
+ child.writes = [];
+ child.stdin = new EventEmitter();
+ child.stdin.destroyed = false;
+ child.stdin.write = (data) => { child.writes.push(String(data)); handle(JSON.parse(String(data).trim())); return true; };
+ child.stdin.end = () => { child.stdin.destroyed = true; };
+ child.stdout = new EventEmitter();
+ child.stderr = new EventEmitter();
+ child.killed = false;
+ child.kill = () => { child.killed = true; };
+ child.args = ["acp"];
+ child.opts = { env: {} };
+ spawnMock.mockReturnValue(child);
+ const send = (o) => child.stdout.emit("data", Buffer.from(JSON.stringify(o) + "\n"));
+ function handle(msg) {
+ if (msg.method === "initialize") send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: 1 } });
+ else if (msg.method === "session/new") send({ jsonrpc: "2.0", id: msg.id, result: { sessionId: "s1" } });
+ else if (msg.method === "session/prompt") {
+ // Mirror real ACP: title on first event, rawInput on a later update.
+ send({
+ jsonrpc: "2.0",
+ method: "session/update",
+ params: { sessionId: "s1", update: { sessionUpdate: "tool_call", toolCallId: "call_abc", title: "Calling mcp_get_weather from clientTools" } },
+ });
+ send({
+ jsonrpc: "2.0",
+ method: "session/update",
+ params: { sessionId: "s1", update: { sessionUpdate: "tool_call_update", toolCallId: "call_abc", rawInput: { city: "Paris" } } },
+ });
+ }
+ }
+
+ const exec = new DevinCliExecutor();
+ const { response } = await exec.execute({
+ model: "swe-1.6-fast",
+ body: {
+ messages: [{ role: "user", content: "weather?" }],
+ tools: [{ type: "function", function: { name: "get_weather", parameters: { type: "object" } } }],
+ },
+ credentials: {},
+ log: { info() {}, debug() {} },
+ });
+ const reader = response.body.getReader();
+ let acc = "";
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ acc += new TextDecoder().decode(value);
+ if (acc.includes("[DONE]")) break;
+ }
+ const tc = JSON.parse(acc.match(/"tool_calls":\[(\{.*?\})\]/)?.[1] ?? "{}");
+ expect(tc.function.name).toBe("get_weather"); // mcp_ prefix stripped, MCP-real untouched
+ expect(tc.id).toBe("call_abc");
+ expect(JSON.parse(tc.function.arguments).city).toBe("Paris");
+ expect(acc).toContain('"finish_reason":"tool_calls"');
+ expect(acc).toContain("[DONE]");
+ });
+
+ it("overrides the agent type via CLI_DEVIN_AGENT_TYPE", async () => {
+ process.env.CLI_DEVIN_AGENT_TYPE = "summarizer";
+ try {
+ const { child } = await runExecute();
+ expect(child.args).toEqual(["acp", "--agent-type", "summarizer"]);
+ } finally {
+ delete process.env.CLI_DEVIN_AGENT_TYPE;
+ }
+ });
+
+ it("sets DEVIN_PERMISSION_MODE=bypass so tool calls don't hang on permission prompts", async () => {
+ const { child } = await runExecute();
+ expect(child.opts.env.DEVIN_PERMISSION_MODE).toBe("bypass");
+ });
+
+ it("does not inject WINDSURF_API_KEY — devin-cli uses stored CLI creds (devin auth login)", async () => {
+ // Provider is noAuth; devin must fall back to ~/.local/share/devin/credentials.toml.
+ // Injecting a bogus WINDSURF_API_KEY makes devin reject stored creds → -32000.
+ const { child } = await runExecute({ accessToken: "bogus-token", apiKey: "bogus-key" });
+ expect(child.opts.env.WINDSURF_API_KEY).toBeUndefined();
+ });
+
+ it("respects an explicit DEVIN_PERMISSION_MODE override", async () => {
+ process.env.DEVIN_PERMISSION_MODE = "accept-edits";
+ try {
+ const { child } = await runExecute();
+ expect(child.opts.env.DEVIN_PERMISSION_MODE).toBe("accept-edits");
+ } finally {
+ delete process.env.DEVIN_PERMISSION_MODE;
+ }
+ });
+
+ it("auto-approves session/request_permission with the first allow option", async () => {
+ const { child } = await runExecute();
+ const writes = child.writes.map((w) => JSON.parse(w.trim()));
+ const resp = writes.find((m) => m.id === 777 && m.result);
+ expect(resp).toBeTruthy();
+ expect(resp.result.outcome.outcome).toBe("selected");
+ expect(resp.result.outcome.optionId).toBe("allow-once");
+ });
+
+ it("sets XDG_CONFIG_HOME when DEVIN_MCP_SERVERS is provided", async () => {
+ process.env.DEVIN_MCP_SERVERS = JSON.stringify({
+ echo: { command: "/usr/bin/node", args: ["/srv/echo.js"] },
+ });
+ try {
+ const { child } = await runExecute();
+ expect(child.opts.env.XDG_CONFIG_HOME).toBeTruthy();
+ // devin reads $XDG_CONFIG_HOME/devin/config.json (E2E verifies content).
+ } finally {
+ delete process.env.DEVIN_MCP_SERVERS;
+ }
+ });
+
+ it("does not set XDG_CONFIG_HOME when DEVIN_MCP_SERVERS is absent", async () => {
+ const { child } = await runExecute();
+ expect(child.opts.env.XDG_CONFIG_HOME).toBeUndefined();
+ });
+
+ it("exposes body.tools as an MCP server (sets XDG_CONFIG_HOME + writes script)", async () => {
+ const fs = await import("node:fs");
+ const os = await import("node:os");
+ const path = await import("node:path");
+ const child = makeFakeChild();
+ spawnMock.mockImplementation((bin, args, opts) => {
+ child.args = args;
+ child.opts = opts;
+ return child;
+ });
+ const exec = new DevinCliExecutor();
+ const { response } = await exec.execute({
+ model: "swe-1.6-fast",
+ body: {
+ messages: [{ role: "user", content: "weather?" }],
+ tools: [
+ { type: "function", function: { name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } } } } },
+ ],
+ },
+ credentials: {},
+ log: { info() {}, debug() {} },
+ });
+ const reader = response.body.getReader();
+ await reader.read();
+ // XDG_CONFIG_HOME set so devin loads the generated config.
+ expect(child.opts.env.XDG_CONFIG_HOME).toBeTruthy();
+ // Static MCP bridge script written to disk.
+ const scriptPath = path.join(os.tmpdir(), "9router-devin-client-tools.mjs");
+ expect(fs.existsSync(scriptPath)).toBe(true);
+ expect(fs.readFileSync(scriptPath, "utf8")).toContain("clientTools");
+ expect(fs.readFileSync(scriptPath, "utf8")).toContain("DEVIN_MCP_TOOLS");
+ });
+});
diff --git a/tests/unit/embedding-usage-persistence.test.js b/tests/unit/embedding-usage-persistence.test.js
new file mode 100644
index 00000000..9328b42e
--- /dev/null
+++ b/tests/unit/embedding-usage-persistence.test.js
@@ -0,0 +1,92 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ handleEmbeddingsCore: vi.fn(),
+ saveRequestUsage: vi.fn(),
+}));
+
+vi.mock("../../src/sse/services/auth.js", () => ({
+ getProviderCredentials: async () => ({
+ apiKey: "provider-secret",
+ connectionId: "connection-a",
+ connectionName: "Provider A",
+ }),
+ markAccountUnavailable: vi.fn(),
+ clearAccountError: vi.fn(),
+ extractApiKey: () => "client-key",
+ getApiKeyOwnerId: async () => "user-a",
+ isValidApiKey: vi.fn(),
+}));
+vi.mock("@/lib/localDb", () => ({ getSettings: async () => ({ requireApiKey: false }) }));
+vi.mock("../../src/sse/services/model.js", () => ({
+ getModelInfo: async () => ({ provider: "openai", model: "text-embedding-3-small" }),
+}));
+vi.mock("../../open-sse/handlers/embeddingsCore.js", () => ({
+ handleEmbeddingsCore: mocks.handleEmbeddingsCore,
+}));
+vi.mock("../../open-sse/utils/error.js", () => ({
+ errorResponse: (status, message) => Response.json({ error: message }, { status }),
+ unavailableResponse: (status, message) => Response.json({ error: message }, { status }),
+}));
+vi.mock("../../src/sse/utils/logger.js", () => ({
+ request: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), info: vi.fn(), maskKey: vi.fn(),
+}));
+vi.mock("../../src/sse/services/tokenRefresh.js", () => ({
+ updateProviderCredentials: vi.fn(),
+ checkAndRefreshToken: async (_provider, credentials) => credentials,
+}));
+vi.mock("@/lib/usageDb.js", () => ({ saveRequestUsage: mocks.saveRequestUsage }));
+
+import { handleEmbeddings } from "../../src/sse/handlers/embeddings.js";
+
+describe("embedding usage persistence", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.saveRequestUsage.mockResolvedValue(undefined);
+ mocks.handleEmbeddingsCore.mockResolvedValue({
+ success: true,
+ usage: { prompt_tokens: 12, total_tokens: 12 },
+ response: Response.json({ data: [] }),
+ });
+ });
+
+ it("records exact provider usage for successful embedding requests", async () => {
+ await handleEmbeddings(new Request("http://localhost/v1/embeddings", {
+ method: "POST",
+ body: JSON.stringify({ model: "openai/text-embedding-3-small", input: "hello" }),
+ }));
+
+ expect(mocks.saveRequestUsage).toHaveBeenCalledWith(expect.objectContaining({
+ provider: "openai",
+ model: "text-embedding-3-small",
+ connectionId: "connection-a",
+ apiKey: "client-key",
+ endpoint: "/v1/embeddings",
+ status: "success",
+ tokens: { prompt_tokens: 12, completion_tokens: 0, total_tokens: 12 },
+ }));
+ });
+
+ it.each([
+ null,
+ {},
+ { prompt_tokens: 0, total_tokens: 0 },
+ { prompt_tokens: "12", total_tokens: 12 },
+ { prompt_tokens: 12, total_tokens: 13 },
+ { prompt_tokens: 12, completion_tokens: 1, total_tokens: 12 },
+ { prompt_tokens: 12, total_tokens: 12, estimated: true },
+ ])("does not record inexact usage %#", async (usage) => {
+ mocks.handleEmbeddingsCore.mockResolvedValue({
+ success: true,
+ usage,
+ response: Response.json({ data: [] }),
+ });
+
+ await handleEmbeddings(new Request("http://localhost/v1/embeddings", {
+ method: "POST",
+ body: JSON.stringify({ model: "openai/text-embedding-3-small", input: "hello" }),
+ }));
+
+ expect(mocks.saveRequestUsage).not.toHaveBeenCalled();
+ });
+});
diff --git a/tests/unit/fetch-success-clears-account.test.js b/tests/unit/fetch-success-clears-account.test.js
new file mode 100644
index 00000000..1002ceb2
--- /dev/null
+++ b/tests/unit/fetch-success-clears-account.test.js
@@ -0,0 +1,92 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ getProviderCredentials: vi.fn(),
+ markAccountUnavailable: vi.fn(),
+ clearAccountError: vi.fn(),
+ extractApiKey: vi.fn(() => null),
+ isValidApiKey: vi.fn(),
+ getSettings: vi.fn(),
+ getCombos: vi.fn(),
+ handleFetchCore: vi.fn(),
+ checkAndRefreshToken: vi.fn(),
+}));
+
+vi.mock("@/sse/services/auth.js", () => ({
+ getProviderCredentials: mocks.getProviderCredentials,
+ markAccountUnavailable: mocks.markAccountUnavailable,
+ clearAccountError: mocks.clearAccountError,
+ extractApiKey: mocks.extractApiKey,
+ getApiKeyOwnerId: vi.fn(async () => null),
+ isValidApiKey: mocks.isValidApiKey,
+}));
+
+vi.mock("@/lib/localDb", () => ({
+ getSettings: mocks.getSettings,
+ getCombos: mocks.getCombos,
+}));
+
+vi.mock("open-sse/handlers/fetch/index.js", () => ({
+ handleFetchCore: mocks.handleFetchCore,
+}));
+
+vi.mock("@/sse/services/tokenRefresh.js", () => ({
+ checkAndRefreshToken: mocks.checkAndRefreshToken,
+ updateProviderCredentials: vi.fn(),
+}));
+
+vi.mock("@/sse/utils/logger.js", () => ({
+ request: vi.fn(),
+ info: vi.fn(),
+ debug: vi.fn(),
+ warn: vi.fn(),
+ error: vi.fn(),
+ maskKey: vi.fn(() => "masked"),
+}));
+
+vi.mock("@/shared/utils/ssrfGuard.js", () => ({
+ assertPublicUrl: vi.fn(),
+}));
+
+import { handleFetch } from "@/sse/handlers/fetch.js";
+
+describe("web fetch account state", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.getSettings.mockResolvedValue({ requireApiKey: false });
+ mocks.getCombos.mockResolvedValue([]);
+ mocks.getProviderCredentials.mockResolvedValue({
+ apiKey: "jina-test-key",
+ connectionId: "jina-connection",
+ connectionName: "Jina Test",
+ _connection: {
+ testStatus: "unavailable",
+ lastError: "old error",
+ modelLock___all: "2026-01-01T00:00:00.000Z",
+ },
+ });
+ mocks.checkAndRefreshToken.mockImplementation(async (_provider, credentials) => credentials);
+ mocks.handleFetchCore.mockResolvedValue({
+ success: true,
+ data: { provider: "jina-reader", content: { text: "ok" } },
+ });
+ });
+
+ it("clears a stale provider lock after a successful fetch", async () => {
+ const response = await handleFetch(new Request("http://localhost/v1/web/fetch", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider: "jina-reader",
+ url: "https://example.com/article",
+ }),
+ }));
+
+ expect(response.status).toBe(200);
+ expect(mocks.clearAccountError).toHaveBeenCalledWith(
+ "jina-connection",
+ expect.objectContaining({ connectionName: "Jina Test" }),
+ );
+ expect(mocks.markAccountUnavailable).not.toHaveBeenCalled();
+ });
+});
diff --git a/tests/unit/gemini-36-integration.test.js b/tests/unit/gemini-36-integration.test.js
new file mode 100644
index 00000000..e401f923
--- /dev/null
+++ b/tests/unit/gemini-36-integration.test.js
@@ -0,0 +1,144 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { createRequire } from "node:module";
+import { readFileSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname, join } from "node:path";
+
+import { getModelUpstreamId } from "../../open-sse/config/providerModels.js";
+import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
+import { applyThinking, stripThinkingSuffix } from "../../open-sse/translator/concerns/thinkingUnified.js";
+import antigravity from "../../open-sse/providers/registry/antigravity.js";
+import geminiCli from "../../open-sse/providers/registry/gemini-cli.js";
+import gemini from "../../open-sse/providers/registry/gemini.js";
+import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
+import {
+ getProjectIdForConnection,
+ removeConnection,
+} from "../../open-sse/services/projectId.js";
+
+const require = createRequire(import.meta.url);
+const mitmConfig = require("../../src/mitm/config.js");
+const here = dirname(fileURLToPath(import.meta.url));
+
+function cloudCodeResponse(projectId) {
+ return {
+ ok: true,
+ json: async () => ({ cloudaicompanionProject: { id: projectId } }),
+ };
+}
+
+afterEach(() => {
+ vi.restoreAllMocks();
+});
+
+describe("Gemini Cloud Code endpoint isolation", () => {
+ it("keeps Gemini CLI on the official cloudcode host", async () => {
+ const connectionId = "gemini-cli-endpoint-test";
+ const fetchMock = vi.fn(async () => cloudCodeResponse("gemini-project"));
+ vi.stubGlobal("fetch", fetchMock);
+
+ await getProjectIdForConnection(connectionId, "token", "gemini-cli");
+
+ expect(fetchMock).toHaveBeenCalledWith(
+ "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
+ expect.objectContaining({ method: "POST" })
+ );
+ expect(geminiCli.transport.baseUrl).toBe("https://cloudcode-pa.googleapis.com/v1internal");
+ removeConnection(connectionId);
+ });
+
+ it("uses the prod cloudcode host for Antigravity discovery but daily for chat", async () => {
+ const connectionId = "antigravity-endpoint-test";
+ const fetchMock = vi.fn(async () => cloudCodeResponse("antigravity-project"));
+ vi.stubGlobal("fetch", fetchMock);
+
+ await getProjectIdForConnection(connectionId, "token", "antigravity");
+
+ // Discovery (loadCodeAssist) on PROD — daily host rejects auth/onboarding calls.
+ expect(fetchMock).toHaveBeenCalledWith(
+ "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
+ expect.objectContaining({ method: "POST" })
+ );
+ // Chat transport still uses the daily host to bypass prod 429.
+ expect(antigravity.transport.baseUrls).toEqual(["https://daily-cloudcode-pa.googleapis.com"]);
+ removeConnection(connectionId);
+ });
+});
+
+describe("Gemini 3.6 Antigravity tiers", () => {
+ it.each(["high", "medium", "low"])(
+ "maps the %s tier to the shared upstream model with matching thinking level",
+ (tier) => {
+ const publicModel = `gemini-3.6-flash-${tier}`;
+ const upstreamModel = getModelUpstreamId("ag", publicModel);
+ const body = {
+ model: stripThinkingSuffix(upstreamModel),
+ request: {
+ contents: [{ role: "user", parts: [{ text: "hello" }] }],
+ generationConfig: {},
+ },
+ };
+
+ applyThinking("antigravity", upstreamModel, body, "antigravity");
+ const finalBody = new AntigravityExecutor().transformRequest(
+ publicModel,
+ body,
+ true,
+ { projectId: "project", connectionId: "connection" }
+ );
+
+ expect(upstreamModel).toBe(`gemini-3.6-flash-tiered(${tier})`);
+ expect(finalBody.model).toBe("gemini-3.6-flash-tiered");
+ expect(finalBody.request.generationConfig.thinkingConfig).toEqual({
+ thinkingLevel: tier,
+ includeThoughts: true,
+ });
+ }
+ );
+});
+
+describe("Gemini 3.6 MITM model extraction", () => {
+ it("exports the model extractor from the side-effect-free MITM config module", () => {
+ expect(mitmConfig.extractModel).toBeTypeOf("function");
+ });
+
+ it.each(["high", "medium", "low"])("extracts the %s thinking tier", (tier) => {
+ const body = Buffer.from(JSON.stringify({
+ request: { generationConfig: { thinkingConfig: { thinkingLevel: tier } } },
+ }));
+
+ expect(mitmConfig.extractModel(
+ "/v1internal/models/gemini-3.6-flash-tiered:streamGenerateContent",
+ body
+ )).toBe(`gemini-3.6-flash-${tier}`);
+ });
+
+ it("defaults invalid or missing thinking levels to medium", () => {
+ const body = Buffer.from(JSON.stringify({
+ request: { generationConfig: { thinkingConfig: { thinkingLevel: "unknown" } } },
+ }));
+
+ expect(mitmConfig.extractModel(
+ "/v1internal/models/gemini-3.6-flash-tiered:streamGenerateContent",
+ body
+ )).toBe("gemini-3.6-flash-medium");
+ });
+});
+
+describe("Gemini 3.6 catalogs and pricing", () => {
+ it("exposes the direct Gemini API models and their pricing", () => {
+ const ids = gemini.models.map((model) => model.id);
+ expect(ids).toContain("gemini-3.6-flash");
+ expect(ids).toContain("gemini-3.5-flash-lite");
+ expect(MODEL_PRICING["gemini-3.6-flash"]).toMatchObject({ input: 1.5, output: 7.5 });
+ expect(MODEL_PRICING["gemini-3.5-flash-lite"]).toMatchObject({ input: 0.3, output: 2.5 });
+ });
+
+ it("keeps the standalone CLI Gemini catalog synchronized", () => {
+ const source = readFileSync(join(here, "../../cli/src/cli/menus/providers.js"), "utf8");
+ const geminiCatalog = source.match(/\n gemini: \[([\s\S]*?)\n \],/)?.[1] || "";
+
+ expect(geminiCatalog).toContain("gemini-3.6-flash");
+ expect(geminiCatalog).toContain("gemini-3.5-flash-lite");
+ });
+});
diff --git a/tests/unit/grok-cli-quota-frame.test.js b/tests/unit/grok-cli-quota-frame.test.js
new file mode 100644
index 00000000..cd86ab4a
--- /dev/null
+++ b/tests/unit/grok-cli-quota-frame.test.js
@@ -0,0 +1,229 @@
+import { describe, it, expect } from "vitest";
+import {
+ decodeGrokCreditsFrame,
+ probeFrameHeader,
+} from "../../open-sse/services/usage/grokCliQuotaFrame.js";
+
+/**
+ * Minimal protobuf encoder for fixtures — real GetGrokCreditsConfig wire shape
+ * (nested field 1 / fixed32 ratio / Timestamp reset + optional trailer 0x80).
+ */
+
+function encodeVarint(value) {
+ const bytes = [];
+ let v = BigInt(value);
+ do {
+ let byte = Number(v & 0x7fn);
+ v >>= 7n;
+ if (v !== 0n) byte |= 0x80;
+ bytes.push(byte);
+ } while (v !== 0n);
+ return Buffer.from(bytes);
+}
+
+function encodeTag(fieldNumber, wireType) {
+ return encodeVarint((fieldNumber << 3) | wireType);
+}
+
+function encodeFixed32Field(fieldNumber, value) {
+ const body = Buffer.alloc(4);
+ body.writeFloatLE(value, 0);
+ return Buffer.concat([encodeTag(fieldNumber, 5), body]);
+}
+
+function encodeLengthDelimited(fieldNumber, body) {
+ return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]);
+}
+
+function encodeVarintField(fieldNumber, value) {
+ return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]);
+}
+
+function encodeTimestampField(fieldNumber, seconds, nanos) {
+ const parts = [];
+ if (seconds !== 0) parts.push(encodeVarintField(1, seconds));
+ if (nanos !== 0) parts.push(encodeVarintField(2, nanos));
+ return encodeLengthDelimited(fieldNumber, Buffer.concat(parts));
+}
+
+function encodeCreditsInfo(shape) {
+ const parts = [];
+ if (shape.usageRatio !== undefined) parts.push(encodeFixed32Field(1, shape.usageRatio));
+ if (shape.asOfSeconds !== undefined) {
+ parts.push(encodeTimestampField(4, shape.asOfSeconds, shape.asOfNanos ?? 0));
+ }
+ if (shape.resetSeconds !== undefined) {
+ parts.push(encodeTimestampField(5, shape.resetSeconds, shape.resetNanos ?? 0));
+ }
+ return Buffer.concat(parts);
+}
+
+function encodeTopLevelMessage(creditsInfo) {
+ return encodeLengthDelimited(1, creditsInfo);
+}
+
+function frameData(payload) {
+ const header = Buffer.alloc(5);
+ header[0] = 0x00;
+ header.writeUInt32BE(payload.length, 1);
+ return Buffer.concat([header, payload]);
+}
+
+function frameTrailer(statusText = "grpc-status:0\r\n") {
+ const body = Buffer.from(statusText, "utf8");
+ const header = Buffer.alloc(5);
+ header[0] = 0x80;
+ header.writeUInt32BE(body.length, 1);
+ return Buffer.concat([header, body]);
+}
+
+const REAL_USAGE_RATIO = 1.0;
+const REAL_ASOF_SECONDS = 1784221140;
+const REAL_ASOF_NANOS = 867850000;
+const REAL_RESET_SECONDS = 1784825940;
+const REAL_RESET_NANOS = 867850000;
+const PERCENT_TOLERANCE = 1e-4;
+
+function isoFromEpoch(seconds, nanos) {
+ return new Date(seconds * 1000 + Math.round(nanos / 1_000_000)).toISOString();
+}
+
+describe("decodeGrokCreditsFrame", () => {
+ it("decodes real GetGrokCreditsConfig shape (nested, fixed32, Timestamp, trailer)", () => {
+ const creditsInfo = encodeCreditsInfo({
+ usageRatio: REAL_USAGE_RATIO,
+ asOfSeconds: REAL_ASOF_SECONDS,
+ asOfNanos: REAL_ASOF_NANOS,
+ resetSeconds: REAL_RESET_SECONDS,
+ resetNanos: REAL_RESET_NANOS,
+ });
+ const buffer = Buffer.concat([frameData(encodeTopLevelMessage(creditsInfo)), frameTrailer()]);
+
+ const result = decodeGrokCreditsFrame(buffer);
+ expect(result).toBeTruthy();
+ expect(result.percentUsed).toBe(100);
+ expect(result.resetAt).toBe(isoFromEpoch(REAL_RESET_SECONDS, REAL_RESET_NANOS));
+ });
+
+ it("ignores trailing gRPC-web trailer frame (flag 0x80)", () => {
+ const creditsInfo = encodeCreditsInfo({
+ usageRatio: 0.5,
+ resetSeconds: REAL_RESET_SECONDS,
+ resetNanos: 0,
+ });
+ const topMessage = encodeTopLevelMessage(creditsInfo);
+ const withoutTrailer = frameData(topMessage);
+ const withTrailer = Buffer.concat([frameData(topMessage), frameTrailer()]);
+
+ const a = decodeGrokCreditsFrame(withoutTrailer);
+ const b = decodeGrokCreditsFrame(withTrailer);
+ expect(a).toBeTruthy();
+ expect(b).toBeTruthy();
+ expect(b.percentUsed).toBe(a.percentUsed);
+ expect(b.resetAt).toBe(a.resetAt);
+ expect(b.percentUsed).toBe(50);
+ });
+
+ it("decodes raw unframed protobuf payload", () => {
+ const creditsInfo = encodeCreditsInfo({
+ usageRatio: 0.75,
+ resetSeconds: REAL_RESET_SECONDS,
+ resetNanos: REAL_RESET_NANOS,
+ });
+ const payload = encodeTopLevelMessage(creditsInfo);
+ expect(probeFrameHeader(payload)).toBeNull();
+
+ const result = decodeGrokCreditsFrame(payload);
+ expect(result).toBeTruthy();
+ expect(Math.abs(result.percentUsed - 75)).toBeLessThan(PERCENT_TOLERANCE);
+ expect(result.resetAt).toBe(isoFromEpoch(REAL_RESET_SECONDS, REAL_RESET_NANOS));
+ });
+
+ it("treats omitted usage-ratio as 0% (proto3 default)", () => {
+ const creditsInfo = encodeCreditsInfo({
+ resetSeconds: REAL_RESET_SECONDS,
+ resetNanos: REAL_RESET_NANOS,
+ });
+ const result = decodeGrokCreditsFrame(frameData(encodeTopLevelMessage(creditsInfo)));
+ expect(result).toBeTruthy();
+ expect(result.percentUsed).toBe(0);
+ expect(result.resetAt).toBe(isoFromEpoch(REAL_RESET_SECONDS, REAL_RESET_NANOS));
+ });
+
+ it("clamps usage ratio above 1.0 to percentUsed 100", () => {
+ const creditsInfo = encodeCreditsInfo({ usageRatio: 1.5 });
+ const result = decodeGrokCreditsFrame(frameData(encodeTopLevelMessage(creditsInfo)));
+ expect(result).toBeTruthy();
+ expect(result.percentUsed).toBe(100);
+ });
+
+ it("returns null for negative usage ratio", () => {
+ const creditsInfo = encodeCreditsInfo({ usageRatio: -0.1 });
+ expect(decodeGrokCreditsFrame(frameData(encodeTopLevelMessage(creditsInfo)))).toBeNull();
+ });
+
+ it("returns null when top-level field 1 is not length-delimited", () => {
+ expect(decodeGrokCreditsFrame(frameData(encodeVarintField(1, 42)))).toBeNull();
+ });
+
+ it("returns null when nested usage-ratio has unexpected wire type", () => {
+ const creditsInfo = encodeLengthDelimited(1, Buffer.from("not-a-float", "utf8"));
+ expect(decodeGrokCreditsFrame(frameData(encodeTopLevelMessage(creditsInfo)))).toBeNull();
+ });
+
+ it("returns null when top-level has no field 1", () => {
+ expect(decodeGrokCreditsFrame(frameData(encodeVarintField(9, 1)))).toBeNull();
+ });
+
+ it("returns null for truncated buffer", () => {
+ const creditsInfo = encodeCreditsInfo({
+ usageRatio: 0.5,
+ resetSeconds: REAL_RESET_SECONDS,
+ resetNanos: REAL_RESET_NANOS,
+ });
+ const buffer = frameData(encodeTopLevelMessage(creditsInfo));
+ expect(decodeGrokCreditsFrame(buffer.subarray(0, buffer.length - 3))).toBeNull();
+ });
+
+ it("returns null for trailer-only body", () => {
+ expect(decodeGrokCreditsFrame(frameTrailer())).toBeNull();
+ });
+
+ it("returns null for empty buffer", () => {
+ expect(decodeGrokCreditsFrame(Buffer.alloc(0))).toBeNull();
+ });
+});
+
+describe("probeFrameHeader", () => {
+ it("rejects declared length that exceeds body", () => {
+ const header = Buffer.alloc(5);
+ header[0] = 0x00;
+ header.writeUInt32BE(9999, 1);
+ expect(probeFrameHeader(Buffer.concat([header, Buffer.from([0x01, 0x02])]))).toBeNull();
+ });
+
+ it("rejects invalid compression flag", () => {
+ const header = Buffer.alloc(5);
+ header[0] = 0x07;
+ expect(probeFrameHeader(header)).toBeNull();
+ });
+
+ it("accepts trailer frame header (flag 0x80)", () => {
+ const result = probeFrameHeader(frameTrailer());
+ expect(result).toBeTruthy();
+ expect(result.flag).toBe(0x80);
+ });
+
+ it("reads frame header at non-zero offset", () => {
+ const creditsInfo = encodeCreditsInfo({ usageRatio: 0.5 });
+ const buffer = Buffer.concat([
+ frameData(encodeTopLevelMessage(creditsInfo)),
+ frameTrailer(),
+ ]);
+ const first = probeFrameHeader(buffer);
+ expect(first).toBeTruthy();
+ const second = probeFrameHeader(buffer, first.payloadStart + first.payloadLength);
+ expect(second).toBeTruthy();
+ expect(second.flag).toBe(0x80);
+ });
+});
diff --git a/tests/unit/grok-cli-usage.test.js b/tests/unit/grok-cli-usage.test.js
index 0c52a2cd..a0e28a5c 100644
--- a/tests/unit/grok-cli-usage.test.js
+++ b/tests/unit/grok-cli-usage.test.js
@@ -113,6 +113,43 @@ describe("parseGrokCliBilling", () => {
expect(parsed.exhausted).toBe(false);
});
+ it("maps creditUsagePercent to a single Weekly SuperGrok bar (not productUsage)", () => {
+ const parsed = parseGrokCliBilling(
+ {
+ config: {
+ currentPeriod: {
+ type: "USAGE_PERIOD_TYPE_WEEKLY",
+ start: "2026-07-17T12:42:26.494595+00:00",
+ end: "2026-07-24T12:42:26.494595+00:00",
+ },
+ creditUsagePercent: 99.0,
+ onDemandCap: { val: 0 },
+ onDemandUsed: { val: 0 },
+ productUsage: [
+ { product: "GrokBuild", usagePercent: 97.0 },
+ { product: "GrokImagine", usagePercent: 2.0 },
+ ],
+ isUnifiedBillingUser: true,
+ prepaidBalance: { val: 0 },
+ billingPeriodStart: "2026-07-17T12:42:26.494595+00:00",
+ billingPeriodEnd: "2026-07-24T12:42:26.494595+00:00",
+ },
+ },
+ { subscriptionTier: "XPremiumPlus", hasGrokCodeAccess: true },
+ );
+ // Single shared-pool bar from creditUsagePercent
+ expect(parsed.quotas["Weekly SuperGrok"]).toMatchObject({
+ used: 99,
+ total: 100,
+ remainingPercentage: 1,
+ resetAt: "2026-07-24T12:42:26.494Z",
+ unlimited: false,
+ });
+ // productUsage must NOT become independent quota bars
+ expect(Object.keys(parsed.quotas)).toEqual(["Weekly SuperGrok"]);
+ expect(parsed.exhausted).toBe(false);
+ });
+
it("maps current monthly fields and snake-case subscription tier", () => {
const parsed = parseGrokCliBilling({
monthlyLimit: { val: 1000 },
@@ -132,6 +169,67 @@ describe("parseGrokCliBilling", () => {
});
});
+function encodeVarint(value) {
+ const bytes = [];
+ let v = BigInt(value);
+ do {
+ let byte = Number(v & 0x7fn);
+ v >>= 7n;
+ if (v !== 0n) byte |= 0x80;
+ bytes.push(byte);
+ } while (v !== 0n);
+ return Buffer.from(bytes);
+}
+
+function encodeTag(fieldNumber, wireType) {
+ return encodeVarint((fieldNumber << 3) | wireType);
+}
+
+function encodeFixed32Field(fieldNumber, value) {
+ const body = Buffer.alloc(4);
+ body.writeFloatLE(value, 0);
+ return Buffer.concat([encodeTag(fieldNumber, 5), body]);
+}
+
+function encodeLengthDelimited(fieldNumber, body) {
+ return Buffer.concat([encodeTag(fieldNumber, 2), encodeVarint(body.length), body]);
+}
+
+function encodeVarintField(fieldNumber, value) {
+ return Buffer.concat([encodeTag(fieldNumber, 0), encodeVarint(value)]);
+}
+
+function encodeTimestampField(fieldNumber, seconds, nanos) {
+ const parts = [];
+ if (seconds !== 0) parts.push(encodeVarintField(1, seconds));
+ if (nanos !== 0) parts.push(encodeVarintField(2, nanos));
+ return encodeLengthDelimited(fieldNumber, Buffer.concat(parts));
+}
+
+/** Framed GetGrokCreditsConfig response for a usage ratio 0..1. */
+function buildCreditsResponseBuffer(usageRatio, resetSeconds = 1784825940, resetNanos = 867850000) {
+ const creditsInfo = Buffer.concat([
+ encodeFixed32Field(1, usageRatio),
+ encodeTimestampField(5, resetSeconds, resetNanos),
+ ]);
+ const topMessage = encodeLengthDelimited(1, creditsInfo);
+ const header = Buffer.alloc(5);
+ header[0] = 0x00;
+ header.writeUInt32BE(topMessage.length, 1);
+ return Buffer.concat([header, topMessage]);
+}
+
+function binaryResponse(buffer, status = 200) {
+ return new Response(buffer, {
+ status,
+ headers: { "content-type": "application/grpc-web+proto" },
+ });
+}
+
+const EMPTY_GRPC_WEB_FRAME = Buffer.from([0, 0, 0, 0, 0]);
+const GRPC_CREDITS_URL =
+ "https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig";
+
describe("getUsageForProvider(grok-cli)", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -174,6 +272,8 @@ describe("getUsageForProvider(grok-cli)", () => {
expect(billingCall[1].headers["x-userid"]).toBe(
"d84768dd-224d-4052-ba49-0d336fa9160c",
);
+ // REST already has numeric quotas — do not hit gRPC fallback
+ expect(proxyAwareFetch.mock.calls).toHaveLength(2);
});
it("surfaces auth-expired message on 401", async () => {
@@ -187,6 +287,8 @@ describe("getUsageForProvider(grok-cli)", () => {
});
expect(usage.message).toMatch(/expired|re-authorize/i);
+ // Auth failure must not attempt gRPC fallback
+ expect(proxyAwareFetch.mock.calls).toHaveLength(2);
});
it("returns depleted on-demand bar without blocking message when cap is zero", async () => {
@@ -204,15 +306,62 @@ describe("getUsageForProvider(grok-cli)", () => {
expect(usage.message).toBeUndefined();
expect(usage.quotas["On-demand"].remainingPercentage).toBe(0);
expect(usage.quotas["On-demand"].total).toBe(1);
+ // Exhausted free already has a quota bar — no gRPC fallback
+ expect(proxyAwareFetch.mock.calls).toHaveLength(2);
});
- it("reports active paid access when provider exposes no numeric quota", async () => {
+ it("falls back to GetGrokCreditsConfig gRPC when paid sub has no REST numeric quota", async () => {
+ const resetSeconds = 1784825940;
+ const resetNanos = 867850000;
+ const resetAt = new Date(
+ resetSeconds * 1000 + Math.round(resetNanos / 1_000_000),
+ ).toISOString();
+
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(EXHAUSTED_BILLING))
- .mockResolvedValueOnce(jsonResponse({
- ...USER_PROFILE,
- subscriptionTier: "XPremiumPlus",
- }));
+ .mockResolvedValueOnce(
+ jsonResponse({
+ ...USER_PROFILE,
+ subscriptionTier: "XPremiumPlus",
+ }),
+ )
+ .mockResolvedValueOnce(binaryResponse(buildCreditsResponseBuffer(0.35, resetSeconds, resetNanos)));
+
+ const usage = await getUsageForProvider({
+ provider: "grok-cli",
+ accessToken: "test-token",
+ });
+
+ expect(usage.message).toBeUndefined();
+ expect(usage.plan).toBe("XPremiumPlus");
+ expect(usage.quotas["Weekly SuperGrok"]).toMatchObject({
+ used: 35,
+ total: 100,
+ remainingPercentage: 65,
+ resetAt,
+ unlimited: false,
+ });
+
+ const grpcCall = proxyAwareFetch.mock.calls[2];
+ expect(grpcCall[0]).toBe(GRPC_CREDITS_URL);
+ expect(grpcCall[1].method).toBe("POST");
+ expect(grpcCall[1].headers.Authorization).toBe("Bearer test-token");
+ expect(grpcCall[1].headers["Content-Type"]).toBe("application/grpc-web+proto");
+ expect(grpcCall[1].headers["X-Grpc-Web"]).toBe("1");
+ // Empty gRPC-web request frame is required (flag 0 + length 0)
+ expect(Buffer.from(grpcCall[1].body)).toEqual(EMPTY_GRPC_WEB_FRAME);
+ });
+
+ it("keeps subscription message when REST empty and gRPC fails open", async () => {
+ proxyAwareFetch
+ .mockResolvedValueOnce(jsonResponse(EXHAUSTED_BILLING))
+ .mockResolvedValueOnce(
+ jsonResponse({
+ ...USER_PROFILE,
+ subscriptionTier: "XPremiumPlus",
+ }),
+ )
+ .mockResolvedValueOnce(binaryResponse(Buffer.alloc(0), 500));
const usage = await getUsageForProvider({
provider: "grok-cli",
@@ -223,6 +372,26 @@ describe("getUsageForProvider(grok-cli)", () => {
expect(usage.message).toMatch(/active.*numeric included quota/i);
expect(usage.quotas).toEqual({});
});
+
+ it("does not throw when gRPC network fails after empty REST quotas", async () => {
+ proxyAwareFetch
+ .mockResolvedValueOnce(jsonResponse(EXHAUSTED_BILLING))
+ .mockResolvedValueOnce(
+ jsonResponse({
+ ...USER_PROFILE,
+ subscriptionTier: "XPremiumPlus",
+ }),
+ )
+ .mockRejectedValueOnce(new Error("network down"));
+
+ const usage = await getUsageForProvider({
+ provider: "grok-cli",
+ accessToken: "test-token",
+ });
+
+ expect(usage.message).toMatch(/active.*numeric included quota/i);
+ expect(usage.quotas).toEqual({});
+ });
});
describe("parseQuotaData(grok-cli)", () => {
diff --git a/tests/unit/jina-reader-fetch.test.js b/tests/unit/jina-reader-fetch.test.js
new file mode 100644
index 00000000..3a6512da
--- /dev/null
+++ b/tests/unit/jina-reader-fetch.test.js
@@ -0,0 +1,66 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { handleFetchCore } from "../../open-sse/handlers/fetch/index.js";
+
+const originalFetch = global.fetch;
+
+describe("Jina Reader fetch", () => {
+ beforeEach(() => {
+ global.fetch = vi.fn();
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
+ it("uses Jina's JSON POST API instead of embedding the URL in the path", async () => {
+ global.fetch.mockResolvedValueOnce(new Response([
+ "Title: Example page",
+ "",
+ "URL Source: https://example.com/article",
+ "",
+ "Markdown Content:",
+ "Hello",
+ ].join("\n")));
+
+ const result = await handleFetchCore({
+ url: "https://example.com/article",
+ format: "markdown",
+ provider: "jina-reader",
+ providerConfig: { timeoutMs: 30000 },
+ credentials: { apiKey: "jina-test-key" },
+ });
+
+ expect(result.success).toBe(true);
+ expect(result.data.title).toBe("Example page");
+ expect(global.fetch).toHaveBeenCalledTimes(1);
+
+ const [requestUrl, init] = global.fetch.mock.calls[0];
+ expect(requestUrl).toBe("https://r.jina.ai/");
+ expect(init.method).toBe("POST");
+ expect(init.headers).toEqual({
+ "content-type": "application/json",
+ authorization: "Bearer jina-test-key",
+ });
+ expect(JSON.parse(init.body)).toEqual({ url: "https://example.com/article" });
+ });
+
+ it("returns the upstream status and error body", async () => {
+ global.fetch.mockResolvedValueOnce(new Response(
+ JSON.stringify({ detail: "Payment required" }),
+ { status: 402, headers: { "Content-Type": "application/json" } },
+ ));
+
+ const result = await handleFetchCore({
+ url: "https://example.com/article",
+ provider: "jina-reader",
+ providerConfig: { timeoutMs: 30000 },
+ credentials: { apiKey: "jina-test-key" },
+ });
+
+ expect(result).toMatchObject({
+ success: false,
+ status: 402,
+ });
+ expect(result.error).toContain("Payment required");
+ });
+});
diff --git a/tests/unit/kimi-usage.test.js b/tests/unit/kimi-usage.test.js
new file mode 100644
index 00000000..9d944a37
--- /dev/null
+++ b/tests/unit/kimi-usage.test.js
@@ -0,0 +1,299 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
+ proxyAwareFetch: vi.fn(),
+}));
+
+import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
+import { getUsageForProvider } from "../../open-sse/services/usage.js";
+import { USAGE_SUPPORTED_PROVIDERS, USAGE_APIKEY_PROVIDERS } from "../../src/shared/constants/providers.js";
+import { PROVIDERS } from "../../open-sse/providers/index.js";
+import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
+
+const KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
+
+function jsonResponse(body, status = 200) {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "Content-Type": "application/json" },
+ });
+}
+
+const ACTIVE_USAGE = {
+ user: {
+ membership: { level: "LEVEL_ADVANCED" },
+ },
+ usage: {
+ limit: "100",
+ used: "35",
+ remaining: "65",
+ resetTime: "2026-08-01T00:00:00Z",
+ },
+ limits: [
+ {
+ window: { type: "rate" },
+ detail: {
+ limit: "60",
+ remaining: "40",
+ resetTime: "2026-07-29T12:00:00Z",
+ },
+ },
+ ],
+};
+
+describe("kimi registry usage flags", () => {
+ it("exposes usage + usageApikey so OAuth and apikey cards appear on /quota", () => {
+ expect(USAGE_SUPPORTED_PROVIDERS).toContain("kimi");
+ expect(USAGE_APIKEY_PROVIDERS).toContain("kimi");
+ });
+
+ it("registers transport.usage url when present (optional)", () => {
+ // Provider may or may not put usage url on transport; handler has its own constant.
+ expect(PROVIDERS.kimi).toBeTruthy();
+ });
+});
+
+describe("getUsageForProvider(kimi) auth selection", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("OAuth path: Bearer + X-Msh-* (not chat x-api-key)", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_USAGE));
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "tok-abc",
+ providerSpecificData: { deviceId: "stable-device-1" },
+ });
+
+ expect(usage.message).toBeUndefined();
+ expect(usage.plan).toBe("Allegro");
+ expect(usage.quotas.Weekly).toMatchObject({
+ used: 35,
+ total: 100,
+ remainingPercentage: 65,
+ });
+
+ expect(proxyAwareFetch).toHaveBeenCalledTimes(1);
+ const [url, opts] = proxyAwareFetch.mock.calls[0];
+ expect(url).toBe(KIMI_USAGE_URL);
+ expect(opts.method).toBe("GET");
+ expect(opts.headers.Authorization).toBe("Bearer tok-abc");
+ expect(opts.headers["x-api-key"]).toBeUndefined();
+ expect(opts.headers["X-Msh-Platform"]).toBe("9router");
+ expect(opts.headers["X-Msh-Device-Id"]).toBe("stable-device-1");
+ expect(opts.headers["X-Msh-Version"]).toBeTruthy();
+ });
+
+ it("apikey path: x-api-key only (no Bearer / X-Msh)", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_USAGE));
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ apiKey: "sk-test-123",
+ });
+
+ expect(usage.message).toBeUndefined();
+ expect(usage.quotas.Weekly.used).toBe(35);
+
+ const [, opts] = proxyAwareFetch.mock.calls[0];
+ expect(opts.headers["x-api-key"]).toBe("sk-test-123");
+ expect(opts.headers.Authorization).toBeUndefined();
+ expect(opts.headers["X-Msh-Platform"]).toBeUndefined();
+ });
+
+ it("prefers apiKey over accessToken when both present", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_USAGE));
+
+ await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "tok-abc",
+ apiKey: "sk-prefer-me",
+ });
+
+ const [, opts] = proxyAwareFetch.mock.calls[0];
+ expect(opts.headers["x-api-key"]).toBe("sk-prefer-me");
+ expect(opts.headers.Authorization).toBeUndefined();
+ expect(opts.headers["X-Msh-Platform"]).toBeUndefined();
+ });
+
+ it("maps membership levels to plan display names", async () => {
+ for (const [level, plan] of [
+ ["LEVEL_BASIC", "Moderato"],
+ ["LEVEL_INTERMEDIATE", "Allegretto"],
+ ["LEVEL_ADVANCED", "Allegro"],
+ ["LEVEL_STANDARD", "Vivace"],
+ ]) {
+ proxyAwareFetch.mockResolvedValueOnce(
+ jsonResponse({
+ user: { membership: { level } },
+ usage: { limit: "10", used: "1", remaining: "9" },
+ }),
+ );
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "t",
+ });
+ expect(usage.plan).toBe(plan);
+ }
+ });
+
+ it("parses Weekly + Ratelimit; does not put absolute remaining on quota rows", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(jsonResponse(ACTIVE_USAGE));
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "tok",
+ });
+
+ // Absolute remaining would break getRemainingPercentage (treats it as 0-100 %)
+ expect(usage.quotas.Weekly.remaining).toBeUndefined();
+ expect(usage.quotas.Weekly.remainingPercentage).toBe(65);
+ expect(usage.quotas.Ratelimit).toMatchObject({
+ used: 20,
+ total: 60,
+ remainingPercentage: expect.closeTo(40 / 60 * 100, 5),
+ });
+ expect(usage.quotas.Ratelimit.remaining).toBeUndefined();
+ });
+
+ it("surfaces re-authorize message only on 401 unauthenticated", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(
+ jsonResponse(
+ {
+ code: "unauthenticated",
+ details: [
+ {
+ debug: {
+ reason: "REASON_INVALID_AUTH_TOKEN",
+ localizedMessage: { message: "Invalid auth token" },
+ },
+ },
+ ],
+ },
+ 401,
+ ),
+ );
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "expired",
+ });
+
+ expect(usage.message).toMatch(/expired|re-authorize/i);
+ expect(usage.message).not.toMatch(/subscribe|permission/i);
+ expect(usage.quotas).toBeUndefined();
+ });
+
+ it("maps 403 REASON_FEATURE_NO_PERMISSION to subscribe message (not expired)", async () => {
+ // Live capture: valid OAuth JWT still returns 403 permission_denied when
+ // the account has no Kimi Code usage entitlement.
+ proxyAwareFetch.mockResolvedValueOnce(
+ jsonResponse(
+ {
+ code: "permission_denied",
+ details: [
+ {
+ type: "common.error.v1.ErrorDetail",
+ debug: {
+ reason: "REASON_FEATURE_NO_PERMISSION",
+ localizedMessage: {
+ locale: "en-US",
+ message:
+ "You do not have permission to use this feature. Please subscribe to access.",
+ },
+ },
+ },
+ ],
+ },
+ 403,
+ ),
+ );
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "valid-but-no-sub",
+ providerSpecificData: { deviceId: "stable-device-1" },
+ });
+
+ expect(usage.message).toMatch(/permission|subscribe/i);
+ expect(usage.message).not.toMatch(/expired|re-authorize/i);
+ // Must not trip usage-route AUTH_EXPIRED_PATTERNS force-refresh loop
+ expect(usage.message.toLowerCase()).not.toMatch(/expired|re-authorize|unauthorized|401/);
+ });
+
+ it("formatKimiUsageError distinguishes 401 vs 403 feature gate", async () => {
+ const { formatKimiUsageError } = await import(
+ "../../open-sse/services/usage/kimi.js"
+ );
+ expect(formatKimiUsageError(401, '{"code":"unauthenticated"}')).toMatch(
+ /expired|re-authorize/i,
+ );
+ expect(
+ formatKimiUsageError(
+ 403,
+ JSON.stringify({
+ code: "permission_denied",
+ details: [
+ {
+ debug: {
+ reason: "REASON_FEATURE_NO_PERMISSION",
+ localizedMessage: {
+ message: "You do not have permission to use this feature.",
+ },
+ },
+ },
+ ],
+ }),
+ ),
+ ).toMatch(/permission|subscribe/i);
+ });
+
+ it("returns tracked-per-request message when usage limit missing", async () => {
+ proxyAwareFetch.mockResolvedValueOnce(
+ jsonResponse({
+ user: { membership: { level: "LEVEL_BASIC" } },
+ usage: {},
+ }),
+ );
+
+ const usage = await getUsageForProvider({
+ provider: "kimi",
+ accessToken: "tok",
+ });
+
+ expect(usage.plan).toBe("Moderato");
+ expect(usage.message).toMatch(/tracked per request/i);
+ });
+
+ it("returns missing-credentials message when neither token nor key", async () => {
+ const usage = await getUsageForProvider({ provider: "kimi" });
+ expect(usage.message).toMatch(/token|key|credential/i);
+ expect(proxyAwareFetch).not.toHaveBeenCalled();
+ });
+});
+
+describe("parseQuotaData(kimi)", () => {
+ it("forwards remainingPercentage for dashboard bars", () => {
+ const rows = parseQuotaData("kimi", {
+ plan: "Allegro",
+ quotas: {
+ Weekly: {
+ used: 35,
+ total: 100,
+ remainingPercentage: 65,
+ resetAt: "2026-08-01T00:00:00.000Z",
+ },
+ },
+ });
+
+ expect(rows).toHaveLength(1);
+ expect(rows[0]).toMatchObject({
+ name: "Weekly",
+ used: 35,
+ total: 100,
+ remainingPercentage: 65,
+ });
+ });
+});
diff --git a/tests/unit/kiro-api-key-endpoint-routing.test.js b/tests/unit/kiro-api-key-endpoint-routing.test.js
new file mode 100644
index 00000000..a0750adc
--- /dev/null
+++ b/tests/unit/kiro-api-key-endpoint-routing.test.js
@@ -0,0 +1,67 @@
+import { describe, expect, it } from "vitest";
+import { KiroExecutor } from "../../open-sse/executors/kiro.js";
+
+const RUNTIME = "https://runtime.us-east-1.kiro.dev/generateAssistantResponse";
+const CODEWHISPERER = "https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse";
+const Q = "https://q.us-east-1.amazonaws.com/generateAssistantResponse";
+
+function credentials(authMethod, region = "us-east-1") {
+ return { providerSpecificData: { authMethod, region } };
+}
+
+describe("Kiro auth-aware endpoint routing", () => {
+ const executor = new KiroExecutor();
+
+ it("routes API-key inference through Amazon Q before other surfaces", () => {
+ expect(executor.getOrderedBaseUrls(credentials("api_key"))).toEqual([
+ Q,
+ CODEWHISPERER,
+ RUNTIME,
+ ]);
+ });
+
+ it("keeps Builder ID OAuth on the Kiro runtime surface", () => {
+ expect(executor.getOrderedBaseUrls(credentials("builder-id"))).toEqual([
+ RUNTIME,
+ CODEWHISPERER,
+ Q,
+ ]);
+ });
+
+ it("keeps external IdP on CodeWhisperer before Amazon Q", () => {
+ expect(executor.getOrderedBaseUrls(credentials("external_idp"))).toEqual([
+ CODEWHISPERER,
+ Q,
+ RUNTIME,
+ ]);
+ });
+
+ it("regionalizes AWS endpoints for IDC without changing Kiro runtime", () => {
+ expect(executor.getOrderedBaseUrls(credentials("idc", "eu-west-1"))).toEqual([
+ "https://codewhisperer.eu-west-1.amazonaws.com/generateAssistantResponse",
+ "https://q.eu-west-1.amazonaws.com/generateAssistantResponse",
+ RUNTIME,
+ ]);
+ });
+
+ it("retries only endpoint/auth-surface failures, not payload-invalid 400s", () => {
+ expect(executor.shouldRetry(400, 0)).toBe(false);
+ expect(executor.shouldRetry(401, 1)).toBe(true);
+ expect(executor.shouldRetry(403, 2)).toBe(false);
+ expect(executor.shouldRetry(422, 0)).toBe(false);
+ });
+
+ it("builds endpoint-specific headers", () => {
+ const auth = { accessToken: "test-key", providerSpecificData: { authMethod: "api_key" } };
+ const qHeaders = executor.buildHeaders(auth, true, Q);
+ const codeWhispererHeaders = executor.buildHeaders(auth, true, CODEWHISPERER);
+ const runtimeHeaders = executor.buildHeaders(auth, true, RUNTIME);
+
+ expect(qHeaders.TokenType).toBe("API_KEY");
+ expect(qHeaders["X-Amz-Target"]).toBeUndefined();
+ expect(codeWhispererHeaders["X-Amz-Target"]).toBe(
+ "AmazonCodeWhispererStreamingService.GenerateAssistantResponse"
+ );
+ expect(runtimeHeaders["X-Amz-Target"]).toBeUndefined();
+ });
+});
diff --git a/tests/unit/kiro-conversation-canonicalization.test.js b/tests/unit/kiro-conversation-canonicalization.test.js
new file mode 100644
index 00000000..e19984d1
--- /dev/null
+++ b/tests/unit/kiro-conversation-canonicalization.test.js
@@ -0,0 +1,372 @@
+import { beforeEach, describe, expect, it } from "vitest";
+import {
+ canonicalizeKiroConversation,
+ normalizeKiroToolSpecs,
+ validateKiroConversation,
+} from "../../open-sse/translator/concerns/kiroConversation.js";
+import { clearKiroSessionReplayStore } from "../../open-sse/utils/kiroSessionReplay.js";
+import { clearSessionStore } from "../../open-sse/utils/sessionManager.js";
+import { claudeToKiroRequest } from "../../open-sse/translator/request/claude-to-kiro.js";
+import { openaiToKiroRequest } from "../../open-sse/translator/request/openai-to-kiro.js";
+
+const modelId = "claude-opus-5";
+
+function tool(name, schema = { type: "object", properties: {} }) {
+ return { name, description: `Tool ${name}`, input_schema: schema };
+}
+
+function specState(names = ["first", "second"]) {
+ const source = names.map((name) => tool(name));
+ return normalizeKiroToolSpecs(source);
+}
+
+function user(content, toolResults = []) {
+ return {
+ userInputMessage: {
+ content,
+ modelId,
+ ...(toolResults.length > 0 && { userInputMessageContext: { toolResults } }),
+ },
+ };
+}
+
+function assistant(content, toolUses = []) {
+ return {
+ assistantResponseMessage: {
+ content,
+ ...(toolUses.length > 0 && { toolUses }),
+ },
+ };
+}
+
+function result(toolUseId, value, status = "success") {
+ return { toolUseId, status, content: [{ text: value }] };
+}
+
+describe("Kiro conversation canonicalizer", () => {
+ beforeEach(() => {
+ clearKiroSessionReplayStore();
+ clearSessionStore();
+ });
+
+ it("keeps complete parallel tool pairs structured", () => {
+ const { specs, nameMap } = specState();
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [
+ { toolUseId: "t1", name: "first", input: { n: 1 } },
+ { toolUseId: "t2", name: "second", input: { n: 2 } },
+ ]),
+ ],
+ currentMessage: user("continue", [result("t1", "one"), result("t2", "two")]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ const calls = canonical.history[1].assistantResponseMessage.toolUses;
+ const results = canonical.currentMessage.userInputMessage.userInputMessageContext.toolResults;
+ expect(calls.map((call) => call.toolUseId)).toEqual(["t1", "t2"]);
+ expect(results.map((item) => item.toolUseId)).toEqual(["t1", "t2"]);
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("keeps the answered parallel call and flattens only the missing one", () => {
+ const { specs, nameMap } = specState();
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [
+ { toolUseId: "t1", name: "first", input: {} },
+ { toolUseId: "t2", name: "second", input: {} },
+ ]),
+ ],
+ currentMessage: user("continue", [result("t1", "one")]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ const assistantMessage = canonical.history[1].assistantResponseMessage;
+ expect(assistantMessage.toolUses).toHaveLength(1);
+ expect(assistantMessage.toolUses[0].toolUseId).toBe("t1");
+ expect(assistantMessage.content).toContain("[Tool call: second(");
+ expect(canonical.repairs.missingResults).toBe(1);
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("flattens non-adjacent and orphaned tool results", () => {
+ const { specs, nameMap } = specState(["first"]);
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [{ toolUseId: "t1", name: "first", input: {} }]),
+ user("result missing here"),
+ assistant("later"),
+ ],
+ currentMessage: user("late result", [result("t1", "too late")]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ expect(JSON.stringify(canonical)).not.toContain('"toolUseId":"t1"');
+ expect(canonical.history[1].assistantResponseMessage.content).toContain("[Tool call:");
+ expect(canonical.currentMessage.userInputMessage.content).toContain("too late");
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("remaps duplicate tool IDs together with their adjacent results", () => {
+ const { specs, nameMap } = specState();
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [
+ { toolUseId: "duplicate", name: "first", input: {} },
+ { toolUseId: "duplicate", name: "second", input: {} },
+ ]),
+ ],
+ currentMessage: user("continue", [
+ result("duplicate", "one"),
+ result("duplicate", "two"),
+ ]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ const calls = canonical.history[1].assistantResponseMessage.toolUses;
+ const results = canonical.currentMessage.userInputMessage.userInputMessageContext.toolResults;
+ expect(new Set(calls.map((call) => call.toolUseId)).size).toBe(2);
+ expect(results.map((item) => item.toolUseId)).toEqual(calls.map((call) => call.toolUseId));
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("deduplicates extra results without losing their text", () => {
+ const { specs, nameMap } = specState(["first"]);
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [{ toolUseId: "t1", name: "first", input: {} }]),
+ ],
+ currentMessage: user("continue", [result("t1", "one"), result("t1", "duplicate")]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ const current = canonical.currentMessage.userInputMessage;
+ expect(current.userInputMessageContext.toolResults).toHaveLength(1);
+ expect(current.content).toContain("duplicate");
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("flattens a trailing unanswered assistant tool call and creates a current user turn", () => {
+ const { specs, nameMap } = specState(["first"]);
+ const canonical = canonicalizeKiroConversation({
+ history: [user("start")],
+ currentMessage: assistant("run", [{ toolUseId: "t1", name: "first", input: {} }]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ expect(canonical.currentMessage.userInputMessage.content).toBe("continue");
+ expect(canonical.history[1].assistantResponseMessage.toolUses).toBeUndefined();
+ expect(canonical.history[1].assistantResponseMessage.content).toContain("[Tool call:");
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("flattens malformed input and tool uses missing from the current specs", () => {
+ const { specs, nameMap } = specState(["first"]);
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [
+ { toolUseId: "t1", name: "first", input: "{bad json" },
+ { toolUseId: "t2", name: "removed_tool", input: {} },
+ ]),
+ ],
+ currentMessage: user("continue", [result("t1", "one"), result("t2", "two")]),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ expect(canonical.history[1].assistantResponseMessage.toolUses).toBeUndefined();
+ expect(canonical.currentMessage.userInputMessage.userInputMessageContext.toolResults).toBeUndefined();
+ expect(canonical.currentMessage.userInputMessage.content).toContain("one");
+ expect(canonical.currentMessage.userInputMessage.content).toContain("two");
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("repairs a 30-call parallel turn with one missing result", () => {
+ const names = Array.from({ length: 30 }, (_, index) => `tool_${index}`);
+ const { specs, nameMap } = specState(names);
+ const calls = names.map((name, index) => ({
+ toolUseId: `t${index}`,
+ name,
+ input: { index },
+ }));
+ const results = names.slice(0, -1).map((_, index) => result(`t${index}`, `r${index}`));
+ const canonical = canonicalizeKiroConversation({
+ history: [user("start"), assistant("run", calls)],
+ currentMessage: user("continue", results),
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ expect(canonical.history[1].assistantResponseMessage.toolUses).toHaveLength(29);
+ expect(canonical.currentMessage.userInputMessage.userInputMessageContext.toolResults).toHaveLength(29);
+ expect(canonical.repairs.missingResults).toBe(1);
+ expect(canonical.valid).toBe(true);
+ });
+
+ it("flattens structured history when the client sent no tool specs", () => {
+ const canonical = canonicalizeKiroConversation({
+ history: [
+ user("start"),
+ assistant("run", [{ toolUseId: "t1", name: "first", input: {} }]),
+ ],
+ currentMessage: user("continue", [result("t1", "one")]),
+ modelId,
+ });
+
+ expect(JSON.stringify(canonical)).not.toContain("toolUses");
+ expect(JSON.stringify(canonical)).not.toContain("toolResults");
+ expect(canonical.history[1].assistantResponseMessage.content).toContain("[Tool call:");
+ expect(canonical.currentMessage.userInputMessage.content).toContain("[Tool result:");
+ });
+
+ it("normalizes names and recursively removes unsupported schema fields", () => {
+ const longDescription = "x".repeat(11000);
+ const { specs, nameMap } = normalizeKiroToolSpecs([{
+ name: "bad tool/name",
+ description: longDescription,
+ input_schema: {
+ additionalProperties: false,
+ properties: {
+ nested: {
+ type: "object",
+ additionalProperties: true,
+ properties: {},
+ required: [],
+ },
+ },
+ required: [],
+ },
+ }]);
+
+ const specification = specs[0].toolSpecification;
+ expect(nameMap.get("bad tool/name")).toBe("bad_tool_name");
+ expect(specification.name.length).toBeLessThanOrEqual(64);
+ expect(specification.description.length).toBe(10237);
+ expect(JSON.stringify(specification.inputSchema.json)).not.toContain("additionalProperties");
+ expect(JSON.stringify(specification.inputSchema.json)).not.toContain('"required":[]');
+ });
+
+ it("does not mutate the source conversation or tool definitions", () => {
+ const sourceTools = [tool("first")];
+ const sourceHistory = [
+ user("start"),
+ assistant("run", [{ toolUseId: "t1", name: "first", input: {} }]),
+ ];
+ const sourceCurrent = user("continue", [result("t1", "one")]);
+ const before = JSON.stringify({ sourceTools, sourceHistory, sourceCurrent });
+ const { specs, nameMap } = normalizeKiroToolSpecs(sourceTools);
+
+ canonicalizeKiroConversation({
+ history: sourceHistory,
+ currentMessage: sourceCurrent,
+ modelId,
+ toolSpecs: specs,
+ nameMap,
+ });
+
+ expect(JSON.stringify({ sourceTools, sourceHistory, sourceCurrent })).toBe(before);
+ });
+
+ it("preserves Claude tool_result errors", () => {
+ const output = claudeToKiroRequest(modelId, {
+ tools: [tool("first")],
+ messages: [
+ { role: "user", content: "start" },
+ { role: "assistant", content: [{ type: "tool_use", id: "t1", name: "first", input: {} }] },
+ { role: "user", content: [{ type: "tool_result", tool_use_id: "t1", is_error: true, content: "failed" }] },
+ ],
+ }, true, {});
+
+ const item = output.conversationState.currentMessage.userInputMessage
+ .userInputMessageContext.toolResults[0];
+ expect(item.status).toBe("error");
+ });
+
+ it("repairs partial parallel results in both direct translators", () => {
+ const claude = claudeToKiroRequest(modelId, {
+ tools: [tool("first"), tool("second")],
+ messages: [
+ { role: "user", content: "start" },
+ { role: "assistant", content: [
+ { type: "tool_use", id: "t1", name: "first", input: {} },
+ { type: "tool_use", id: "t2", name: "second", input: {} },
+ ] },
+ { role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "one" }] },
+ ],
+ }, true, {});
+ const openai = openaiToKiroRequest(modelId, {
+ tools: [
+ { type: "function", function: { name: "first", parameters: { type: "object", properties: {} } } },
+ { type: "function", function: { name: "second", parameters: { type: "object", properties: {} } } },
+ ],
+ messages: [
+ { role: "user", content: "start" },
+ { role: "assistant", content: "", tool_calls: [
+ { id: "t1", type: "function", function: { name: "first", arguments: "{}" } },
+ { id: "t2", type: "function", function: { name: "second", arguments: "{}" } },
+ ] },
+ { role: "tool", tool_call_id: "t1", content: "one" },
+ ],
+ }, true, {});
+
+ for (const payload of [claude, openai]) {
+ const state = payload.conversationState;
+ const validation = validateKiroConversation(
+ state.history,
+ state.currentMessage,
+ state.currentMessage.userInputMessage.userInputMessageContext.tools
+ );
+ expect(validation.valid).toBe(true);
+ expect(state.history[1].assistantResponseMessage.toolUses).toHaveLength(1);
+ }
+ });
+
+ it("does not let session replay replace a tool-result turn", () => {
+ const credentials = {
+ rawHeaders: { "x-session-id": "kiro-replay-tool-result-regression" },
+ connectionId: "kiro-account",
+ };
+ claudeToKiroRequest(modelId, {
+ messages: [{ role: "user", content: "frozen session start" }],
+ }, true, credentials);
+
+ const output = claudeToKiroRequest(modelId, {
+ tools: [tool("first")],
+ messages: [
+ { role: "assistant", content: [{ type: "tool_use", id: "t1", name: "first", input: {} }] },
+ { role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "kept" }] },
+ ],
+ }, true, credentials);
+ const state = output.conversationState;
+ const allText = JSON.stringify(state);
+
+ expect(allText).toContain("frozen session start");
+ expect(allText).toContain("kept");
+ expect(validateKiroConversation(
+ state.history,
+ state.currentMessage,
+ state.currentMessage.userInputMessage.userInputMessageContext.tools
+ ).valid).toBe(true);
+ });
+});
diff --git a/tests/unit/kiro-profile-arn.test.js b/tests/unit/kiro-profile-arn.test.js
index 1925582b..bfa3906c 100644
--- a/tests/unit/kiro-profile-arn.test.js
+++ b/tests/unit/kiro-profile-arn.test.js
@@ -4,10 +4,8 @@ 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.
+ * KiroService.validateApiKey validates against the Amazon Q model catalog and
+ * returns an account-bound credential without inventing a profileArn.
*
* Note: OAuth (Builder ID / IDC) profileArn resolution is handled upstream by
* fetchKiroProfileArn in providers.js and is covered there — not here.
@@ -16,11 +14,10 @@ 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";
+ it("validates an API key against Amazon Q without inventing profileArn", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
- json: async () => ({ profiles: [{ arn: expectedArn }] }),
+ json: async () => ({ models: [{ modelId: "claude-opus-5" }] }),
});
const svc = new KiroService();
@@ -29,17 +26,18 @@ describe("kiro API-key auth (KiroService.validateApiKey)", () => {
expect(cred).toEqual({
accessToken: "my-secret-key",
refreshToken: null,
- profileArn: expectedArn,
+ profileArn: null,
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"
+ expect(url).toBe(
+ "https://q.us-east-1.amazonaws.com/ListAvailableModels?origin=AI_EDITOR"
);
+ expect(init.method).toBe("GET");
+ expect(init.headers.Authorization).toBe("Bearer my-secret-key");
+ expect(init.headers.TokenType).toBe("API_KEY");
});
it("rejects an empty API key without a network call", async () => {
@@ -60,4 +58,15 @@ describe("kiro API-key auth (KiroService.validateApiKey)", () => {
/API key validation failed/
);
});
+
+ it("rejects a 200 response with an empty model catalog", async () => {
+ vi.spyOn(globalThis, "fetch").mockResolvedValue({
+ ok: true,
+ json: async () => ({ models: [] }),
+ });
+ const svc = new KiroService();
+ await expect(svc.validateApiKey("empty-key")).rejects.toThrow(
+ /returned no available models/
+ );
+ });
});
diff --git a/tests/unit/openai-to-kiro.test.js b/tests/unit/openai-to-kiro.test.js
index dbaf7c85..17773bdb 100644
--- a/tests/unit/openai-to-kiro.test.js
+++ b/tests/unit/openai-to-kiro.test.js
@@ -424,6 +424,31 @@ describe("openaiToKiroRequest", () => {
expect(result.additionalModelRequestFields).toBeUndefined();
});
+ it.each([
+ ["claude-sonnet-4.5-thinking-agentic(high)", "claude-sonnet-4.5"],
+ ["glm-5-thinking-agentic(medium)", "glm-5"],
+ ])("normalizes unsupported Kiro intensity suffix for %s", (model, upstream) => {
+ const result = openaiToKiroRequest(model, {
+ messages: [{ role: "user", content: "hello" }],
+ }, true, {});
+
+ expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe(upstream);
+ expect(result.additionalModelRequestFields).toBeUndefined();
+ expect(systemPromptOf(result)).toContain("CHUNKED WRITE PROTOCOL");
+ });
+
+ it("maps a supported Kiro Claude intensity suffix to native effort fields", () => {
+ const result = openaiToKiroRequest("claude-sonnet-5-thinking-agentic(high)", {
+ messages: [{ role: "user", content: "hello" }],
+ }, true, {});
+
+ expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-5");
+ expect(result.additionalModelRequestFields).toEqual({
+ thinking: { type: "adaptive", display: "summarized" },
+ output_config: { effort: "high" },
+ });
+ });
+
it("does not send additionalModelRequestFields for date-suffixed Claude 4 model ids", () => {
const body = {
reasoning_effort: "high",
diff --git a/tests/unit/provider-test-models-routing.test.js b/tests/unit/provider-test-models-routing.test.js
index 00f3a735..595cd89d 100644
--- a/tests/unit/provider-test-models-routing.test.js
+++ b/tests/unit/provider-test-models-routing.test.js
@@ -11,6 +11,10 @@ vi.mock("@/lib/localDb", () => ({
getApiKeys: mocks.getApiKeys,
}));
+vi.mock("@/lib/providers/connectionAccess", () => ({
+ getProviderConnectionAccess: vi.fn(async () => ({ ownerId: "admin-a" })),
+}));
+
vi.mock("@/shared/utils/machineId", () => ({
getConsistentMachineId: mocks.getConsistentMachineId,
}));
diff --git a/tests/unit/thinking-levels-kiro.test.js b/tests/unit/thinking-levels-kiro.test.js
new file mode 100644
index 00000000..edb3b7aa
--- /dev/null
+++ b/tests/unit/thinking-levels-kiro.test.js
@@ -0,0 +1,14 @@
+import { describe, it, expect } from "vitest";
+import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
+
+describe("getThinkingLevels for Kiro", () => {
+ it("does not advertise native intensity for legacy Kiro models", () => {
+ expect(getThinkingLevels("kiro", "claude-sonnet-4.5")).toBeNull();
+ expect(getThinkingLevels("kiro", "glm-5")).toBeNull();
+ });
+
+ it("advertises native levels for supported Kiro models", () => {
+ expect(getThinkingLevels("kiro", "claude-sonnet-5")).toContain("high");
+ expect(getThinkingLevels("kiro", "gpt-5.6-sol")).toContain("xhigh");
+ });
+});
diff --git a/tests/unit/token-refresh-generic.test.js b/tests/unit/token-refresh-generic.test.js
new file mode 100644
index 00000000..d4507c2e
--- /dev/null
+++ b/tests/unit/token-refresh-generic.test.js
@@ -0,0 +1,146 @@
+/**
+ * Generic OAuth2 token refresh — config-driven profiles.
+ *
+ * Verifies refreshAccessToken() handles the 5 foldable providers
+ * (qwen, iflow, github, kimi, claude) via a REFRESH_PROFILES table,
+ * while preserving the legacy generic path for unknown providers.
+ */
+
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+
+const originalFetch = global.fetch;
+
+function mockFetchOnce(payload, { ok = true, status = 200 } = {}) {
+ const fn = vi.fn().mockResolvedValue({
+ ok,
+ status,
+ json: () => Promise.resolve(payload),
+ text: () => Promise.resolve(JSON.stringify(payload)),
+ });
+ global.fetch = fn;
+ return fn;
+}
+
+describe("refreshAccessToken — config-driven profiles", () => {
+ beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); global.fetch = originalFetch; });
+ afterEach(() => { global.fetch = originalFetch; });
+
+ it("qwen: form body + clientId, surfaces resource_url as providerSpecificData", async () => {
+ const fm = mockFetchOnce({
+ access_token: "qw-acc",
+ refresh_token: "qw-refresh-rotated",
+ expires_in: 7200,
+ resource_url: "https://dashscope.aliyuncs.com",
+ });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ const out = await refreshAccessToken("qwen", "qw-old-refresh", {}, console);
+
+ expect(out).toEqual({
+ accessToken: "qw-acc",
+ refreshToken: "qw-refresh-rotated",
+ expiresIn: 7200,
+ providerSpecificData: { resourceUrl: "https://dashscope.aliyuncs.com" },
+ });
+ const [url, init] = fm.mock.calls[0];
+ expect(init.method).toBe("POST");
+ expect(init.headers["Content-Type"]).toBe("application/x-www-form-urlencoded");
+ const body = new URLSearchParams(init.body);
+ expect(body.get("grant_type")).toBe("refresh_token");
+ expect(body.get("refresh_token")).toBe("qw-old-refresh");
+ expect(body.get("client_id")).toBeTruthy();
+ });
+
+ it("iflow: Basic Auth header from clientId:clientSecret, form body keeps client_secret", async () => {
+ const fm = mockFetchOnce({ access_token: "if-acc", refresh_token: "if-rot", expires_in: 3600 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ await refreshAccessToken("iflow", "if-old", {}, console);
+
+ const [, init] = fm.mock.calls[0];
+ expect(init.headers["Authorization"]).toMatch(/^Basic /);
+ const body = new URLSearchParams(init.body);
+ expect(body.get("client_id")).toBeTruthy();
+ expect(body.get("client_secret")).toBeTruthy();
+ });
+
+ it("github: omits client_secret when config has none", async () => {
+ const fm = mockFetchOnce({ access_token: "gh-acc", expires_in: 28800 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ const out = await refreshAccessToken("github", "gh-old", {}, console);
+
+ const body = new URLSearchParams(fm.mock.calls[0][1].body);
+ expect(body.get("client_secret")).toBeNull();
+ expect(out.accessToken).toBe("gh-acc");
+ expect(out.refreshToken).toBe("gh-old");
+ });
+
+ it("kimi: merges X-Msh-* headers from credentials.providerSpecificData.deviceId", async () => {
+ const fm = mockFetchOnce({ access_token: "km-acc", expires_in: 86400 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ await refreshAccessToken("kimi", "km-old", {
+ providerSpecificData: { deviceId: "dev-xyz" },
+ }, console);
+
+ const headers = fm.mock.calls[0][1].headers;
+ // Kimi's buildKimiHeaders must contribute at least one X-Msh- header
+ const mshKeys = Object.keys(headers).filter((k) => k.toLowerCase().startsWith("x-msh-"));
+ expect(mshKeys.length).toBeGreaterThan(0);
+ });
+
+ it("claude: JSON body, client_id only (no client_secret)", async () => {
+ const fm = mockFetchOnce({ access_token: "cl-acc", refresh_token: "cl-rot", expires_in: 3600 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ await refreshAccessToken("claude", "cl-old", {}, console);
+
+ const [, init] = fm.mock.calls[0];
+ expect(init.headers["Content-Type"]).toBe("application/json");
+ const parsed = JSON.parse(init.body);
+ expect(parsed.grant_type).toBe("refresh_token");
+ expect(parsed.client_id).toBeTruthy();
+ expect(parsed).not.toHaveProperty("client_secret");
+ });
+
+ it("returns null on non-ok response", async () => {
+ mockFetchOnce({ error: "invalid_grant" }, { ok: false, status: 400 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+ const out = await refreshAccessToken("qwen", "dead", {}, console);
+ expect(out).toBeNull();
+ });
+
+ it("returns null when refreshToken missing", async () => {
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+ const out = await refreshAccessToken("qwen", "", {}, console);
+ expect(out).toBeNull();
+ });
+
+ it("dedupes concurrent calls with same refresh token (same dedupKey)", async () => {
+ const fm = mockFetchOnce({ access_token: "dd-acc", expires_in: 3600 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+ const creds = { providerSpecificData: { deviceId: "d" } };
+ await Promise.all([
+ refreshAccessToken("kimi", "dup-refresh", creds, console),
+ refreshAccessToken("kimi", "dup-refresh", creds, console),
+ ]);
+ expect(fm).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe("refreshAccessToken — legacy generic path (no profile)", () => {
+ beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); global.fetch = originalFetch; });
+ afterEach(() => { global.fetch = originalFetch; });
+
+ it("still works for an unprofiled provider via config.refreshUrl/clientId/clientSecret", async () => {
+ const fm = mockFetchOnce({ access_token: "gen-acc", expires_in: 3600 });
+ const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
+
+ await refreshAccessToken("cline", "gen-old", {}, console);
+
+ const body = new URLSearchParams(fm.mock.calls[0][1].body);
+ expect(body.get("grant_type")).toBe("refresh_token");
+ expect(body.get("client_id")).toBeTruthy();
+ });
+});
diff --git a/tests/unit/usage-dispatch.test.js b/tests/unit/usage-dispatch.test.js
index 097aa83b..e150d376 100644
--- a/tests/unit/usage-dispatch.test.js
+++ b/tests/unit/usage-dispatch.test.js
@@ -15,8 +15,8 @@ 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", "grok-cli",
- "orbit-provider",
+ "minimax", "minimax-cn", "vercel-ai-gateway", "grok-cli", "kimi",
+ "deepseek", "orbit-provider",
];
describe("usage dispatch", () => {
diff --git a/tests/unit/windsurf-executor.test.js b/tests/unit/windsurf-executor.test.js
new file mode 100644
index 00000000..952358f8
--- /dev/null
+++ b/tests/unit/windsurf-executor.test.js
@@ -0,0 +1,199 @@
+import { describe, it, expect } from "vitest";
+import {
+ resolveWsModelId,
+ buildGetChatMessageRequest,
+ grpcWebFrame,
+ decodeCompletionChunk,
+ default as WindsurfExecutor,
+} from "open-sse/executors/windsurf.js";
+import { PROVIDERS } from "open-sse/config/providers.js";
+
+// ─── Protobuf helpers for building expected wire bytes in tests ──────────────
+
+function encodeVarint(value) {
+ const bytes = [];
+ let v = value >>> 0;
+ while (v > 0x7f) { bytes.push((v & 0x7f) | 0x80); v >>>= 7; }
+ bytes.push(v & 0x7f);
+ return new Uint8Array(bytes);
+}
+function encodeLenField(fieldNum, payload) {
+ const tag = encodeVarint((fieldNum << 3) | 2);
+ const len = encodeVarint(payload.length);
+ const out = new Uint8Array(tag.length + len.length + payload.length);
+ out.set(tag, 0); out.set(len, tag.length); out.set(payload, tag.length + len.length);
+ return out;
+}
+function encodeStringField(fieldNum, str) {
+ return encodeLenField(fieldNum, new TextEncoder().encode(str));
+}
+
+describe("windsurf MODEL_ALIAS_MAP", () => {
+ it("maps SWE models to snake-case wire names", () => {
+ expect(resolveWsModelId("swe-1.6-fast")).toBe("swe-1-6-fast");
+ expect(resolveWsModelId("swe-1.5")).toBe("swe-1-5");
+ });
+ it("maps Claude 4.5 to MODEL_PRIVATE_* aliases", () => {
+ expect(resolveWsModelId("claude-sonnet-4.5")).toBe("MODEL_PRIVATE_2");
+ expect(resolveWsModelId("claude-opus-4.5")).toBe("MODEL_CLAUDE_4_5_OPUS");
+ });
+ it("applies default effort level for bare gpt-5.x ids", () => {
+ expect(resolveWsModelId("gpt-5.5")).toBe("gpt-5-5-medium");
+ expect(resolveWsModelId("gpt-5.4")).toBe("gpt-5-4-medium");
+ });
+ it("passes through unknown ids as-is", () => {
+ expect(resolveWsModelId("custom-model")).toBe("custom-model");
+ });
+});
+
+describe("grpcWebFrame", () => {
+ it("prepends a 5-byte header: 0x00 flag + big-endian length", () => {
+ const payload = new Uint8Array([1, 2, 3, 4, 5]);
+ const frame = grpcWebFrame(payload);
+ expect(frame[0]).toBe(0x00);
+ const view = new DataView(frame.buffer);
+ expect(view.getUint32(1, false)).toBe(5); // big-endian length
+ expect(Array.from(frame.slice(5))).toEqual([1, 2, 3, 4, 5]);
+ });
+ it("encodes empty payload as a 5-byte frame", () => {
+ const frame = grpcWebFrame(new Uint8Array(0));
+ expect(frame.length).toBe(5);
+ expect(frame[0]).toBe(0x00);
+ });
+});
+
+describe("buildGetChatMessageRequest", () => {
+ it("emits metadata (field 1), cascade_id (2), model (3), messages (4+)", () => {
+ const payload = buildGetChatMessageRequest("sk-ws-test", "swe-1.6", [
+ { role: "user", content: "hello" },
+ ]);
+ expect(payload.length).toBeGreaterThan(10);
+ // First byte 0x0a = field 1, wire type 2 (length-delimited) → metadata present
+ expect(payload[0]).toBe(0x0a);
+ });
+
+ it("embeds the apiKey inside the metadata sub-message", () => {
+ const payload = buildGetChatMessageRequest("sk-ws-secret", "gpt-5", []);
+ // The metadata bytes are the first length-delimited field — should contain the key.
+ const asString = new TextDecoder().decode(payload);
+ expect(asString).toContain("sk-ws-secret");
+ // And the IDE identification fields.
+ expect(asString).toContain("windsurf");
+ expect(asString).toContain("3.14.0");
+ });
+
+ it("appends one field-4 message per chat message", () => {
+ // Proper top-level protobuf field counter (byte 0x22 collides with content bytes).
+ const countField = (buf, target) => {
+ let offset = 0;
+ let count = 0;
+ while (offset < buf.length) {
+ let result = 0, shift = 0;
+ while (offset < buf.length) {
+ const b = buf[offset++];
+ result |= (b & 0x7f) << shift;
+ if ((b & 0x80) === 0) break;
+ shift += 7;
+ }
+ const fieldNum = result >>> 3;
+ const wireType = result & 0x07;
+ if (wireType === 2) {
+ let len = 0, ls = 0;
+ while (offset < buf.length) {
+ const b = buf[offset++];
+ len |= (b & 0x7f) << ls;
+ if ((b & 0x80) === 0) break;
+ ls += 7;
+ }
+ if (fieldNum === target) count++;
+ offset += len;
+ } else if (wireType === 0) {
+ while (offset < buf.length) {
+ const b = buf[offset++];
+ if ((b & 0x80) === 0) break;
+ }
+ } else if (wireType === 1) {
+ offset += 8;
+ } else if (wireType === 5) {
+ offset += 4;
+ } else {
+ break;
+ }
+ }
+ return count;
+ };
+ const one = buildGetChatMessageRequest("k", "m", [{ role: "user", content: "a" }]);
+ const two = buildGetChatMessageRequest("k", "m", [
+ { role: "user", content: "a" },
+ { role: "assistant", content: "b" },
+ ]);
+ expect(countField(one, 4)).toBe(1);
+ expect(countField(two, 4)).toBe(2);
+ });
+});
+
+describe("decodeCompletionChunk", () => {
+ it("decodes a ContentChunk (field 1 → text)", () => {
+ const chunk = encodeLenField(1, encodeStringField(1, "hello world"));
+ const decoded = decodeCompletionChunk(chunk);
+ expect(decoded).toEqual({ kind: "content", text: "hello world" });
+ });
+
+ it("decodes an ErrorChunk (field 4 → message)", () => {
+ const chunk = encodeLenField(4, encodeStringField(1, "quota exhausted"));
+ const decoded = decodeCompletionChunk(chunk);
+ expect(decoded).toEqual({ kind: "error", message: "quota exhausted" });
+ });
+
+ it("decodes a DoneChunk (field 3 → UsageStats with prompt/completion tokens)", () => {
+ // UsageStats: field 1 = prompt_tokens (varint), field 2 = completion_tokens (varint)
+ const usage = new Uint8Array([...encodeVarint((1 << 3) | 0), ...encodeVarint(42), ...encodeVarint((2 << 3) | 0), ...encodeVarint(99)]);
+ const doneChunk = encodeLenField(3, encodeLenField(1, usage));
+ const decoded = decodeCompletionChunk(doneChunk);
+ expect(decoded.kind).toBe("done");
+ expect(decoded.promptTokens).toBe(42);
+ expect(decoded.completionTokens).toBe(99);
+ });
+
+ it("returns { kind: 'unknown' } for empty buffer", () => {
+ expect(decodeCompletionChunk(new Uint8Array(0))).toEqual({ kind: "unknown" });
+ });
+});
+
+describe("WindsurfExecutor class", () => {
+ it("constructor wires the current Codeium chat endpoint", () => {
+ const ex = new WindsurfExecutor();
+ expect(ex.provider).toBe("windsurf");
+ expect(ex.config).toBeDefined();
+ expect(ex.config.baseUrl).toContain("server.codeium.com");
+ expect(typeof ex.execute).toBe("function");
+ });
+
+ it("buildHeaders emits grpc-web+proto + Bearer token", () => {
+ const ex = new WindsurfExecutor();
+ const h = ex.buildHeaders({ accessToken: "sk-ws-abc" });
+ expect(h["Content-Type"]).toBe("application/grpc-web+proto");
+ expect(h.Accept).toBe("application/grpc-web+proto");
+ expect(h["X-Grpc-Web"]).toBe("1");
+ expect(h.Authorization).toBe("Bearer sk-ws-abc");
+ expect(h["User-Agent"]).toMatch(/^windsurf\//);
+ });
+
+ it("buildHeaders omits Authorization when no token", () => {
+ const ex = new WindsurfExecutor();
+ const h = ex.buildHeaders({});
+ expect(h.Authorization).toBeUndefined();
+ });
+
+ it("buildUrl returns the GetChatMessage endpoint", () => {
+ const ex = new WindsurfExecutor();
+ expect(ex.buildUrl()).toBe("https://server.codeium.com/exa.language_server_pb.LanguageServerService/GetChatMessage");
+ });
+
+ it("uses a valid fallback while the provider remains hidden from the public registry", () => {
+ expect(PROVIDERS.windsurf).toBeUndefined();
+ expect(new WindsurfExecutor().config.baseUrl).toBe(
+ "https://server.codeium.com/exa.language_server_pb.LanguageServerService/GetChatMessage"
+ );
+ });
+});