mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
feat: add API-key Kimi Coding provider path (#463)
* feat: add api-key Kimi Coding provider support * fix(kimi-coding): honor apikey auth header in executor Ensure DefaultExecutor sends x-api-key for kimi-coding-apikey at runtime and deduplicate shared kimi coding config blocks in registry and models config to reduce drift between oauth and apikey variants. --------- Co-authored-by: OmniRoute Agent <agent@omniroute.local>
This commit is contained in:
@@ -78,6 +78,22 @@ interface LegacyProvider {
|
||||
clientVersion?: string;
|
||||
}
|
||||
|
||||
const KIMI_CODING_SHARED = {
|
||||
format: "claude",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
authHeader: "x-api-key",
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14",
|
||||
},
|
||||
models: [
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
{ id: "kimi-k2.5-thinking", name: "Kimi K2.5 Thinking" },
|
||||
{ id: "kimi-latest", name: "Kimi Latest" },
|
||||
] as RegistryModel[],
|
||||
} as const;
|
||||
|
||||
// ── Registry ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
@@ -559,16 +575,9 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
"kimi-coding": {
|
||||
id: "kimi-coding",
|
||||
alias: "kmc",
|
||||
format: "claude",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
...KIMI_CODING_SHARED,
|
||||
urlSuffix: "?beta=true",
|
||||
authType: "oauth",
|
||||
authHeader: "x-api-key",
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14",
|
||||
},
|
||||
oauth: {
|
||||
clientIdEnv: "KIMI_CODING_OAUTH_CLIENT_ID",
|
||||
clientIdDefault: "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
@@ -576,11 +585,13 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
refreshUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
authUrl: "https://auth.kimi.com/api/oauth/device_authorization",
|
||||
},
|
||||
models: [
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
{ id: "kimi-k2.5-thinking", name: "Kimi K2.5 Thinking" },
|
||||
{ id: "kimi-latest", name: "Kimi Latest" },
|
||||
],
|
||||
},
|
||||
|
||||
"kimi-coding-apikey": {
|
||||
id: "kimi-coding-apikey",
|
||||
alias: "kmca",
|
||||
...KIMI_CODING_SHARED,
|
||||
authType: "apikey",
|
||||
},
|
||||
|
||||
kilocode: {
|
||||
|
||||
@@ -54,6 +54,7 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
break;
|
||||
case "glm":
|
||||
case "kimi-coding":
|
||||
case "kimi-coding-apikey":
|
||||
case "minimax":
|
||||
case "minimax-cn":
|
||||
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
|
||||
|
||||
@@ -28,6 +28,14 @@ type ProviderModelsConfigEntry = {
|
||||
parseResponse: (data: any) => any;
|
||||
};
|
||||
|
||||
const KIMI_CODING_MODELS_CONFIG: ProviderModelsConfigEntry = {
|
||||
url: "https://api.kimi.com/coding/v1/models",
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
authHeader: "x-api-key",
|
||||
parseResponse: (data) => data.data || data.models || [],
|
||||
};
|
||||
|
||||
// Providers that return hardcoded models (no remote /models API)
|
||||
const STATIC_MODEL_PROVIDERS = {
|
||||
deepgram: () => [
|
||||
@@ -134,11 +142,10 @@ const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> = {
|
||||
parseResponse: (data) => data.data || [],
|
||||
},
|
||||
"kimi-coding": {
|
||||
url: "https://api.kimi.com/coding/v1/models",
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
authHeader: "x-api-key",
|
||||
parseResponse: (data) => data.data || data.models || [],
|
||||
...KIMI_CODING_MODELS_CONFIG,
|
||||
},
|
||||
"kimi-coding-apikey": {
|
||||
...KIMI_CODING_MODELS_CONFIG,
|
||||
},
|
||||
anthropic: {
|
||||
url: "https://api.anthropic.com/v1/models",
|
||||
|
||||
@@ -35,6 +35,7 @@ export const PROVIDER_ENDPOINTS = {
|
||||
glm: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
kimi: "https://api.moonshot.ai/v1/chat/completions",
|
||||
"kimi-coding": "https://api.kimi.com/coding/v1/messages",
|
||||
"kimi-coding-apikey": "https://api.kimi.com/coding/v1/messages",
|
||||
minimax: "https://api.minimax.io/anthropic/v1/messages",
|
||||
"minimax-cn": "https://api.minimaxi.com/anthropic/v1/messages",
|
||||
openai: "https://api.openai.com/v1/chat/completions",
|
||||
|
||||
@@ -83,6 +83,15 @@ export const APIKEY_PROVIDERS = {
|
||||
textIcon: "KM",
|
||||
website: "https://kimi.moonshot.cn",
|
||||
},
|
||||
"kimi-coding-apikey": {
|
||||
id: "kimi-coding-apikey",
|
||||
alias: "kmca",
|
||||
name: "Kimi Coding (API Key)",
|
||||
icon: "psychology",
|
||||
color: "#1E40AF",
|
||||
textIcon: "KC",
|
||||
website: "https://kimi.com",
|
||||
},
|
||||
minimax: {
|
||||
id: "minimax",
|
||||
alias: "minimax",
|
||||
|
||||
@@ -7,10 +7,8 @@ import { detectFormat } from "../../open-sse/services/provider.ts";
|
||||
import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCore.ts";
|
||||
import { translateRequest } from "../../open-sse/translator/index.ts";
|
||||
import { GithubExecutor } from "../../open-sse/executors/github.ts";
|
||||
import {
|
||||
CodexExecutor,
|
||||
setDefaultFastServiceTierEnabled,
|
||||
} from "../../open-sse/executors/codex.ts";
|
||||
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
|
||||
import { CodexExecutor, setDefaultFastServiceTierEnabled } from "../../open-sse/executors/codex.ts";
|
||||
import { translateNonStreamingResponse } from "../../open-sse/handlers/responseTranslator.ts";
|
||||
import { extractUsageFromResponse } from "../../open-sse/handlers/usageExtractor.ts";
|
||||
import { parseSSEToResponsesOutput } from "../../open-sse/handlers/sseParser.ts";
|
||||
@@ -60,6 +58,14 @@ test("GithubExecutor keeps non-codex model on /chat/completions", () => {
|
||||
assert.match(url, /\/chat\/completions$/);
|
||||
});
|
||||
|
||||
test("DefaultExecutor uses x-api-key for kimi-coding-apikey", () => {
|
||||
const executor = new DefaultExecutor("kimi-coding-apikey");
|
||||
const headers = executor.buildHeaders({ apiKey: "sk-kimi-test" }, true);
|
||||
|
||||
assert.equal(headers["x-api-key"], "sk-kimi-test");
|
||||
assert.equal(headers.Authorization, undefined);
|
||||
});
|
||||
|
||||
test("CodexExecutor forces stream=true for upstream compatibility", () => {
|
||||
const executor = new CodexExecutor();
|
||||
const transformed = executor.transformRequest(
|
||||
|
||||
@@ -48,3 +48,44 @@ test("serper validation still rejects unauthorized keys", async () => {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("kimi-coding-apikey validation uses Kimi Coding messages endpoint", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const calls = [];
|
||||
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
calls.push({
|
||||
url: String(url),
|
||||
method: init.method || "GET",
|
||||
headers: init.headers || {},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 400,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "kimi-coding-apikey",
|
||||
apiKey: "sk-kimi-test",
|
||||
});
|
||||
|
||||
assert.equal(result.valid, true);
|
||||
assert.equal(result.error, null);
|
||||
assert.equal(calls.length, 1);
|
||||
|
||||
assert.equal(calls[0].url, "https://api.kimi.com/coding/v1/messages");
|
||||
assert.equal(calls[0].method, "POST");
|
||||
assert.equal(calls[0].headers["x-api-key"], "sk-kimi-test");
|
||||
assert.equal(calls[0].headers["Anthropic-Version"], "2023-06-01");
|
||||
|
||||
for (const call of calls) {
|
||||
assert.equal(call.url.includes("?beta=true/messages"), false);
|
||||
assert.equal(call.url.includes("?beta=true/models"), false);
|
||||
}
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user