Merge branch 'master' into gitea/new_feature

This commit is contained in:
2026-07-30 23:14:05 +07:00
252 changed files with 20302 additions and 4337 deletions

View File

@@ -10,7 +10,7 @@
"kr": "kiro",
"cu": "cursor",
"kc": "kilocode",
"kmc": "kimi-coding",
"kmc": "kimi",
"cl": "cline",
"oc": "opencode",
"ocg": "opencode-go",
@@ -93,16 +93,44 @@
"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": "devin-cli",
"devin": "devin-cli",
"devin-cli": "devin-cli",
"morph": "morph",
"morphllm": "morph"
},
"idToAlias": {
"alicode": "alicode",
"alicode-intl": "alicode-intl",
"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",
@@ -111,12 +139,15 @@
"clinepass": "clinepass",
"cloudflare-ai": "cloudflare-ai",
"codebuddy-cn": "cbcn",
"codebuddy-intl": "cbai",
"codex": "cx",
"cohere": "cohere",
"commandcode": "commandcode",
"cursor": "cu",
"deepgram": "deepgram",
"deepseek": "deepseek",
"devin-cli": "dv",
"featherless": "featherless",
"fireworks": "fireworks",
"gemini": "gemini",
"gemini-cli": "gc",
@@ -129,16 +160,18 @@
"groq": "groq",
"hyperbolic": "hyperbolic",
"iflow": "if",
"kilo-gateway": "kgw",
"kilocode": "kc",
"kimchi": "kimchi",
"kimi": "kimi",
"kimi-coding": "kmc",
"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,10 +182,13 @@
"opencode-go": "opencode-go",
"openrouter": "openrouter",
"perplexity": "perplexity",
"perplexity-agent": "perplexity-agent",
"perplexity-web": "perplexity-web",
"qoder": "qd",
"qwen": "qw",
"sambanova": "samba",
"siliconflow": "siliconflow",
"tencent": "hunyuan",
"together": "together",
"venice": "venice",
"vercel-ai-gateway": "vercel-ai-gateway",
@@ -161,17 +197,23 @@
"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",
"alims-intl",
"anthropic",
"assemblyai",
"black-forest-labs",
"blackbox",
"bm",
"byteplus",
"bzl",
"cbai",
"cbcn",
"cc",
"cerebras",
@@ -185,9 +227,11 @@
"cx",
"deepgram",
"deepseek",
"dv",
"edge-tts",
"elevenlabs-tts-models",
"fal-ai",
"featherless",
"fireworks",
"gc",
"gcli",
@@ -201,18 +245,21 @@
"grok-web",
"groq",
"huggingface",
"hunyuan",
"hyperbolic",
"if",
"kc",
"kgw",
"kimchi",
"kimi",
"kmc",
"kr",
"llm7",
"local-device",
"minimax",
"minimax-cn",
"mistral",
"mmf",
"morph",
"nanobanana",
"nebius",
"nvidia",
@@ -226,11 +273,14 @@
"openrouter-tts-models",
"openrouter-tts-voices",
"perplexity",
"perplexity-agent",
"perplexity-web",
"qd",
"qianfan",
"qw",
"recraft",
"runwayml",
"samba",
"sdwebui",
"siliconflow",
"stability-ai",
@@ -242,6 +292,7 @@
"voyage-ai",
"xai",
"xiaomi-mimo",
"xiaomi-tokenplan"
"xiaomi-tokenplan",
"zd"
]
}

View File

@@ -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

View File

@@ -35,14 +35,14 @@
"xai": "https://auth.x.ai/oauth2/token",
"grok-cli": "https://auth.x.ai/oauth2/token",
"cline": "https://api.cline.bot/api/v1/auth/token",
"kimi-coding": "https://auth.kimi.com/api/oauth/token"
"kimi": "https://auth.kimi.com/api/oauth/token"
},
"authUrls": {
"kiro": "https://prod.us-east-1.auth.desktop.kiro.dev"
},
"refreshUrls": {
"cline": "https://api.cline.bot/api/v1/auth/refresh",
"kimi-coding": "https://auth.kimi.com/api/oauth/token",
"kimi": "https://auth.kimi.com/api/oauth/token",
"xai": "https://auth.x.ai/oauth2/token",
"grok-cli": "https://auth.x.ai/oauth2/token"
},
@@ -51,7 +51,7 @@
"codex": "app_EMoamEEZ73f0CkXaXp7hrann",
"qwen": "f0304373b74a44d2b584a3fb70ca9e56",
"iflow": "10009311001",
"kimi-coding": "17e5f671-d194-4dfb-9706-5516cb48c098",
"kimi": "17e5f671-d194-4dfb-9706-5516cb48c098",
"grok-cli": "b1a00492-073a-47ea-816f-4c329264a828"
}
}

View File

@@ -19,18 +19,17 @@
"baseUrl": "https://api.anthropic.com/v1/messages",
"format": "claude",
"headers": {
"Anthropic-Version": "2023-06-01",
"anthropic-version": "2023-06-01",
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
}
},
"antigravity": {
"baseUrls": [
"https://daily-cloudcode-pa.googleapis.com",
"https://daily-cloudcode-pa.sandbox.googleapis.com"
"https://daily-cloudcode-pa.googleapis.com"
],
"format": "antigravity",
"headers": {
"User-Agent": "antigravity/1.107.0 darwin/arm64"
"User-Agent": "antigravity/ide/2.1.1 darwin/arm64"
},
"retry": {
"429": {
@@ -232,7 +231,7 @@
"Content-Type": "application/connect+proto",
"User-Agent": "connect-es/1.6.1"
},
"clientVersion": "3.1.0"
"clientVersion": "3.12.17"
},
"deepgram": {
"baseUrl": "https://api.deepgram.com/v1/listen",
@@ -270,6 +269,11 @@
}
]
},
"featherless": {
"baseUrl": "https://api.featherless.ai/v1/chat/completions",
"validateUrl": "https://api.featherless.ai/v1/models",
"format": "openai"
},
"fireworks": {
"baseUrl": "https://api.fireworks.ai/inference/v1/chat/completions",
"validateUrl": "https://api.fireworks.ai/inference/v1/models",
@@ -307,6 +311,7 @@
"github": {
"baseUrl": "https://api.githubcopilot.com/chat/completions",
"responsesUrl": "https://api.githubcopilot.com/responses",
"messagesUrl": "https://api.githubcopilot.com/v1/messages",
"headers": {
"copilot-integration-id": "vscode-chat",
"editor-version": "vscode/1.110.0",
@@ -398,17 +403,18 @@
"modelsUrl": "https://cli-chat-proxy.grok.com/v1/models",
"userUrl": "https://cli-chat-proxy.grok.com/v1/user",
"billingUrl": "https://cli-chat-proxy.grok.com/v1/billing",
"clientVersion": "0.2.93",
"clientIdentifier": "grok-pager",
"clientVersion": "0.2.99",
"clientIdentifier": "grok-shell",
"tokenAuth": "xai-grok-cli",
"headers": {
"User-Agent": "grok-pager/0.2.93 grok-shell/0.2.93 (linux; x86_64)",
"x-xai-token-auth": "xai-grok-cli",
"x-grok-client-identifier": "grok-pager",
"x-grok-client-version": "0.2.93",
"x-authenticateresponse": "authenticate-response"
"User-Agent": "grok-shell/0.2.99 (linux; x86_64)",
"x-grok-client-identifier": "grok-shell",
"x-grok-client-version": "0.2.99"
},
"usage": {
"url": "https://cli-chat-proxy.grok.com/v1/billing?format=credits",
"userUrl": "https://cli-chat-proxy.grok.com/v1/user?include=subscription"
},
"compactionAt": 400000,
"retry": {
"429": {
"attempts": 2,
@@ -477,7 +483,7 @@
"scheme": "bearer"
}
},
"kimi-coding": {
"kimi": {
"baseUrl": "https://api.kimi.com/coding/v1/messages",
"format": "claude",
"urlSuffix": "?beta=true",
@@ -528,45 +534,6 @@
}
]
},
"kimi": {
"baseUrl": "https://api.kimi.com/coding/v1/messages",
"format": "claude",
"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"
},
"transports": [
{
"format": "openai",
"baseUrl": "https://api.kimi.com/coding/v1/chat/completions",
"auth": {
"combined": true,
"header": "Authorization",
"scheme": "bearer"
}
},
{
"format": "claude",
"baseUrl": "https://api.kimi.com/coding/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"
}
}
]
},
"kiro": {
"baseUrl": "https://runtime.us-east-1.kiro.dev/generateAssistantResponse",
"baseUrls": [
@@ -774,6 +741,11 @@
"validateUrl": "https://api.perplexity.ai/models",
"format": "openai"
},
"perplexity-agent": {
"baseUrl": "https://api.perplexity.ai/v1/responses",
"validateUrl": "https://api.perplexity.ai/v1/models",
"format": "openai-responses"
},
"qoder": {
"baseUrl": "https://api3.qoder.sh/algo/api/v2/service/pro/sse/agent_chat_generation",
"headers": {},
@@ -900,5 +872,103 @@
}
}
]
},
"alims-intl": {
"baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
"headers": {},
"quirks": {
"preserveCacheControl": true
},
"format": "openai"
},
"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"
},
"zed": {
"baseUrl": "https://cloud.zed.dev/completions",
"format": "openai",
"forceStream": true,
"headers": {
"content-type": "application/json"
},
"auth": {
"combined": true,
"header": "Authorization",
"scheme": "<user_id> <access_token>"
},
"usage": {
"url": "https://cloud.zed.dev/client/users/me"
},
"modelsUrl": "https://cloud.zed.dev/models"
},
"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"
},
"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"
},
"bluesminds": {
"baseUrl": "https://api.bluesminds.com/v1/chat/completions",
"validateUrl": "https://api.bluesminds.com/v1/models",
"format": "openai"
},
"kilo-gateway": {
"baseUrl": "https://api.kilo.ai/api/gateway/chat/completions",
"validateUrl": "https://api.kilo.ai/api/gateway/models",
"format": "openai"
},
"llm7": {
"baseUrl": "https://api.llm7.io/v1/chat/completions",
"validateUrl": "https://api.llm7.io/v1/models",
"format": "openai"
},
"sambanova": {
"baseUrl": "https://api.sambanova.ai/v1/chat/completions",
"validateUrl": "https://api.sambanova.ai/v1/models",
"format": "openai"
},
"tencent": {
"baseUrl": "https://api.hunyuan.cloud.tencent.com/v1/chat/completions",
"validateUrl": "https://api.hunyuan.cloud.tencent.com/v1/models",
"format": "openai"
},
"morph": {
"baseUrl": "https://api.morphllm.com/v1/chat/completions",
"validateUrl": "https://api.morphllm.com/v1/models",
"format": "openai"
},
"devin-cli": {
"baseUrl": "devin://acp/stdio",
"format": "openai"
}
}

View File

@@ -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)

View File

@@ -22,7 +22,7 @@ const resolved = {
// Grok CLI injects oauth.tokenUrl onto PROVIDERS via OAUTH_INJECT_FIELDS
"grok-cli": PROVIDERS["grok-cli"]?.tokenUrl,
cline: PROVIDERS.cline?.tokenUrl,
"kimi-coding": PROVIDERS["kimi-coding"]?.tokenUrl,
kimi: PROVIDERS.kimi?.tokenUrl,
},
authUrls: {
qwen: PROVIDERS.qwen?.authUrl,
@@ -31,7 +31,7 @@ const resolved = {
},
refreshUrls: {
cline: PROVIDERS.cline?.refreshUrl,
"kimi-coding": PROVIDERS["kimi-coding"]?.refreshUrl,
kimi: PROVIDERS.kimi?.refreshUrl,
xai: PROVIDERS.xai?.refreshUrl,
"grok-cli": PROVIDERS["grok-cli"]?.tokenUrl,
},
@@ -40,7 +40,7 @@ const resolved = {
codex: PROVIDERS.codex?.clientId,
qwen: PROVIDERS.qwen?.clientId,
iflow: PROVIDERS.iflow?.clientId,
"kimi-coding": PROVIDERS["kimi-coding"]?.clientId,
kimi: PROVIDERS.kimi?.clientId,
"grok-cli": PROVIDERS["grok-cli"]?.clientId,
},
};

View File

@@ -5,8 +5,34 @@ import { translateRequest } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const O2K = (body) => translateRequest(FORMATS.OPENAI, FORMATS.KIRO, "m", body, true, null, "kiro");
const R2K = (model, body) => translateRequest(
FORMATS.OPENAI_RESPONSES,
FORMATS.KIRO,
model,
body,
true,
null,
"kiro"
);
describe("OpenAI → Kiro", () => {
it.each([
["high", "gpt-5.6-sol"],
["medium", "gpt-5.6-terra"],
["low", "gpt-5.6-luna"],
])("preserves Responses reasoning.effort %s through the full Kiro route", (effort, model) => {
const out = R2K(model, {
input: "Use the requested effort",
reasoning: { effort },
});
expect(out.additionalModelRequestFields).toEqual({
reasoning: { effort },
});
expect(out.systemPrompt || "").not.toContain("<thinking_mode>");
expect(out.systemPrompt || "").not.toContain("<max_thinking_length>");
});
// openai-to-kiro.js — safeJSONParse guards bad tool-call JSON (fixed in PR #1582)
it("malformed tool arguments do not throw the whole request", () => {
expect(() =>

View File

@@ -98,6 +98,18 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>");
});
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" },
@@ -112,6 +124,59 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>");
});
it("maps Claude-format effort to GPT-5.6 reasoning fields without legacy prompt tags", () => {
const out = C2K({
output_config: { effort: "low" },
messages: [{ role: "user", content: "think lightly" }],
}, null, "gpt-5.6-sol");
expect(out.additionalModelRequestFields).toEqual({
reasoning: { effort: "low" },
});
expect(out.systemPrompt || "").not.toContain("<thinking_mode>");
expect(out.systemPrompt || "").not.toContain("<max_thinking_length>");
});
it.each(["auto", "minimal", "ultra"])(
"keeps the legacy thinking fallback for unsupported GPT-5.6 effort %s",
(effort) => {
const out = C2K({
output_config: { effort },
messages: [{ role: "user", content: "Use legacy thinking" }],
}, null, "gpt-5.6-sol");
expect(out.additionalModelRequestFields).toBeUndefined();
expect(out.systemPrompt).toContain("<thinking_mode>enabled</thinking_mode>");
expect(out.systemPrompt).toContain("<max_thinking_length>");
}
);
it.each(["none", "off", "disabled"])(
"keeps GPT-5.6 reasoning intentionally disabled for effort %s",
(effort) => {
const out = C2K({
output_config: { effort },
messages: [{ role: "user", content: "Do not reason" }],
}, null, "gpt-5.6-sol");
expect(out.additionalModelRequestFields).toBeUndefined();
expect(out.systemPrompt || "").not.toContain("<thinking_mode>");
expect(out.systemPrompt || "").not.toContain("<max_thinking_length>");
}
);
it("keeps explicit Claude effort ahead of an injected OpenAI effort", () => {
const out = C2K({
output_config: { effort: "low" },
reasoning_effort: "high",
messages: [{ role: "user", content: "honor the client effort" }],
}, null, "gpt-5.6-sol");
expect(out.additionalModelRequestFields).toEqual({
reasoning: { effort: "low" },
});
});
it("sends Claude system as top-level systemPrompt and keeps a user-content fallback", () => {
const out = C2K({
system: "system-only instruction",

View File

@@ -1,24 +1,35 @@
// #2591 — Alibaba Intl (alicode-intl) must use the OpenAI-compatible-mode
// DashScope endpoint so standard DashScope API keys work. The previous
// coding-intl host only accepted Alibaba Coding Plan keys and rejected
// ordinary DashScope keys with "Invalid API key".
// #2591 — Alibaba Intl key types split across two hosts:
// - alicode-intl: Coding Plan keys (sk-sp-...) → coding-intl.dashscope.aliyuncs.com
// - alims-intl: standard DashScope API keys (sk-...) → dashscope-intl.aliyuncs.com/compatible-mode
// The two key types are NOT interchangeable across hosts. Split into two providers
// so each key type reaches its own host.
import { describe, it, expect } from "vitest";
import alicodeIntl from "../../open-sse/providers/registry/alicode-intl.js";
import alimsIntl from "../../open-sse/providers/registry/alims-intl.js";
describe("alicode-intl endpoint (issue #2591)", () => {
it("routes to the compatible-mode DashScope endpoint", () => {
describe("alicode-intl endpoint (Coding Plan keys)", () => {
it("routes to the coding-intl host for Coding Plan keys", () => {
expect(alicodeIntl.id).toBe("alicode-intl");
expect(alicodeIntl.transport.baseUrl).toBe(
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
"https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions"
);
});
it("does not use the coding-intl host that rejects standard keys", () => {
expect(alicodeIntl.transport.baseUrl).not.toContain("coding-intl.dashscope.aliyuncs.com");
});
it("keeps the chat/completions path and preserveCacheControl quirk", () => {
expect(alicodeIntl.transport.baseUrl).toContain("/v1/chat/completions");
expect(alicodeIntl.transport.quirks.preserveCacheControl).toBe(true);
});
});
describe("alims-intl endpoint (standard DashScope keys)", () => {
it("routes to the compatible-mode DashScope endpoint for standard keys", () => {
expect(alimsIntl.id).toBe("alims-intl");
expect(alimsIntl.transport.baseUrl).toBe(
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
);
});
it("does not use the coding-intl host that rejects standard keys", () => {
expect(alimsIntl.transport.baseUrl).not.toContain("coding-intl.dashscope.aliyuncs.com");
});
});

View File

@@ -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");
});

View File

@@ -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 });
});
});

View File

@@ -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",

View File

@@ -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);

View File

@@ -152,7 +152,8 @@ describe("Codex Refresh Token", () => {
expect(getRefreshLeadMs("claude")).toBe(4 * 60 * 60 * 1000); // 4 hours
expect(getRefreshLeadMs("iflow")).toBe(24 * 60 * 60 * 1000); // 24 hours
expect(getRefreshLeadMs("qwen")).toBe(20 * 60 * 1000); // 20 minutes
expect(getRefreshLeadMs("kimi-coding")).toBe(5 * 60 * 1000); // 5 minutes
expect(getRefreshLeadMs("kimi")).toBe(5 * 60 * 1000); // 5 minutes
expect(getRefreshLeadMs("kimi-coding")).toBe(5 * 60 * 1000); // legacy alias
expect(getRefreshLeadMs("antigravity")).toBe(5 * 60 * 1000); // 5 minutes
});

View File

@@ -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");
});
});

View File

@@ -0,0 +1,282 @@
import { describe, expect, it } from "vitest";
import {
decodeMessage,
encodeField,
encodeAgentValue,
decodeAgentValue,
encodeMcpToolDefinition,
encodeMcpTools,
decodeMcpArgs,
encodeMcpResultSuccess,
encodeMcpResultError,
encodeMcpResultToolNotFound,
} from "../../open-sse/utils/cursorProtobuf.js";
import {
isAgentCapableRequest,
buildAgentRunFrame,
} from "../../open-sse/executors/cursor.js";
// AgentService (agent.v1) codec tests — validate the production implementation
// in cursorProtobuf.js + the executor's frame builders. Pure round-trip, no network.
// Field numbers verified against Cursor's agent.proto (extracted via @oh-my-pi).
const LEN = 2;
// McpArgs.args map entry { field1: key, field2: Value }
const entry = (k, v) => Buffer.concat([
Buffer.from(encodeField(2, LEN,
Buffer.concat([Buffer.from(encodeField(1, LEN, k)), Buffer.from(encodeField(2, LEN, encodeAgentValue(v)))])
)),
]);
describe("Cursor AgentService codec (cursorProtobuf.js)", () => {
describe("google.protobuf.Value round-trip", () => {
const cases = [
["null", null],
["bool true", true],
["bool false", false],
["string", "hello"],
["integer", 42],
["float", 3.14],
["empty object", {}],
["flat object", { a: 1, b: "x", c: true }],
["nested object", { outer: { inner: [1, 2, "three"] } }],
["array of mixed", [1, "two", false, null]],
["deeply nested", { a: { b: { c: { d: 1 } } } }],
];
for (const [label, value] of cases) {
it(`encodes/decodes ${label}`, () => {
expect(decodeAgentValue(encodeAgentValue(value))).toEqual(value);
});
}
});
describe("McpToolDefinition", () => {
it("encodes name, description, input_schema (Value), provider, tool_name", () => {
const schema = { type: "object", properties: { city: { type: "string" } }, required: ["city"] };
const def = encodeMcpToolDefinition({ function: { name: "get_weather", description: "Get weather", parameters: schema } });
const msg = decodeMessage(def);
expect(Buffer.from(msg.get(1)[0].value).toString("utf8")).toBe("get_weather");
expect(Buffer.from(msg.get(2)[0].value).toString("utf8")).toBe("Get weather");
expect(Buffer.from(msg.get(4)[0].value).toString("utf8")).toBe("9router");
expect(Buffer.from(msg.get(5)[0].value).toString("utf8")).toBe("get_weather");
expect(decodeAgentValue(msg.get(3)[0].value)).toEqual(schema);
});
it("preserves nested JSON-schema types", () => {
const schema = {
type: "object",
properties: {
query: { type: "string", description: "search query" },
opts: { type: "array", items: { type: "string" } },
},
required: ["query"],
};
const def = encodeMcpToolDefinition({ function: { name: "search", parameters: schema } });
const msg = decodeMessage(def);
expect(decodeAgentValue(msg.get(3)[0].value)).toEqual(schema);
});
it("accepts flat tool shape (no .function wrapper)", () => {
const def = encodeMcpToolDefinition({ name: "noop", description: "d", inputSchema: { type: "object" } });
const msg = decodeMessage(def);
expect(Buffer.from(msg.get(1)[0].value).toString("utf8")).toBe("noop");
});
});
describe("encodeMcpTools", () => {
it("produces empty bytes for no tools", () => {
expect(encodeMcpTools([]).length).toBe(0);
expect(encodeMcpTools().length).toBe(0);
});
it("wraps multiple tool defs as repeated field 1", () => {
const tools = [
{ function: { name: "get_weather", parameters: { type: "object" } } },
{ function: { name: "calculate", parameters: { type: "object" } } },
];
const mcpTools = encodeMcpTools(tools);
const inner = decodeMessage(mcpTools);
expect(inner.get(1).length).toBe(2);
});
});
describe("McpArgs decode", () => {
it("decodes name, toolName, toolCallId, and typed args map", () => {
const argsBytes = Buffer.concat([
entry("city", "Hanoi"),
entry("count", 5),
entry("flag", true),
entry("nested", { a: [1, 2] }),
]);
const mcpArgs = Buffer.concat([
Buffer.from(encodeField(1, LEN, "get_weather")),
argsBytes,
Buffer.from(encodeField(3, LEN, "call_abc")),
Buffer.from(encodeField(5, LEN, "get_weather")),
]);
const decoded = decodeMcpArgs(mcpArgs);
expect(decoded.name).toBe("get_weather");
expect(decoded.toolName).toBe("get_weather");
expect(decoded.toolCallId).toBe("call_abc");
expect(decoded.args).toEqual({ city: "Hanoi", count: 5, flag: true, nested: { a: [1, 2] } });
});
it("handles empty args map", () => {
const mcpArgs = Buffer.concat([
Buffer.from(encodeField(1, LEN, "noop")),
Buffer.from(encodeField(5, LEN, "noop")),
]);
expect(decodeMcpArgs(mcpArgs).args).toEqual({});
});
});
describe("McpResult success", () => {
it("builds success with single text content", () => {
const bytes = encodeMcpResultSuccess({ textItems: ['{"temp":32}'], isError: false });
const msg = decodeMessage(bytes); // McpResult level
expect(msg.has(1)).toBe(true); // success variant
const success = decodeMessage(msg.get(1)[0].value);
expect(success.get(1).length).toBe(1);
expect(success.get(2)[0].value).toBe(0); // is_error=false
const item = decodeMessage(success.get(1)[0].value);
const textContent = decodeMessage(item.get(1)[0].value);
expect(Buffer.from(textContent.get(1)[0].value).toString("utf8")).toBe('{"temp":32}');
});
it("builds success with multiple text items", () => {
const bytes = encodeMcpResultSuccess({ textItems: ["line1", "line2"] });
const success = decodeMessage(decodeMessage(bytes).get(1)[0].value);
expect(success.get(1).length).toBe(2);
});
it("marks is_error=true", () => {
const bytes = encodeMcpResultSuccess({ textItems: ["fail"], isError: true });
const success = decodeMessage(decodeMessage(bytes).get(1)[0].value);
expect(success.get(2)[0].value).toBe(1);
});
});
describe("McpResult image content", () => {
it("builds image item with raw bytes + mime type", () => {
const imgBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
const bytes = encodeMcpResultSuccess({ imageItems: [{ data: imgBytes, mimeType: "image/png" }] });
const success = decodeMessage(decodeMessage(bytes).get(1)[0].value);
const item = decodeMessage(success.get(1)[0].value);
expect(item.has(2)).toBe(true); // image variant
const img = decodeMessage(item.get(2)[0].value);
expect(Buffer.from(img.get(1)[0].value)).toEqual(Buffer.from(imgBytes));
expect(Buffer.from(img.get(2)[0].value).toString("utf8")).toBe("image/png");
});
it("builds mixed text + image content", () => {
const imgBytes = new Uint8Array([1, 2, 3]);
const bytes = encodeMcpResultSuccess({ textItems: ["see image"], imageItems: [{ data: imgBytes, mimeType: "image/jpeg" }] });
const success = decodeMessage(decodeMessage(bytes).get(1)[0].value);
expect(success.get(1).length).toBe(2);
expect(decodeMessage(success.get(1)[0].value).has(1)).toBe(true); // text
expect(decodeMessage(success.get(1)[1].value).has(2)).toBe(true); // image
});
});
describe("McpResult error / toolNotFound", () => {
it("builds error result (field 2)", () => {
const bytes = encodeMcpResultError("tool crashed");
const msg = decodeMessage(bytes);
expect(msg.has(2)).toBe(true);
const err = decodeMessage(msg.get(2)[0].value);
expect(Buffer.from(err.get(1)[0].value).toString("utf8")).toBe("tool crashed");
});
it("builds toolNotFound result (field 5)", () => {
const bytes = encodeMcpResultToolNotFound("missing_tool");
const msg = decodeMessage(bytes);
expect(msg.has(5)).toBe(true);
const tnf = decodeMessage(msg.get(5)[0].value);
expect(Buffer.from(tnf.get(1)[0].value).toString("utf8")).toBe("missing_tool");
});
});
});
describe("Cursor AgentService executor helpers (cursor.js)", () => {
describe("isAgentCapableRequest", () => {
it("accepts plain text content", () => {
expect(isAgentCapableRequest({ messages: [{ role: "user", content: "hi" }] })).toBe(true);
});
it("accepts array text content", () => {
expect(isAgentCapableRequest({ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }] })).toBe(true);
});
it("accepts request with tools declared", () => {
expect(isAgentCapableRequest({ messages: [{ role: "user", content: "hi" }], tools: [{ function: { name: "t" } }] })).toBe(true);
});
it("accepts history with assistant tool_calls + tool results", () => {
expect(isAgentCapableRequest({
messages: [
{ role: "user", content: "weather?" },
{ role: "assistant", content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: "{}" } }] },
{ role: "tool", tool_call_id: "c1", content: "sunny" },
{ role: "user", content: "thanks" },
],
})).toBe(true);
});
it("rejects non-text (image) content", () => {
expect(isAgentCapableRequest({ messages: [{ role: "user", content: [{ type: "image_url" }] }] })).toBe(false);
});
it("rejects missing messages", () => {
expect(isAgentCapableRequest({})).toBe(false);
expect(isAgentCapableRequest(null)).toBe(false);
});
});
describe("buildAgentRunFrame", () => {
// buildAgentRunFrame returns a wrapped Connect-RPC frame (5-byte header + AgentClientMessage).
const unwrap = (frame) => frame.subarray(5);
it("encodes a text-only run request with system + model", () => {
const frame = unwrap(buildAgentRunFrame(
[{ role: "system", content: "be brief" }, { role: "user", content: "hi" }],
"gpt-5.2",
));
const clientMsg = decodeMessage(frame);
expect(clientMsg.has(1)).toBe(true); // run_request
const run = decodeMessage(clientMsg.get(1)[0].value);
expect(run.has(2)).toBe(true); // action
expect(run.has(9)).toBe(true); // requested_model
});
it("encodes mcp_tools (field 4) when tools are provided", () => {
const tools = [{ function: { name: "get_weather", description: "weather", parameters: { type: "object", properties: { city: { type: "string" } } } } }];
const frame = unwrap(buildAgentRunFrame([{ role: "user", content: "weather?" }], "gpt-5.2", tools));
const run = decodeMessage(decodeMessage(frame).get(1)[0].value);
expect(run.has(4)).toBe(true); // mcp_tools
const mcpTools = decodeMessage(run.get(4)[0].value);
expect(mcpTools.get(1).length).toBe(1);
});
it("omits mcp_tools when no tools provided", () => {
const frame = unwrap(buildAgentRunFrame([{ role: "user", content: "hi" }], "gpt-5.2", []));
const run = decodeMessage(decodeMessage(frame).get(1)[0].value);
expect(run.has(4)).toBe(false);
});
it("encodes conversation_history from prior turns including tool calls/results", () => {
const messages = [
{ role: "user", content: "weather in Tokyo?" },
{ role: "assistant", content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: '{"city":"Tokyo"}' } }] },
{ role: "tool", tool_call_id: "c1", content: "18C cloudy" },
{ role: "user", content: "thanks" },
];
const frame = unwrap(buildAgentRunFrame(messages, "gpt-5.2", []));
const run = decodeMessage(decodeMessage(frame).get(1)[0].value);
const action = decodeMessage(run.get(2)[0].value);
const userAction = decodeMessage(action.get(1)[0].value);
expect(userAction.has(7)).toBe(true); // conversation_history (field 7)
const history = decodeMessage(userAction.get(7)[0].value);
expect(history.get(1).length).toBeGreaterThanOrEqual(2); // prior turns
});
});
});

View File

@@ -0,0 +1,103 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
clearCursorModelCache,
parseCursorUsableModels,
resolveCursorModels,
} from "../../open-sse/services/cursorModels.js";
const originalFetch = global.fetch;
function varint(value) {
const bytes = [];
while (value >= 0x80) {
bytes.push((value & 0x7f) | 0x80);
value >>>= 7;
}
bytes.push(value);
return Uint8Array.from(bytes);
}
function field(fieldNumber, value) {
return Uint8Array.from([(fieldNumber << 3) | 2, ...varint(value.length), ...value]);
}
function text(value) {
return new TextEncoder().encode(value);
}
function concat(...parts) {
const size = parts.reduce((sum, part) => sum + part.length, 0);
const result = new Uint8Array(size);
let offset = 0;
for (const part of parts) {
result.set(part, offset);
offset += part.length;
}
return result;
}
function model(id, name) {
return field(1, concat(field(1, text(id)), field(4, text(name))));
}
describe("Cursor live model catalog", () => {
beforeEach(() => {
clearCursorModelCache();
});
afterEach(() => {
global.fetch = originalFetch;
clearCursorModelCache();
});
it("decodes the GetUsableModels protobuf response", () => {
const payload = concat(
model("default", "Auto"),
model("gpt-5.3-codex", "GPT 5.3 Codex"),
model("gpt-5.3-codex", "Duplicate"),
);
expect(parseCursorUsableModels(payload)).toEqual([
{ id: "default", name: "Auto" },
{ id: "gpt-5.3-codex", name: "GPT 5.3 Codex" },
]);
});
it("fetches the account-specific catalog and caches it", async () => {
const payload = concat(model("claude-4.6-opus", "Claude 4.6 Opus"));
global.fetch = vi.fn().mockResolvedValue(new Response(payload, { status: 200 }));
const credentials = {
accessToken: "cursor-token",
providerSpecificData: { machineId: "machine-id" },
};
await expect(resolveCursorModels(credentials)).resolves.toEqual({
models: [{ id: "claude-4.6-opus", name: "Claude 4.6 Opus" }],
});
await expect(resolveCursorModels(credentials)).resolves.toEqual({
models: [{ id: "claude-4.6-opus", name: "Claude 4.6 Opus" }],
});
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith(
"https://agent.api5.cursor.sh/agent.v1.AgentService/GetUsableModels",
expect.objectContaining({
method: "POST",
body: expect.any(Uint8Array),
headers: expect.objectContaining({
"content-type": "application/proto",
accept: "application/proto",
}),
}),
);
});
it("fails open when the Cursor catalog request fails", async () => {
global.fetch = vi.fn().mockResolvedValue(new Response("no", { status: 403 }));
await expect(resolveCursorModels({
accessToken: "cursor-token",
providerSpecificData: { machineId: "machine-id" },
})).resolves.toBeNull();
});
});

View File

@@ -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,
});
});
});

View File

@@ -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 <cwd> 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: `<environment_context>\n <cwd>${workspace}</cwd>\n</environment_context>\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");
});
});

View File

@@ -0,0 +1,91 @@
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",
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();
});
});

View File

@@ -0,0 +1,91 @@
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,
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();
});
});

View File

@@ -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");
});
});

View File

@@ -0,0 +1,176 @@
import { describe, expect, it } from "vitest";
import {
applyGrokBuildConfig,
getGrokSubagentSlot,
parseGrokBuildConfig,
resetGrokBuildConfig,
} from "../../src/lib/grokBuildConfig.js";
const BASE_CONFIG = `[cli]
installer = "internal"
[ui]
yolo = false
[models]
default = "grok-4.5"
default_reasoning_effort = "high"
[subagents]
enabled = true
[subagents.models]
general-purpose = "grok-4.5"
explore = "grok-build"
plan = "grok-4.5"
[mcp_servers.example]
url = "https://example.com/mcp"
enabled = true
`;
const APPLY_INPUT = {
baseUrl: "http://127.0.0.1:20128/v1",
apiKey: "sk-test",
model: "cx/gpt-5.6-sol",
contextWindow: 400000,
subagentModels: {
"general-purpose": { model: "cc/claude-sonnet-5", contextWindow: 1000000 },
explore: { model: "gemini/gemini-3-flash", contextWindow: 1048576 },
},
};
describe("grokBuildConfig", () => {
it("creates independent main and per-type subagent model slots", () => {
const result = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
const parsed = parseGrokBuildConfig(result);
expect(parsed.default).toBe("9router");
expect(parsed.model).toMatchObject({
model: "cx/gpt-5.6-sol",
base_url: "http://127.0.0.1:20128/v1",
context_window: 400000,
});
expect(parsed.subagentMappings).toMatchObject({
"general-purpose": "9router-general-purpose",
explore: "9router-explore",
plan: "grok-4.5",
});
expect(parsed.subagentModels["general-purpose"]).toMatchObject({
model: "cc/claude-sonnet-5",
context_window: 1000000,
});
expect(parsed.subagentModels.explore).toMatchObject({
model: "gemini/gemini-3-flash",
context_window: 1048576,
});
expect(parsed.subagentModels.plan).toBeNull();
});
it("preserves unrelated config sections", () => {
const result = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
expect(result).toContain("[cli]\ninstaller = \"internal\"");
expect(result).toContain("[ui]\nyolo = false");
expect(result).toContain("default_reasoning_effort = \"high\"");
expect(result).toContain("[mcp_servers.example]");
expect(result).toContain("url = \"https://example.com/mcp\"");
});
it("is idempotent and updates owned slots without duplicate sections", () => {
let result = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
result = applyGrokBuildConfig(result, {
...APPLY_INPUT,
model: "cc/claude-opus-4.8",
contextWindow: 1000000,
subagentModels: {
...APPLY_INPUT.subagentModels,
explore: { model: "mimo/mimo", contextWindow: 262144 },
},
});
expect(result.match(/^\[model\.9router\]$/gm)).toHaveLength(1);
expect(result.match(/^\[model\.9router-general-purpose\]$/gm)).toHaveLength(1);
expect(result.match(/^\[model\.9router-explore\]$/gm)).toHaveLength(1);
expect(result.match(/^# 9router-prev-subagent-explore/gm)).toHaveLength(1);
expect(parseGrokBuildConfig(result).model).toMatchObject({
model: "cc/claude-opus-4.8",
context_window: 1000000,
});
expect(parseGrokBuildConfig(result).subagentModels.explore).toMatchObject({
model: "mimo/mimo",
context_window: 262144,
});
});
it("blank override restores previous subagent mapping and removes owned slot", () => {
let result = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
result = applyGrokBuildConfig(result, {
...APPLY_INPUT,
subagentModels: {
"general-purpose": APPLY_INPUT.subagentModels["general-purpose"],
// explore omitted => inherit / restore previous
},
});
const parsed = parseGrokBuildConfig(result);
expect(parsed.subagentMappings.explore).toBe("grok-build");
expect(parsed.subagentModels.explore).toBeNull();
expect(result).not.toContain("[model.9router-explore]");
expect(parsed.subagentMappings["general-purpose"]).toBe("9router-general-purpose");
});
it("reset restores previous default and all previous subagent mappings", () => {
const applied = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
const reset = resetGrokBuildConfig(applied);
const parsed = parseGrokBuildConfig(reset);
expect(parsed.default).toBe("grok-4.5");
expect(parsed.model).toBeNull();
expect(parsed.subagentMappings).toEqual({
"general-purpose": "grok-4.5",
explore: "grok-build",
plan: "grok-4.5",
});
expect(reset).not.toContain("[model.9router-");
expect(reset).not.toContain("9router-prev-");
expect(reset).toContain("[mcp_servers.example]");
});
it("removes mappings that were originally unset", () => {
const config = `[models]\ndefault = "grok-build"\n\n[mcp_servers.x]\nenabled = true\n`;
const applied = applyGrokBuildConfig(config, {
...APPLY_INPUT,
subagentModels: {
plan: { model: "cc/claude-sonnet-5", contextWindow: 1000000 },
},
});
const reset = resetGrokBuildConfig(applied);
expect(parseGrokBuildConfig(applied).subagentMappings.plan).toBe("9router-plan");
expect(parseGrokBuildConfig(reset).subagentMappings.plan).toBeNull();
expect(reset).not.toContain("[subagents.models]");
expect(reset).toContain("[mcp_servers.x]");
});
it("legacy callers without subagentModels leave existing overrides untouched", () => {
const applied = applyGrokBuildConfig(BASE_CONFIG, APPLY_INPUT);
const updatedMainOnly = applyGrokBuildConfig(applied, {
baseUrl: APPLY_INPUT.baseUrl,
apiKey: APPLY_INPUT.apiKey,
model: "gemini/gemini-3.1-pro",
contextWindow: 1048576,
});
const parsed = parseGrokBuildConfig(updatedMainOnly);
expect(parsed.model.model).toBe("gemini/gemini-3.1-pro");
expect(parsed.subagentMappings.explore).toBe("9router-explore");
expect(parsed.subagentModels.explore.model).toBe("gemini/gemini-3-flash");
});
it("returns stable slot names only for supported subagent types", () => {
expect(getGrokSubagentSlot("general-purpose")).toBe("9router-general-purpose");
expect(getGrokSubagentSlot("explore")).toBe("9router-explore");
expect(getGrokSubagentSlot("plan")).toBe("9router-plan");
expect(getGrokSubagentSlot("unknown")).toBeNull();
});
});

View File

@@ -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);
});
});

View File

@@ -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)", () => {

View File

@@ -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");
});
});

View File

@@ -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,
});
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -0,0 +1,64 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@/lib/usageDb.js", () => ({
appendRequestLog: vi.fn(async () => {}),
saveRequestDetail: vi.fn(async () => {}),
saveRequestUsage: vi.fn(async () => {})
}));
const { FORMATS } = await import("../../open-sse/translator/formats.js");
const {
handleForcedSSEToJson,
parseSSEToOpenAIResponse
} = await import("../../open-sse/handlers/chatCore/sseToJsonHandler.js");
describe("Kiro non-streaming error propagation", () => {
it("prefers a terminal SSE error over earlier semantic chunks", () => {
const raw = [
'data: {"choices":[{"delta":{"content":"partial"},"finish_reason":null}]}',
'data: {"error":{"message":"Kiro transport failed","code":"kiro_missing_terminal"}}',
"data: [DONE]"
].join("\n\n");
expect(parseSSEToOpenAIResponse(raw, "kiro")).toEqual({
error: {
message: "Kiro transport failed",
code: "kiro_missing_terminal"
}
});
});
it("returns 502 instead of collapsing a failed Kiro SSE stream into stop", async () => {
const encoder = new TextEncoder();
const raw = [
'data: {"choices":[{"delta":{"content":"partial"},"finish_reason":null}]}',
'data: {"error":{"message":"Kiro stream ended incompletely","code":"kiro_missing_terminal"}}',
"data: [DONE]",
""
].join("\n\n");
const result = await handleForcedSSEToJson({
providerResponse: new Response(new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(raw));
controller.close();
}
}), { headers: { "content-type": "text/event-stream" } }),
sourceFormat: FORMATS.OPENAI,
provider: "kiro",
model: "kr/claude-opus-4.8",
body: { model: "kr/claude-opus-4.8", messages: [] },
stream: false,
requestStartTime: Date.now(),
connectionId: "test-connection",
clientRawRequest: { endpoint: "/v1/chat/completions" },
trackDone: vi.fn(),
appendLog: vi.fn()
});
const json = await result.response.json();
expect(result.success).toBe(false);
expect(result.response.status).toBe(502);
expect(json.error.message).toContain("Kiro stream ended incompletely");
expect(json).not.toHaveProperty("choices");
});
});

View File

@@ -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/
);
});
});

View File

@@ -0,0 +1,778 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const fetchMock = vi.fn();
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: (...args) => fetchMock(...args)
}));
const { KiroExecutor } = await import("../../open-sse/executors/kiro.js");
const encoder = new TextEncoder();
const credentials = {
accessToken: "test-token",
providerSpecificData: { kiroToolCallRepair: true }
};
function crc32(bytes) {
let crc = 0xffffffff;
for (const byte of bytes) {
crc ^= byte;
for (let bit = 0; bit < 8; bit++) {
crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
}
}
return (crc ^ 0xffffffff) >>> 0;
}
function encodeHeader(name, value) {
const nameBytes = encoder.encode(name);
const valueBytes = encoder.encode(value);
const bytes = new Uint8Array(1 + nameBytes.length + 3 + valueBytes.length);
let offset = 0;
bytes[offset++] = nameBytes.length;
bytes.set(nameBytes, offset);
offset += nameBytes.length;
bytes[offset++] = 7;
new DataView(bytes.buffer).setUint16(offset, valueBytes.length, false);
offset += 2;
bytes.set(valueBytes, offset);
return bytes;
}
function concat(chunks) {
const output = new Uint8Array(chunks.reduce((size, chunk) => size + chunk.byteLength, 0));
let offset = 0;
for (const chunk of chunks) {
output.set(chunk, offset);
offset += chunk.byteLength;
}
return output;
}
function frameFromEntries(entries, payload) {
const headers = concat(entries.map(([name, value]) => encodeHeader(name, value)));
const payloadBytes = encoder.encode(JSON.stringify(payload));
const totalLength = 12 + headers.byteLength + payloadBytes.byteLength + 4;
const frame = new Uint8Array(totalLength);
const view = new DataView(frame.buffer);
view.setUint32(0, totalLength, false);
view.setUint32(4, headers.byteLength, false);
frame.set(headers, 12);
frame.set(payloadBytes, 12 + headers.byteLength);
return checksum(frame);
}
function frame(eventType, payload) {
return frameFromEntries([[":event-type", eventType]], payload);
}
function checksum(bytes) {
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
view.setUint32(8, crc32(bytes.subarray(0, 8)), false);
view.setUint32(bytes.byteLength - 4, crc32(bytes.subarray(0, bytes.byteLength - 4)), false);
return bytes;
}
function response(frames, status = 200) {
return new Response(new ReadableStream({
start(controller) {
for (const value of frames) controller.enqueue(value);
controller.close();
}
}), { status, statusText: status === 200 ? "OK" : "Upstream Error" });
}
function controlledResponse(frames = []) {
let controller;
const value = new Response(new ReadableStream({
start(streamController) {
controller = streamController;
for (const item of frames) controller.enqueue(item);
}
}), { status: 200 });
return {
value,
enqueue(item) {
controller.enqueue(item);
},
close() {
controller.close();
}
};
}
async function text(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let output = "";
while (true) {
const { done, value } = await reader.read();
if (done) return output + decoder.decode();
output += decoder.decode(value, { stream: true });
}
}
async function execute(executor = new KiroExecutor(), overrides = {}) {
return executor.execute({
model: "kr/claude-opus-4.8",
body: { systemPrompt: "base", conversationState: {} },
stream: true,
credentials,
...overrides
});
}
beforeEach(() => {
fetchMock.mockReset();
delete process.env.KIRO_TOOL_CALL_REPAIR_BUFFER_MAX_BYTES;
delete process.env.KIRO_TOOL_CALL_REPAIR_TTFT_TIMEOUT_MS;
delete process.env.KIRO_TOOL_CALL_REPAIR_STALL_TIMEOUT_MS;
});
afterEach(() => {
delete process.env.KIRO_TOOL_CALL_REPAIR_BUFFER_MAX_BYTES;
delete process.env.KIRO_TOOL_CALL_REPAIR_TTFT_TIMEOUT_MS;
delete process.env.KIRO_TOOL_CALL_REPAIR_STALL_TIMEOUT_MS;
});
describe("Kiro terminal integrity recovery", () => {
it("keeps semantic output private behind a heartbeat until clean EOF", async () => {
const upstream = controlledResponse([
frame("assistantResponseEvent", { content: "private until validated" })
]);
fetchMock.mockResolvedValueOnce(upstream.value);
const result = await execute();
const reader = result.response.body.getReader();
expect(new TextDecoder().decode((await reader.read()).value)).toBe(": kiro-validation\n\n");
let settled = false;
const semantic = reader.read().then((value) => {
settled = true;
return value;
});
await Promise.resolve();
expect(settled).toBe(false);
upstream.close();
expect(new TextDecoder().decode((await semantic).value)).toContain("private until validated");
await reader.cancel();
});
it("accepts CLI-compatible text and usage frames at clean EOF without messageStop", async () => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Complete answer." }),
frame("meteringEvent", { usage: 2, unit: "credit" }),
frame("contextUsageEvent", { contextUsagePercentage: 10 })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("Complete answer.");
expect(body).toContain('"finish_reason":"stop"');
expect(body).toContain('"kiro_credits":2');
});
it("parses frames split across chunks and multiple frames in one chunk", async () => {
const first = frame("assistantResponseEvent", { content: "split " });
const second = frame("assistantResponseEvent", { content: "boundaries" });
const combined = concat([first, second]);
fetchMock.mockResolvedValueOnce(new Response(new ReadableStream({
start(controller) {
controller.enqueue(combined.slice(0, 9));
controller.enqueue(combined.slice(9, first.byteLength + 5));
controller.enqueue(combined.slice(first.byteLength + 5));
controller.close();
}
})));
const body = await (await execute()).response.text();
expect(body).toContain('"content":"split "');
expect(body).toContain('"content":"boundaries"');
expect(body).toContain('"finish_reason":"stop"');
});
it("accepts messageStop without semantic output as explicit completion", async () => {
fetchMock.mockResolvedValueOnce(response([frame("messageStopEvent", {})]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain('"finish_reason":"stop"');
expect(body).not.toContain("kiro_missing_terminal");
});
it.each(["...", "…"])("repairs exact ellipsis final %s without leaking it", async (ellipsis) => {
fetchMock
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: ellipsis })]))
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: "Recovered answer." })]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Recovered answer.");
expect(body).not.toContain(`"content":"${ellipsis}"`);
});
it.each([
"接下來我只再確認部署結果。",
"我會重新抓取最新日誌並確認結果。",
"目前證據顯示只在 **03:48:30–03:49:00 TPE** 出現少量 NonKA 504;主池 106/106、副池 50/50,且兩池都沒有重啟。最後補查 504 access log,確認 host/路徑與是否為集中流量。",
"Next I'll verify the deployment logs.",
"Let me check the remaining failures."
])("repairs conservative future-action final: %s", async (progress) => {
fetchMock
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: progress })]))
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: "Verification completed." })]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Verification completed.");
expect(body).not.toContain(progress);
});
it.each([
"Working...",
"I'll check the logs. They show no errors and deployment succeeded.",
"Let me check: status is 200 and the checksum matches abc123.",
"我會檢查版本。版本是 1.2.3。",
"接下來請你先批准部署,我會等待你的確認。",
"已完成驗證,所有測試均通過。",
"目前證據顯示只有少量 504,且主副池均未重啟。",
"目前證據顯示只有少量 504。最後補查結果顯示沒有集中流量。",
"目前證據顯示只有少量 504。最後補查,結果顯示沒有集中流量。",
"目前證據顯示只有少量 504。最後補查:結果顯示沒有集中流量。",
"目前證據顯示只有少量 504。最後補查 504 access log,結果顯示沒有集中流量。",
"目前證據顯示只有少量 504。最後補查 504 access log,確認 host/路徑與有無集中流量:無集中流量。",
"目前證據顯示只有少量 504。最後補查 504 access log,確認 host/路徑與是否為集中流量(答案是否定的)。",
"目前證據顯示只有少量 504。最後補充兩點已確認的結果。",
"The verification is complete and all tests passed."
])("does not retry legitimate final: %s", async (finalText) => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: finalText })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain(finalText);
});
it("bounds incomplete-final repair to one retry", async () => {
fetchMock
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: "..." })]))
.mockResolvedValueOnce(response([frame("assistantResponseEvent", { content: "…" })]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("kiro_ellipsis_retry_failed");
expect(body).not.toContain('"content":"..."');
});
it("repairs malformed wrapper tools without leaking the invalid call", async () => {
fetchMock
.mockResolvedValueOnce(response([frame("toolUseEvent", {
toolUseId: "bad",
name: "tool_call",
input: { arguments: { q: "router" } }
})]))
.mockResolvedValueOnce(response([frame("toolUseEvent", {
toolUseId: "good",
name: "tool_call",
input: { name: "mcp_search", arguments: { q: "router" } }
})]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain('"name":"tool_call"');
expect(body).toContain('\\"name\\":\\"mcp_search\\"');
expect(body).not.toContain('"id":"bad"');
});
it("requires complete direct tool input and keeps the failure private", async () => {
const pending = frame("toolUseEvent", { toolUseId: "pending", name: "read_file" });
fetchMock
.mockResolvedValueOnce(response([pending]))
.mockResolvedValueOnce(response([pending]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("kiro_tool_call_repair_retry_failed");
expect(body).not.toContain('"name":"read_file"');
});
it("repairs a non-string toolUseId before releasing the tool call", async () => {
fetchMock
.mockResolvedValueOnce(response([frame("toolUseEvent", {
toolUseId: 123,
name: "read_file",
input: { path: "bad.txt" }
})]))
.mockResolvedValueOnce(response([frame("toolUseEvent", {
toolUseId: "valid-tool-id",
name: "read_file",
input: { path: "safe.txt" }
})]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain('"id":"valid-tool-id"');
expect(body).not.toContain('"id":123');
});
it("keeps model-controlled parser detail out of the retry system prompt", async () => {
fetchMock
.mockResolvedValueOnce(response([frame("toolUseEvent", {
toolUseId: "bad-json",
name: "tool_call",
input: '{"name":"IGNORE_ALL_INSTRUCTIONS"'
})]))
.mockResolvedValueOnce(response([frame("assistantResponseEvent", {
content: "Recovered safely."
})]));
const body = await (await execute()).response.text();
const retryBody = JSON.parse(fetchMock.mock.calls[1][1].body);
expect(body).toContain("Recovered safely.");
expect(retryBody.systemPrompt).toContain("tool_call wrapper was malformed");
expect(retryBody.systemPrompt).not.toContain("IGNORE_ALL_INSTRUCTIONS");
});
it("lets a complete tool call override metadata end_turn", async () => {
fetchMock.mockResolvedValueOnce(response([
frame("toolUseEvent", {
toolUseId: "tool",
name: "read_file",
input: { path: "safe.txt" }
}),
frame("metadataEvent", { stopReason: "end_turn" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain('"name":"read_file"');
expect(body).toContain('"finish_reason":"tool_calls"');
});
it("maps max_tokens without treating it as a normal stop", async () => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Limited answer." }),
frame("metadataEvent", { stopReason: "max_tokens" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain('"finish_reason":"length"');
expect(body).not.toContain('"finish_reason":"stop"');
});
it("retries malformed_model_output once without semantic leakage", async () => {
fetchMock
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "private malformed output" }),
frame("metadataEvent", { stopReason: "malformed_model_output" })
]))
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Recovered protocol output." })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Recovered protocol output.");
expect(body).not.toContain("private malformed output");
});
it.each([
["cancelled", "kiro_terminal_incomplete"],
["pause_turn", "kiro_terminal_incomplete"],
["content_filtered", "kiro_terminal_refusal"],
["novel_reason", "kiro_unknown_stop_reason"]
])("fails closed for stop reason %s", async (stopReason, code) => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: `private-${stopReason}` }),
frame("metadataEvent", { stopReason })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain(code);
expect(body).not.toContain(`private-${stopReason}`);
expect(body).not.toContain('"finish_reason":"stop"');
});
it.each([
[
frame("messageStopEvent", { stopReason: "content_filtered" }),
frame("metadataEvent", { stopReason: "end_turn" })
],
[
frame("metadataEvent", { stopReason: "end_turn" }),
frame("messageStopEvent", { stopReason: "content_filtered" })
]
])("preserves the most restrictive conflicting stop reason", async (...stopFrames) => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "private filtered output" }),
...stopFrames
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("kiro_terminal_refusal");
expect(body).not.toContain("private filtered output");
});
it("prefers a non-retryable terminal reason over an earlier retryable reason", async () => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "private malformed output" }),
frame("metadataEvent", { stopReason: "malformed_model_output" }),
frame("messageStopEvent", { stopReason: "cancelled" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("kiro_terminal_incomplete");
expect(body).toContain('"stop_reason":"cancelled"');
expect(body).not.toContain("private malformed output");
});
it("preserves an authoritative refusal returned by the bounded retry", async () => {
fetchMock
.mockResolvedValueOnce(response([]))
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "private filtered retry" }),
frame("metadataEvent", { stopReason: "content_filtered" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("kiro_terminal_refusal");
expect(body).not.toContain("kiro_missing_terminal_retry_failed");
expect(body).not.toContain("private filtered retry");
});
it.each([
["max_tokens", "kiro_terminal_incomplete"],
["cancelled", "kiro_terminal_incomplete"],
["content_filtered", "kiro_terminal_refusal"],
["novel_reason", "kiro_unknown_stop_reason"]
])("does not let a valid tool override failure stop reason %s", async (stopReason, code) => {
fetchMock.mockResolvedValueOnce(response([
frame("toolUseEvent", {
toolUseId: "blocked-tool",
name: "read_file",
input: { path: "secret.txt" }
}),
frame("metadataEvent", { stopReason })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain(code);
expect(body).not.toContain('"name":"read_file"');
});
it.each(["content_filtered", "cancelled", "max_tokens"])(
"classifies failure %s before validating a malformed deferred tool",
async (stopReason) => {
fetchMock.mockResolvedValueOnce(response([
frame("toolUseEvent", { toolUseId: "bad-tool", name: "read_file" }),
frame("metadataEvent", { stopReason })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain(stopReason === "content_filtered"
? "kiro_terminal_refusal"
: "kiro_terminal_incomplete");
expect(body).not.toContain("kiro_tool_call_repair_retry_failed");
expect(body).not.toContain('"name":"read_file"');
}
);
it.each([
["content_filtered", [frame("toolUseEvent", {
toolUseId: 123,
name: "read_file",
input: { path: "bad.txt" }
})], "kiro_terminal_refusal"],
["cancelled", [frame("toolUseEvent", {
toolUseId: "missing-name",
input: { path: "bad.txt" }
})], "kiro_terminal_incomplete"],
["max_tokens", [
frame("toolUseEvent", { toolUseId: "changing", name: "read_file" }),
frame("toolUseEvent", { toolUseId: "changing", name: "write_file" })
], "kiro_terminal_incomplete"]
])("continues past eager tool-shape errors to authoritative stop %s", async (stopReason, toolFrames, code) => {
fetchMock.mockResolvedValueOnce(response([
...toolFrames,
frame("metadataEvent", { stopReason })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain(code);
expect(body).not.toContain("kiro_tool_call_repair_retry_failed");
expect(body).not.toContain('"tool_calls"');
});
it("retries a TTFT timeout once while preserving cancellation semantics", async () => {
process.env.KIRO_TOOL_CALL_REPAIR_TTFT_TIMEOUT_MS = "1";
fetchMock
.mockResolvedValueOnce(controlledResponse().value)
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Recovered after timeout." })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Recovered after timeout.");
});
it("treats validated non-semantic frames as watchdog activity", async () => {
process.env.KIRO_TOOL_CALL_REPAIR_TTFT_TIMEOUT_MS = "30";
process.env.KIRO_TOOL_CALL_REPAIR_STALL_TIMEOUT_MS = "30";
const upstream = controlledResponse();
fetchMock.mockResolvedValueOnce(upstream.value);
setTimeout(() => upstream.enqueue(frame("meteringEvent", { usage: 1 })), 20);
setTimeout(() => upstream.enqueue(frame("contextUsageEvent", { contextUsagePercentage: 5 })), 40);
setTimeout(() => {
upstream.enqueue(frame("assistantResponseEvent", { content: "Completed after active frames." }));
upstream.close();
}, 60);
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("Completed after active frames.");
});
it("retries a response-body read failure once", async () => {
fetchMock
.mockResolvedValueOnce(new Response(new ReadableStream({
start(controller) {
controller.error(new Error("socket reset"));
}
})))
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Recovered after read failure." })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Recovered after read failure.");
expect(body).not.toContain("socket reset");
});
it.each([
["message CRC", () => {
const corrupt = frame("assistantResponseEvent", { content: "corrupt CRC" });
corrupt[corrupt.byteLength - 1] ^= 0xff;
return [corrupt];
}],
["prelude CRC", () => {
const corrupt = frame("assistantResponseEvent", { content: "corrupt prelude" });
corrupt[8] ^= 0xff;
return [corrupt];
}],
["truncated frame", () => {
const truncated = frame("assistantResponseEvent", { content: "truncated" });
return [truncated.slice(0, -3)];
}],
["out-of-bounds headers", () => {
const corrupt = frame("assistantResponseEvent", { content: "bad headers" });
new DataView(corrupt.buffer).setUint32(4, corrupt.byteLength - 15, false);
return [checksum(corrupt)];
}],
["duplicate headers", () => [
frameFromEntries([
[":event-type", "assistantResponseEvent"],
[":event-type", "metadataEvent"]
], { content: "duplicate" })
]]
])("retries %s and releases only the valid attempt", async (_name, invalidFrames) => {
fetchMock
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "must stay private" }),
...invalidFrames()
]))
.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "Recovered after validation." })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(body).toContain("Recovered after validation.");
expect(body).not.toContain("must stay private");
});
it("reports corrupt-frame provenance when the bounded retry also fails", async () => {
const corruptFrame = () => {
const corrupt = frame("assistantResponseEvent", { content: "corrupt" });
corrupt[corrupt.byteLength - 1] ^= 0xff;
return corrupt;
};
fetchMock
.mockResolvedValueOnce(response([corruptFrame()]))
.mockResolvedValueOnce(response([corruptFrame()]));
const body = await (await execute()).response.text();
expect(body).toContain("kiro_missing_terminal_retry_failed");
expect(body).toContain('"terminal_provenance":"corrupt_eventstream_frame"');
expect(body).toContain('"transport_state":"corrupt_frame"');
});
it("caps diagnostic event-type cardinality", async () => {
let terminal;
const executor = new KiroExecutor();
const frames = Array.from({ length: 100 }, (_, index) =>
frame(`unknownEvent${index}`, { index })
);
frames.push(frame("assistantResponseEvent", { content: "done" }));
const transformed = executor.transformEventStreamToSSE(
response(frames),
"kr/claude-opus-4.8",
{ onTerminalState: (value) => { terminal = value; } }
);
await transformed.text();
expect(terminal.event_counts).toEqual({
other: 100,
assistantResponseEvent: 1
});
});
it("rejects a raw chunk before concatenating beyond the protocol bound", async () => {
let terminal;
const executor = new KiroExecutor();
const transformed = executor.transformEventStreamToSSE(
response([new Uint8Array(65)]),
"kr/claude-opus-4.8",
{
maxRawBytes: 64,
onTerminalState: (value) => { terminal = value; }
}
);
const body = await transformed.text();
expect(body).toContain("buffered bytes exceed the protocol bound");
expect(terminal.terminal_provenance).toBe("corrupt_eventstream_frame");
});
it.each(["error", "exception"])("propagates EventStream %s without retry or leakage", async (messageType) => {
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "must stay private" }),
frameFromEntries([
[":message-type", messageType],
...(messageType === "exception" ? [[":exception-type", "InternalServerException"]] : [])
], { message: "upstream failed" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("kiro_upstream_eventstream_error");
expect(body).toContain("upstream failed");
expect(body).not.toContain("must stay private");
});
it("surfaces retry HTTP failures as SSE after heartbeat commits headers", async () => {
fetchMock
.mockResolvedValueOnce(response([]))
.mockResolvedValueOnce(new Response("unauthorized", {
status: 401,
statusText: "Unauthorized"
}));
const result = await execute();
const body = await result.response.text();
expect(result.response.status).toBe(200);
expect(body).toContain("kiro_integrity_retry_upstream_error");
expect(body).toContain("unauthorized");
});
it("bounds the retry HTTP error body", async () => {
fetchMock
.mockResolvedValueOnce(response([]))
.mockResolvedValueOnce(new Response(`error-start-${"x".repeat(10_000)}-error-tail`, {
status: 401,
statusText: "Unauthorized"
}));
const body = await (await execute()).response.text();
expect(body).toContain("error-start-");
expect(body).not.toContain("error-tail");
expect(body.length).toBeLessThan(5000);
});
it("propagates cancellation while validation is waiting for EOF", async () => {
const upstream = controlledResponse([
frame("assistantResponseEvent", { content: "waiting" })
]);
fetchMock.mockResolvedValueOnce(upstream.value);
const abort = new AbortController();
const result = await execute(new KiroExecutor(), { signal: abort.signal });
const reader = result.response.body.getReader();
await reader.read();
abort.abort("client cancelled");
await expect(reader.read()).rejects.toMatchObject({ name: "AbortError" });
});
it("fails safely when the private gate exceeds its configured bound", async () => {
process.env.KIRO_TOOL_CALL_REPAIR_BUFFER_MAX_BYTES = "8";
fetchMock.mockResolvedValueOnce(response([
frame("assistantResponseEvent", { content: "larger than eight bytes" })
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("integrity buffer exceeded");
expect(body).not.toContain("larger than eight bytes");
});
it("counts deferred tool fragments against the private memory bound", async () => {
process.env.KIRO_TOOL_CALL_REPAIR_BUFFER_MAX_BYTES = "128";
fetchMock.mockResolvedValueOnce(response([
frame("toolUseEvent", {
toolUseId: "large-tool",
name: "read_file",
input: { path: "x".repeat(200) }
})
]));
const body = await (await execute()).response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(body).toContain("kiro_integrity_buffer_exceeded");
expect(body).not.toContain('"name":"read_file"');
});
});

View File

@@ -32,10 +32,23 @@ function createMockFrame(eventType, payloadObj) {
offset += headerValueBytes.length;
buffer.set(payloadBytes, offset);
view.setUint32(8, crc32(buffer.subarray(0, 8)), false);
view.setUint32(totalLength - 4, crc32(buffer.subarray(0, totalLength - 4)), false);
return buffer;
}
function crc32(bytes) {
let crc = 0xffffffff;
for (const byte of bytes) {
crc ^= byte;
for (let bit = 0; bit < 8; bit++) {
crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
}
}
return (crc ^ 0xffffffff) >>> 0;
}
async function readAllSSE(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
@@ -130,14 +143,16 @@ describe("KiroExecutor thinking tag stripping", () => {
expect(contentChunks.length).toBe(0);
});
it("emits a terminal chunk at messageStop before the upstream stream closes", async () => {
it("waits for clean EOF before emitting stop after messageStop", async () => {
const executor = new KiroExecutor();
const f1 = createMockFrame("assistantResponseEvent", { content: "OK" });
const f2 = createMockFrame("messageStopEvent", {});
let upstreamController;
const readableStream = new ReadableStream({
start(controller) {
upstreamController = controller;
controller.enqueue(f1);
controller.enqueue(f2);
}
@@ -147,11 +162,16 @@ describe("KiroExecutor thinking tag stripping", () => {
const reader = transformedResponse.body.getReader();
const decoder = new TextDecoder();
let output = "";
for (let i = 0; i < 4 && !output.includes("\"finish_reason\":\"stop\""); i++) {
const { value } = await readNextWithTimeout(reader);
output += decoder.decode(value, { stream: true });
const { value } = await readNextWithTimeout(reader);
output += decoder.decode(value, { stream: true });
expect(output).not.toContain("\"finish_reason\":\"stop\"");
upstreamController.close();
while (!output.includes("\"finish_reason\":\"stop\"")) {
const { value: nextValue, done } = await readNextWithTimeout(reader);
if (done) break;
output += decoder.decode(nextValue, { stream: true });
}
await reader.cancel();
expect(output).toContain("\"finish_reason\":\"stop\"");
});

View File

@@ -316,6 +316,102 @@ describe("openaiToKiroRequest", () => {
});
});
it.each([
["high", "gpt-5.6-sol"],
["medium", "kiro/gpt-5.6-terra"],
["low", "gpt-5.6-luna"],
])("maps GPT-5.6 reasoning.effort %s without legacy prompt tags", (effort, model) => {
const body = {
reasoning: { effort },
messages: [{ role: "user", content: "Use the requested effort" }]
};
const result = openaiToKiroRequest(model, body, true, {});
expect(result.additionalModelRequestFields).toEqual({
reasoning: { effort },
});
expect(systemPromptOf(result)).not.toContain("<thinking_mode>");
expect(systemPromptOf(result)).not.toContain("<max_thinking_length>");
expect(contentOf(result)).not.toContain("<thinking_mode>");
expect(contentOf(result)).not.toContain("<max_thinking_length>");
});
it.each([
["xhigh", "gpt-5.6-terra", "xhigh"],
["max", "gpt-5.6-sol", "xhigh"],
])("preserves GPT-5.6 effort %s as supported wire effort %s", (effort, model, wireEffort) => {
const body = {
reasoning: { effort },
messages: [{ role: "user", content: "Use extended effort" }]
};
const result = openaiToKiroRequest(model, body, true, {});
expect(result.additionalModelRequestFields).toEqual({
reasoning: { effort: wireEffort },
});
expect(systemPromptOf(result)).not.toContain("<thinking_mode>");
expect(systemPromptOf(result)).not.toContain("<max_thinking_length>");
});
it("omits GPT-5.6 effort fields and legacy prompt tags when effort is absent", () => {
const body = {
messages: [{ role: "user", content: "No explicit reasoning effort" }]
};
const result = openaiToKiroRequest("gpt-5.6-sol", body, true, {});
expect(result.additionalModelRequestFields).toBeUndefined();
expect(systemPromptOf(result)).not.toContain("<thinking_mode>");
expect(systemPromptOf(result)).not.toContain("<max_thinking_length>");
});
it.each(["auto", "minimal", "ultra"])(
"keeps the legacy thinking fallback for unsupported GPT-5.6 effort %s",
(effort) => {
const body = {
reasoning: { effort },
messages: [{ role: "user", content: "Use legacy thinking" }]
};
const result = openaiToKiroRequest("gpt-5.6-luna", body, true, {});
expect(result.additionalModelRequestFields).toBeUndefined();
expect(systemPromptOf(result)).toContain("<thinking_mode>enabled</thinking_mode>");
expect(systemPromptOf(result)).toContain("<max_thinking_length>");
}
);
it.each(["none", "off", "disabled"])(
"keeps GPT-5.6 reasoning intentionally disabled for effort %s",
(effort) => {
const body = {
reasoning: { effort },
messages: [{ role: "user", content: "Do not reason" }]
};
const result = openaiToKiroRequest("gpt-5.6-luna", body, true, {});
expect(result.additionalModelRequestFields).toBeUndefined();
expect(systemPromptOf(result)).not.toContain("<thinking_mode>");
expect(systemPromptOf(result)).not.toContain("<max_thinking_length>");
}
);
it("keeps the thinking-alias fallback when GPT effort is blank", () => {
const body = {
reasoning: { effort: "" },
messages: [{ role: "user", content: "Use the thinking alias" }]
};
const result = openaiToKiroRequest("gpt-5.6-sol-thinking", body, true, {});
expect(result.additionalModelRequestFields).toBeUndefined();
expect(systemPromptOf(result)).toContain("<thinking_mode>enabled</thinking_mode>");
expect(systemPromptOf(result)).toContain("<max_thinking_length>");
});
it("does not send additionalModelRequestFields for legacy Kiro model ids", () => {
const body = {
reasoning_effort: "high",
@@ -328,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",

View File

@@ -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");
});
});

View File

@@ -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();
});
});

View File

@@ -0,0 +1,39 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
describe("cloudflared PID ownership", () => {
let dataDir;
beforeEach(() => {
dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-tunnel-pid-"));
process.env.DATA_DIR = dataDir;
vi.resetModules();
});
afterEach(() => {
delete process.env.DATA_DIR;
fs.rmSync(dataDir, { recursive: true, force: true });
});
it("does not let an old child clear its successor PID", async () => {
const { clearPid, loadPid, savePid } = await import("../../src/lib/tunnel/cloudflare/pid.js");
savePid(100);
savePid(200);
clearPid(100);
expect(loadPid()).toBe(200);
clearPid(200);
expect(loadPid()).toBeNull();
});
it("releases PID and process ownership for the exiting child only", () => {
const source = fs.readFileSync(new URL("../../src/lib/tunnel/cloudflare/cloudflared.js", import.meta.url), "utf8");
expect(source.match(/clearPid\(child\.pid\)/g)).toHaveLength(2);
expect(source.match(/cloudflaredProcess === child/g)).toHaveLength(2);
});
});

View File

@@ -15,7 +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", "xai", "grok-cli",
"minimax", "minimax-cn", "vercel-ai-gateway", "grok-cli", "kimi", "xai",
"deepseek",
];
describe("usage dispatch", () => {

View File

@@ -0,0 +1,198 @@
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 config from PROVIDERS.windsurf", () => {
const ex = new WindsurfExecutor();
expect(ex.provider).toBe("windsurf");
expect(ex.config).toBeDefined();
expect(ex.config.baseUrl).toContain("server.self-serve.windsurf.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.self-serve.windsurf.com/exa.language_server_pb.LanguageServerService/GetChatMessage");
});
it("PROVIDERS.windsurf baseUrl is the chat endpoint (registry in sync)", () => {
expect(PROVIDERS.windsurf.baseUrl).toBe(
"https://server.self-serve.windsurf.com/exa.language_server_pb.LanguageServerService/GetChatMessage"
);
});
});