mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
feat(kimi): merge OAuth into dual-auth provider, add K3/K2.7 models
Gộp kimi-coding vào kimi (oauth+apikey), parity CLIProxyAPI device flow/headers/refresh. Thêm K3 + K2.7 Code (+ Kimi Code ids), pricing/caps vision, cập nhật baseline. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -131,7 +131,7 @@ const APIKEY_PROVIDERS = {
|
||||
openrouter: { id: "openrouter", name: "OpenRouter" },
|
||||
glm: { id: "glm", name: "GLM Coding" },
|
||||
minimax: { id: "minimax", name: "Minimax Coding" },
|
||||
kimi: { id: "kimi", name: "Kimi Coding" },
|
||||
kimi: { id: "kimi", name: "Kimi" },
|
||||
openai: { id: "openai", name: "OpenAI" },
|
||||
anthropic: { id: "anthropic", name: "Anthropic" },
|
||||
gemini: { id: "gemini", name: "Gemini" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { platform, arch } from "os";
|
||||
import { platform, arch, hostname } from "os";
|
||||
import { PROVIDERS, PROVIDER_OAUTH } from "./providers.js";
|
||||
import { ANTIGRAVITY_IDE_USER_AGENT } from "../providers/shared.js";
|
||||
import { createRequire } from "module";
|
||||
|
||||
// === Gemini CLI === derive từ registry gemini-cli.transport
|
||||
export const GEMINI_CLI_VERSION = PROVIDERS["gemini-cli"]?.cliVersion;
|
||||
@@ -171,12 +172,44 @@ export const OAUTH_ENDPOINTS = {
|
||||
github: { token: PROVIDER_OAUTH["github"]?.tokenUrl, auth: PROVIDER_OAUTH["github"]?.authorizeUrl, deviceCode: PROVIDER_OAUTH["github"]?.deviceCodeUrl },
|
||||
};
|
||||
|
||||
// Generate Kimi OAuth custom headers
|
||||
export function buildKimiHeaders() {
|
||||
let _appVersion;
|
||||
function getAppPackageVersion() {
|
||||
if (_appVersion) return _appVersion;
|
||||
try {
|
||||
const require = createRequire(import.meta.url);
|
||||
_appVersion = require("../../package.json").version || "0.0.0";
|
||||
} catch {
|
||||
_appVersion = process.env.npm_package_version || "0.0.0";
|
||||
}
|
||||
return _appVersion;
|
||||
}
|
||||
|
||||
// Kimi Code OAuth / API headers (CLIProxyAPI internal/auth/kimi commonHeaders parity).
|
||||
// deviceId must stay stable per connection for the whole OAuth session.
|
||||
export function buildKimiHeaders(deviceId) {
|
||||
const osName = platform();
|
||||
const architecture = arch();
|
||||
let deviceModel = `${osName} ${architecture}`;
|
||||
if (osName === "darwin") deviceModel = `macOS ${architecture}`;
|
||||
else if (osName === "win32") deviceModel = `Windows ${architecture}`;
|
||||
else if (osName === "linux") deviceModel = `Linux ${architecture}`;
|
||||
|
||||
let deviceName = "unknown";
|
||||
try {
|
||||
deviceName = hostname() || "unknown";
|
||||
} catch {
|
||||
deviceName = "unknown";
|
||||
}
|
||||
|
||||
const resolvedId = (typeof deviceId === "string" && deviceId.trim())
|
||||
? deviceId.trim()
|
||||
: `kimi-${Date.now()}`;
|
||||
|
||||
return {
|
||||
"X-Msh-Platform": "9router",
|
||||
"X-Msh-Version": "2.1.2",
|
||||
"X-Msh-Device-Model": typeof process !== "undefined" ? `${process.platform} ${process.arch}` : "unknown",
|
||||
"X-Msh-Device-Id": `kimi-${Date.now()}`
|
||||
"X-Msh-Version": getAppPackageVersion(),
|
||||
"X-Msh-Device-Name": deviceName,
|
||||
"X-Msh-Device-Model": deviceModel,
|
||||
"X-Msh-Device-Id": resolvedId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ function applyAuth(headers, desc, credentials) {
|
||||
|
||||
// Provider-specific header quirks kept as small hooks (not pure auth).
|
||||
const HEADER_HOOKS = {
|
||||
kimiHeaders: (h) => Object.assign(h, buildKimiHeaders()),
|
||||
// Stable device_id from OAuth connection (CLIProxyAPI KimiTokenStorage.DeviceID)
|
||||
kimiHeaders: (h, c) => Object.assign(h, buildKimiHeaders(c?.providerSpecificData?.deviceId)),
|
||||
clineHeaders: (h, c) => Object.assign(h, buildClineHeaders(c.apiKey || c.accessToken)),
|
||||
kilocodeOrg: (h, c) => { if (c.providerSpecificData?.orgId) h["X-Kilocode-OrganizationID"] = c.providerSpecificData.orgId; },
|
||||
claudeOverlay: (h) => {
|
||||
@@ -227,7 +228,8 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
kiro: () => this.refreshKiro(credentials.refreshToken, proxyOptions),
|
||||
cline: () => this.refreshCline(credentials.refreshToken, proxyOptions),
|
||||
clinepass: () => this.refreshCline(credentials.refreshToken, proxyOptions),
|
||||
"kimi-coding": () => this.refreshKimiCoding(credentials.refreshToken, proxyOptions),
|
||||
kimi: () => this.refreshKimi(credentials, proxyOptions),
|
||||
"kimi-coding": () => this.refreshKimi(credentials, proxyOptions),
|
||||
kilocode: () => this.refreshKilocode(credentials.refreshToken, proxyOptions)
|
||||
};
|
||||
|
||||
@@ -307,16 +309,20 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
return { accessToken, refreshToken: data?.refreshToken || refreshToken, expiresIn };
|
||||
}
|
||||
|
||||
async refreshKimiCoding(refreshToken, proxyOptions = null) {
|
||||
const kimiHeaders = buildKimiHeaders();
|
||||
const response = await proxyAwareFetch(PROVIDERS["kimi-coding"].refreshUrl, {
|
||||
// CLIProxyAPI DeviceFlowClient.RefreshToken — form body + X-Msh-* headers + stable device_id
|
||||
async refreshKimi(credentials, proxyOptions = null) {
|
||||
const refreshToken = credentials.refreshToken;
|
||||
const cfg = PROVIDERS.kimi || PROVIDERS["kimi-coding"];
|
||||
if (!cfg?.refreshUrl || !cfg?.clientId) return null;
|
||||
const kimiHeaders = buildKimiHeaders(credentials?.providerSpecificData?.deviceId);
|
||||
const response = await proxyAwareFetch(cfg.refreshUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json",
|
||||
...kimiHeaders
|
||||
},
|
||||
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: PROVIDERS["kimi-coding"].clientId })
|
||||
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: cfg.clientId })
|
||||
}, proxyOptions);
|
||||
if (!response.ok) return null;
|
||||
const tokens = await response.json();
|
||||
|
||||
@@ -96,6 +96,14 @@ export const MODEL_CAPABILITIES = {
|
||||
// Qwen plain coder/text (no vision) — registry "vision-model" / "coder-model" aliases
|
||||
"vision-model": { vision: true, reasoning: true, thinkingFormat: "qwen", contextWindow: 1000000 },
|
||||
"coder-model": { reasoning: true, thinkingFormat: "qwen", contextWindow: 1000000 },
|
||||
|
||||
// Kimi flagship + coding (platform + Kimi Code ids) — vision/video native
|
||||
"kimi-k3": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 1048576, maxOutput: 131072 },
|
||||
"k3": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 1048576, maxOutput: 131072 },
|
||||
"kimi-for-coding": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 },
|
||||
"kimi-for-coding-highspeed": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 },
|
||||
"kimi-k2.7-code": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 },
|
||||
"kimi-k2.7-code-highspeed": { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 },
|
||||
};
|
||||
|
||||
const KIRO_GPT_5_6_CAPABILITIES = { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 };
|
||||
@@ -222,7 +230,9 @@ export const PATTERN_CAPABILITIES = [
|
||||
{ pattern: "*qwen*", caps: { reasoning: true, thinkingFormat: "qwen", contextWindow: 262144 } },
|
||||
|
||||
// ── Kimi (enabled→reasoning_effort; K2.7-code cannot disable) ─────
|
||||
{ pattern: "*kimi*k2.7*code*", caps: { vision: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 262144 } },
|
||||
{ pattern: "*kimi*k3*", caps: { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 1048576, maxOutput: 131072 } },
|
||||
{ pattern: "*kimi*for-coding*", caps: { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 } },
|
||||
{ pattern: "*kimi*k2.7*code*", caps: { vision: true, videoInput: true, reasoning: true, thinkingFormat: "kimi", thinkingCanDisable: false, contextWindow: 262144, maxOutput: 65536 } },
|
||||
{ pattern: "*kimi*k2*", caps: { vision: true, reasoning: true, thinkingFormat: "kimi", contextWindow: 262144, maxOutput: 262144 } },
|
||||
{ pattern: "*kimi*", caps: { reasoning: true, thinkingFormat: "kimi", contextWindow: 262144 } },
|
||||
|
||||
|
||||
@@ -75,10 +75,18 @@ export const MODEL_PRICING = {
|
||||
"qwen3-coder-flash": { input: 0.50, output: 2.00, cached: 0.25, reasoning: 3.00, cache_creation: 0.50 },
|
||||
|
||||
// === Kimi ===
|
||||
// Official platform.kimi.ai: cache-hit / cache-miss / output per 1M tokens
|
||||
"kimi-k3": { input: 3.00, output: 15.00, cached: 0.30, reasoning: 15.00, cache_creation: 3.00 },
|
||||
"k3": { input: 3.00, output: 15.00, cached: 0.30, reasoning: 15.00, cache_creation: 3.00 },
|
||||
"kimi-k2.7-code": { input: 0.95, output: 4.00, cached: 0.19, reasoning: 4.00, cache_creation: 0.95 },
|
||||
"kimi-k2.7-code-highspeed": { input: 1.90, output: 8.00, cached: 0.38, reasoning: 8.00, cache_creation: 1.90 },
|
||||
"kimi-for-coding": { input: 0.95, output: 4.00, cached: 0.19, reasoning: 4.00, cache_creation: 0.95 },
|
||||
"kimi-for-coding-highspeed": { input: 1.90, output: 8.00, cached: 0.38, reasoning: 8.00, cache_creation: 1.90 },
|
||||
"kimi-k2": { input: 1.00, output: 4.00, cached: 0.50, reasoning: 6.00, cache_creation: 1.00 },
|
||||
"kimi-k2-thinking": { input: 1.50, output: 6.00, cached: 0.75, reasoning: 9.00, cache_creation: 1.50 },
|
||||
"kimi-k2.5": { input: 1.20, output: 4.80, cached: 0.60, reasoning: 7.20, cache_creation: 1.20 },
|
||||
"kimi-k2.5-thinking": { input: 1.80, output: 7.20, cached: 0.90, reasoning: 10.80, cache_creation: 1.80 },
|
||||
"kimi-k2.6": { input: 1.00, output: 4.00, cached: 0.50, reasoning: 6.00, cache_creation: 1.00 },
|
||||
"kimi-latest": { input: 1.00, output: 4.00, cached: 0.50, reasoning: 6.00, cache_creation: 1.00 },
|
||||
|
||||
// === DeepSeek ===
|
||||
@@ -185,6 +193,7 @@ export const PATTERN_PRICING = [
|
||||
|
||||
// --- Kimi ---
|
||||
{ pattern: "kimi-*-thinking", pricing: { input: 1.80, output: 7.20, cached: 0.90, reasoning: 10.80, cache_creation: 1.80 } },
|
||||
{ pattern: "kimi-k3*", pricing: { input: 3.00, output: 15.00, cached: 0.30, reasoning: 15.00, cache_creation: 3.00 } },
|
||||
{ pattern: "kimi-k2*", pricing: { input: 1.20, output: 4.80, cached: 0.60, reasoning: 7.20, cache_creation: 1.20 } },
|
||||
{ pattern: "kimi-*", pricing: { input: 1.00, output: 4.00, cached: 0.50, reasoning: 6.00, cache_creation: 1.00 } },
|
||||
|
||||
|
||||
@@ -52,53 +52,52 @@ import p49 from "./jina-ai.js";
|
||||
import p50 from "./jina-reader.js";
|
||||
import p51 from "./kilocode.js";
|
||||
import p52 from "./kimchi.js";
|
||||
import p53 from "./kimi-coding.js";
|
||||
import p54 from "./kimi.js";
|
||||
import p55 from "./kiro.js";
|
||||
import p56 from "./linkup.js";
|
||||
import p57 from "./local-device.js";
|
||||
import p58 from "./mimo-free.js";
|
||||
import p59 from "./minimax-cn.js";
|
||||
import p60 from "./minimax.js";
|
||||
import p61 from "./mistral.js";
|
||||
import p62 from "./mmf.js";
|
||||
import p63 from "./nanobanana.js";
|
||||
import p64 from "./nebius.js";
|
||||
import p65 from "./nvidia.js";
|
||||
import p66 from "./ollama-local.js";
|
||||
import p67 from "./ollama.js";
|
||||
import p68 from "./openai.js";
|
||||
import p69 from "./opencode-go.js";
|
||||
import p70 from "./opencode.js";
|
||||
import p71 from "./openrouter.js";
|
||||
import p72 from "./perplexity-web.js";
|
||||
import p73 from "./perplexity.js";
|
||||
import p74 from "./perplexity-agent.js";
|
||||
import p75 from "./playht.js";
|
||||
import p76 from "./qoder.js";
|
||||
import p77 from "./qwen.js";
|
||||
import p78 from "./recraft.js";
|
||||
import p79 from "./runwayml.js";
|
||||
import p80 from "./sdwebui.js";
|
||||
import p81 from "./searchapi.js";
|
||||
import p82 from "./searxng.js";
|
||||
import p83 from "./serper.js";
|
||||
import p84 from "./siliconflow.js";
|
||||
import p85 from "./stability-ai.js";
|
||||
import p86 from "./tavily.js";
|
||||
import p87 from "./together.js";
|
||||
import p88 from "./topaz.js";
|
||||
import p89 from "./tortoise.js";
|
||||
import p90 from "./venice.js";
|
||||
import p91 from "./vercel-ai-gateway.js";
|
||||
import p92 from "./vertex-partner.js";
|
||||
import p93 from "./vertex.js";
|
||||
import p94 from "./volcengine-ark.js";
|
||||
import p95 from "./voyage-ai.js";
|
||||
import p96 from "./xai.js";
|
||||
import p97 from "./xiaomi-mimo.js";
|
||||
import p98 from "./xiaomi-tokenplan.js";
|
||||
import p99 from "./youcom.js";
|
||||
import p53 from "./kimi.js";
|
||||
import p54 from "./kiro.js";
|
||||
import p55 from "./linkup.js";
|
||||
import p56 from "./local-device.js";
|
||||
import p57 from "./mimo-free.js";
|
||||
import p58 from "./minimax-cn.js";
|
||||
import p59 from "./minimax.js";
|
||||
import p60 from "./mistral.js";
|
||||
import p61 from "./mmf.js";
|
||||
import p62 from "./nanobanana.js";
|
||||
import p63 from "./nebius.js";
|
||||
import p64 from "./nvidia.js";
|
||||
import p65 from "./ollama-local.js";
|
||||
import p66 from "./ollama.js";
|
||||
import p67 from "./openai.js";
|
||||
import p68 from "./opencode-go.js";
|
||||
import p69 from "./opencode.js";
|
||||
import p70 from "./openrouter.js";
|
||||
import p71 from "./perplexity-web.js";
|
||||
import p72 from "./perplexity.js";
|
||||
import p73 from "./perplexity-agent.js";
|
||||
import p74 from "./playht.js";
|
||||
import p75 from "./qoder.js";
|
||||
import p76 from "./qwen.js";
|
||||
import p77 from "./recraft.js";
|
||||
import p78 from "./runwayml.js";
|
||||
import p79 from "./sdwebui.js";
|
||||
import p80 from "./searchapi.js";
|
||||
import p81 from "./searxng.js";
|
||||
import p82 from "./serper.js";
|
||||
import p83 from "./siliconflow.js";
|
||||
import p84 from "./stability-ai.js";
|
||||
import p85 from "./tavily.js";
|
||||
import p86 from "./together.js";
|
||||
import p87 from "./topaz.js";
|
||||
import p88 from "./tortoise.js";
|
||||
import p89 from "./venice.js";
|
||||
import p90 from "./vercel-ai-gateway.js";
|
||||
import p91 from "./vertex-partner.js";
|
||||
import p92 from "./vertex.js";
|
||||
import p93 from "./volcengine-ark.js";
|
||||
import p94 from "./voyage-ai.js";
|
||||
import p95 from "./xai.js";
|
||||
import p96 from "./xiaomi-mimo.js";
|
||||
import p97 from "./xiaomi-tokenplan.js";
|
||||
import p98 from "./youcom.js";
|
||||
|
||||
export default [
|
||||
p0,
|
||||
@@ -200,5 +199,4 @@ export default [
|
||||
p96,
|
||||
p97,
|
||||
p98,
|
||||
p99
|
||||
];
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { CLAUDE_API_HEADERS, KIMI_CODING_BASE_URL } from "../shared.js";
|
||||
|
||||
export default {
|
||||
id: "kimi-coding",
|
||||
hidden: true,
|
||||
priority: 120,
|
||||
alias: "kmc",
|
||||
display: {
|
||||
name: "Kimi Coding",
|
||||
icon: "psychology",
|
||||
color: "#1E40AF",
|
||||
textIcon: "KC",
|
||||
website: "https://kimi.moonshot.cn",
|
||||
notice: {
|
||||
signupUrl: "https://kimi.moonshot.cn",
|
||||
},
|
||||
},
|
||||
category: "oauth",
|
||||
transport: {
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
format: "claude",
|
||||
urlSuffix: "?beta=true",
|
||||
headers: { ...CLAUDE_API_HEADERS },
|
||||
clientId: "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
tokenUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
refreshUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
auth: {
|
||||
combined: true,
|
||||
header: "x-api-key",
|
||||
scheme: "raw",
|
||||
hooks: [
|
||||
"kimiHeaders",
|
||||
],
|
||||
},
|
||||
},
|
||||
// Multi-endpoint: pick the transport matching client sourceFormat to skip translation.
|
||||
transports: [
|
||||
{
|
||||
format: "openai",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/chat/completions",
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer", hooks: ["kimiHeaders"] },
|
||||
},
|
||||
{
|
||||
format: "claude",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
urlSuffix: "?beta=true",
|
||||
headers: { ...CLAUDE_API_HEADERS },
|
||||
auth: { combined: true, header: "x-api-key", scheme: "raw", hooks: ["kimiHeaders"] },
|
||||
},
|
||||
],
|
||||
models: [
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6" },
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
{ id: "kimi-k2.5-thinking", name: "Kimi K2.5 Thinking" },
|
||||
{ id: "kimi-latest", name: "Kimi Latest" },
|
||||
],
|
||||
oauth: {
|
||||
deviceCodeUrl: "https://auth.kimi.com/api/oauth/device_authorization",
|
||||
tokenUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
refreshLeadMs: 300000,
|
||||
},
|
||||
features: {
|
||||
usage: true,
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,14 @@
|
||||
import { CLAUDE_API_HEADERS, KIMI_CODING_BASE_URL } from "../shared.js";
|
||||
import { CLAUDE_API_HEADERS } from "../shared.js";
|
||||
|
||||
// Dual auth (same pattern as xai): OAuth = Kimi Code subscription (device code),
|
||||
// API key = platform.moonshot / api.kimi.com. Transport is shared.
|
||||
// CLIProxyAPI parity: client_id, auth.kimi.com device+token, X-Msh-* headers, device_id.
|
||||
export default {
|
||||
id: "kimi",
|
||||
priority: 170,
|
||||
alias: "kimi",
|
||||
// Legacy id + short alias from former kimi-coding registry entry
|
||||
aliases: ["kimi-coding", "kmc"],
|
||||
display: {
|
||||
name: "Kimi",
|
||||
icon: "psychology",
|
||||
@@ -12,18 +17,25 @@ export default {
|
||||
website: "https://kimi.moonshot.cn",
|
||||
notice: {
|
||||
apiKeyUrl: "https://platform.moonshot.ai/console/api-keys",
|
||||
signupUrl: "https://www.kimi.com/code",
|
||||
},
|
||||
},
|
||||
category: "apikey",
|
||||
category: "oauth",
|
||||
authModes: ["oauth", "apikey"],
|
||||
hasOAuth: true,
|
||||
transport: {
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
format: "claude",
|
||||
urlSuffix: "?beta=true",
|
||||
headers: { ...CLAUDE_API_HEADERS },
|
||||
clientId: "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
tokenUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
refreshUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
auth: {
|
||||
combined: true,
|
||||
header: "x-api-key",
|
||||
scheme: "raw",
|
||||
hooks: ["kimiHeaders"],
|
||||
},
|
||||
},
|
||||
// Multi-endpoint: pick the transport matching client sourceFormat to skip translation.
|
||||
@@ -31,26 +43,47 @@ export default {
|
||||
{
|
||||
format: "openai",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/chat/completions",
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer" },
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer", hooks: ["kimiHeaders"] },
|
||||
},
|
||||
{
|
||||
format: "claude",
|
||||
baseUrl: "https://api.kimi.com/coding/v1/messages",
|
||||
urlSuffix: "?beta=true",
|
||||
headers: { ...CLAUDE_API_HEADERS },
|
||||
auth: { combined: true, header: "x-api-key", scheme: "raw" },
|
||||
auth: { combined: true, header: "x-api-key", scheme: "raw", hooks: ["kimiHeaders"] },
|
||||
},
|
||||
],
|
||||
models: [
|
||||
// Flagship K3 — platform.kimi.ai id `kimi-k3`, Kimi Code OAuth id `k3` (up to 1M)
|
||||
{ id: "kimi-k3", name: "Kimi K3" },
|
||||
{ id: "k3", name: "Kimi K3 (Code)" },
|
||||
// Kimi Code subscription stable ids (map to K2.7 Code backend)
|
||||
{ id: "kimi-for-coding", name: "Kimi for Coding" },
|
||||
{ id: "kimi-for-coding-highspeed", name: "Kimi for Coding Highspeed" },
|
||||
// Pay-as-you-go platform ids
|
||||
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code" },
|
||||
{ id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed" },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6" },
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
{ id: "kimi-k2.5-thinking", name: "Kimi K2.5 Thinking" },
|
||||
{ id: "kimi-latest", name: "Kimi Latest" },
|
||||
],
|
||||
serviceKinds: ["llm","webSearch"],
|
||||
serviceKinds: ["llm", "webSearch"],
|
||||
searchViaChat: {
|
||||
defaultModel: "kimi-k2.5",
|
||||
defaultModel: "kimi-k3",
|
||||
endpoint: "https://api.moonshot.cn/v1/chat/completions",
|
||||
pricingUrl: "https://platform.moonshot.ai/docs/pricing/chat",
|
||||
pricingUrl: "https://platform.kimi.ai/docs/pricing/chat",
|
||||
},
|
||||
oauth: {
|
||||
clientId: "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
deviceCodeUrl: "https://auth.kimi.com/api/oauth/device_authorization",
|
||||
tokenUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
refreshUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
// CLIProxyAPI refreshThresholdSeconds = 300
|
||||
refreshLeadMs: 300000,
|
||||
authorizeDeviceUrl: "https://www.kimi.com/code/authorize_device",
|
||||
},
|
||||
features: {
|
||||
usage: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OAUTH_ENDPOINTS, REFRESH_LEAD_MS } from "../config/appConstants.js";
|
||||
import {
|
||||
refreshXaiToken,
|
||||
refreshAccessToken,
|
||||
refreshKimiToken,
|
||||
refreshClaudeOAuthToken,
|
||||
refreshGoogleToken,
|
||||
refreshQwenToken,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
// Re-export all provider refresh functions (preserves public API for all consumers)
|
||||
export {
|
||||
refreshAccessToken,
|
||||
refreshKimiToken,
|
||||
refreshClaudeOAuthToken,
|
||||
refreshGoogleToken,
|
||||
refreshQwenToken,
|
||||
@@ -44,7 +46,10 @@ export function isUnrecoverableRefreshError(result) {
|
||||
}
|
||||
|
||||
export function getRefreshLeadMs(provider) {
|
||||
return REFRESH_LEAD_MS[provider] || TOKEN_EXPIRY_BUFFER_MS;
|
||||
if (REFRESH_LEAD_MS[provider]) return REFRESH_LEAD_MS[provider];
|
||||
// Legacy id after kimi-coding → kimi merge
|
||||
if (provider === "kimi-coding" && REFRESH_LEAD_MS.kimi) return REFRESH_LEAD_MS.kimi;
|
||||
return TOKEN_EXPIRY_BUFFER_MS;
|
||||
}
|
||||
|
||||
export function parseVertexSaJson(apiKey) {
|
||||
@@ -133,6 +138,9 @@ const REFRESH_HANDLERS = {
|
||||
"grok-cli": (c, log) => refreshXaiToken(c.refreshToken, log),
|
||||
gcli: (c, log) => refreshXaiToken(c.refreshToken, log),
|
||||
"codebuddy-cn": (c, log) => refreshCodebuddyToken(c.refreshToken, log),
|
||||
// Kimi Code OAuth (merged into id `kimi`); legacy id still routes here
|
||||
kimi: (c, log) => refreshKimiToken(c.refreshToken, c, log),
|
||||
"kimi-coding": (c, log) => refreshKimiToken(c.refreshToken, c, log),
|
||||
vertex: vertexRefreshHandler,
|
||||
"vertex-partner": vertexRefreshHandler
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PROVIDERS, PROVIDER_OAUTH } from "../../config/providers.js";
|
||||
import { OAUTH_ENDPOINTS, GITHUB_COPILOT } from "../../config/appConstants.js";
|
||||
import { OAUTH_ENDPOINTS, GITHUB_COPILOT, buildKimiHeaders } from "../../config/appConstants.js";
|
||||
import { proxyAwareFetch } from "../../utils/proxyFetch.js";
|
||||
import { dedupRefresh } from "./dedup.js";
|
||||
import { buildExternalIdpRefreshParams } from "../../../src/lib/oauth/kiroExternalIdp.js";
|
||||
@@ -91,6 +91,52 @@ export async function refreshAccessToken(provider, refreshToken, credentials, lo
|
||||
}, log);
|
||||
}
|
||||
|
||||
// CLIProxyAPI DeviceFlowClient.RefreshToken: form body (no client_secret) + X-Msh-* headers
|
||||
export async function refreshKimiToken(refreshToken, credentials, log) {
|
||||
const config = PROVIDERS.kimi;
|
||||
if (!config?.refreshUrl || !config?.clientId) {
|
||||
log?.warn?.("TOKEN_REFRESH", "No Kimi refresh URL/clientId configured");
|
||||
return null;
|
||||
}
|
||||
if (!refreshToken) return null;
|
||||
|
||||
return dedupRefresh("kimi", refreshToken, async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
...buildKimiHeaders(credentials?.providerSpecificData?.deviceId),
|
||||
};
|
||||
const response = await fetch(config.refreshUrl, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
client_id: config.clientId,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
log?.error?.("TOKEN_REFRESH", `Failed to refresh token for kimi`, {
|
||||
status: response.status,
|
||||
error: errorText,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
const tokens = await response.json();
|
||||
return {
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token || refreshToken,
|
||||
expiresIn: tokens.expires_in,
|
||||
};
|
||||
} catch (error) {
|
||||
log?.error?.("TOKEN_REFRESH", `Error refreshing token for kimi`, { error: error.message });
|
||||
return null;
|
||||
}
|
||||
}, log);
|
||||
}
|
||||
|
||||
export async function refreshClaudeOAuthToken(refreshToken, log) {
|
||||
if (!refreshToken) return null;
|
||||
return dedupRefresh("claude", refreshToken, async () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { getProviderIconSrc, markProviderIconMissing } from "@/shared/utils/providerIcon";
|
||||
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal, NoAuthProxyCard, ConfirmModal } from "@/shared/components";
|
||||
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS } from "@/shared/constants/providers";
|
||||
import { getModelsByProviderId, getModelKind } from "@/shared/constants/models";
|
||||
@@ -151,8 +152,12 @@ export default function ProviderDetailPage() {
|
||||
const oauthConnectionLabel =
|
||||
providerId === "xai" ? "Grok Build OAuth"
|
||||
: providerId === "grok-cli" ? "Grok CLI Device Login"
|
||||
: providerId === "kimi" ? "Kimi Coding OAuth"
|
||||
: "OAuth";
|
||||
const apiKeyConnectionLabel = providerId === "xai" ? "xAI API Key" : "API Key";
|
||||
const apiKeyConnectionLabel =
|
||||
providerId === "xai" ? "xAI API Key"
|
||||
: providerId === "kimi" ? "Kimi API Key"
|
||||
: "API Key";
|
||||
// Resolve suffix "(level)" for a model when a thinking level is picked and the model supports it.
|
||||
const resolveThinkingSuffix = (modelId) => {
|
||||
if (!thinkingMode || thinkingMode === "auto") return null;
|
||||
@@ -1231,7 +1236,7 @@ export default function ProviderDetailPage() {
|
||||
if (isAnthropicCompatible) {
|
||||
return "/providers/anthropic-m.png";
|
||||
}
|
||||
return `/providers/${providerInfo.id}.png`;
|
||||
return getProviderIconSrc(providerInfo.id);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1250,7 +1255,7 @@ export default function ProviderDetailPage() {
|
||||
className="flex size-12 shrink-0 items-center justify-center rounded-lg"
|
||||
style={{ backgroundColor: `${providerInfo.color}15` }}
|
||||
>
|
||||
{headerImgError ? (
|
||||
{headerImgError || !getHeaderIconPath() ? (
|
||||
<span className="text-sm font-bold" style={{ color: providerInfo.color }}>
|
||||
{providerInfo.textIcon || providerInfo.id.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
@@ -1262,7 +1267,12 @@ export default function ProviderDetailPage() {
|
||||
height={48}
|
||||
className="max-h-12 max-w-12 rounded-lg object-contain"
|
||||
sizes="48px"
|
||||
onError={() => setHeaderImgError(true)}
|
||||
onError={() => {
|
||||
markProviderIconMissing(providerInfo.id);
|
||||
setHeaderImgError(true);
|
||||
}}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -154,6 +154,7 @@ export async function GET(request, { params }) {
|
||||
const noPkceDeviceProviders = [
|
||||
"github",
|
||||
"kiro",
|
||||
"kimi",
|
||||
"kimi-coding",
|
||||
"kilocode",
|
||||
"codebuddy-cn",
|
||||
@@ -279,10 +280,11 @@ export async function POST(request, { params }) {
|
||||
}
|
||||
|
||||
// Providers that don't use PKCE for device code
|
||||
const noPkceProviders = ["github", "kimi-coding", "kilocode", "codebuddy-cn"];
|
||||
const noPkceProviders = ["github", "kimi", "kimi-coding", "kilocode", "codebuddy-cn"];
|
||||
let result;
|
||||
if (noPkceProviders.includes(provider)) {
|
||||
result = await pollForToken(provider, deviceCode);
|
||||
// kimi needs extraData._kimiDeviceId for stable X-Msh-Device-Id (CLIProxyAPI parity)
|
||||
result = await pollForToken(provider, deviceCode, null, extraData);
|
||||
} else if (provider === "kiro") {
|
||||
// Kiro needs extraData (clientId, clientSecret) from device code response
|
||||
result = await pollForToken(provider, deviceCode, null, extraData);
|
||||
@@ -303,9 +305,10 @@ export async function POST(request, { params }) {
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
// Save to database
|
||||
// Save to database (legacy kimi-coding OAuth → dual-auth kimi)
|
||||
const providerId = provider === "kimi-coding" ? "kimi" : provider;
|
||||
const connection = await createProviderConnection({
|
||||
provider,
|
||||
provider: providerId,
|
||||
authType: "oauth",
|
||||
...result.tokens,
|
||||
expiresAt: result.tokens.expiresIn
|
||||
|
||||
@@ -75,7 +75,8 @@ const OAUTH_TEST_CONFIG = {
|
||||
authPrefix: "Bearer ",
|
||||
refreshable: false,
|
||||
},
|
||||
"kimi-coding": { checkExpiry: true, refreshable: false },
|
||||
kimi: { checkExpiry: true, refreshable: true },
|
||||
"kimi-coding": { checkExpiry: true, refreshable: true },
|
||||
cursor: { tokenExists: true },
|
||||
kilocode: {
|
||||
url: `${KILOCODE_CONFIG.apiBaseUrl}/api/profile`,
|
||||
|
||||
@@ -89,12 +89,18 @@ export const CURSOR_CONFIG = {
|
||||
},
|
||||
};
|
||||
|
||||
// Kimi Coding OAuth Configuration (Device Code Flow)
|
||||
// clientId uses env override — dynamic, not stored in registry
|
||||
export const KIMI_CODING_CONFIG = {
|
||||
...PROVIDER_OAUTH["kimi-coding"],
|
||||
clientId: process.env.KIMI_CODING_OAUTH_CLIENT_ID || REGISTRY_PROVIDERS["kimi-coding"]?.clientId,
|
||||
// Kimi Code OAuth (Device Code Flow) — merged into provider id `kimi` (dual auth)
|
||||
// clientId: registry first, env override for forks
|
||||
export const KIMI_CONFIG = {
|
||||
...PROVIDER_OAUTH["kimi"],
|
||||
clientId:
|
||||
process.env.KIMI_CODING_OAUTH_CLIENT_ID ||
|
||||
process.env.KIMI_OAUTH_CLIENT_ID ||
|
||||
REGISTRY_PROVIDERS["kimi"]?.clientId ||
|
||||
PROVIDER_OAUTH["kimi"]?.clientId,
|
||||
};
|
||||
// Back-compat alias for any remaining KIMI_CODING_CONFIG imports
|
||||
export const KIMI_CODING_CONFIG = KIMI_CONFIG;
|
||||
|
||||
// KiloCode OAuth Configuration (Custom Device Auth Flow)
|
||||
export const KILOCODE_CONFIG = { ...PROVIDER_OAUTH["kilocode"] };
|
||||
@@ -134,7 +140,8 @@ export const PROVIDERS = {
|
||||
GITHUB: "github",
|
||||
KIRO: "kiro",
|
||||
CURSOR: "cursor",
|
||||
KIMI_CODING: "kimi-coding",
|
||||
KIMI: "kimi",
|
||||
KIMI_CODING: "kimi",
|
||||
KILOCODE: "kilocode",
|
||||
CLINE: "cline",
|
||||
CLINEPASS: "clinepass",
|
||||
|
||||
+41
-14
@@ -20,7 +20,7 @@ import {
|
||||
KIRO_CONFIG,
|
||||
assertValidAwsRegion,
|
||||
CURSOR_CONFIG,
|
||||
KIMI_CODING_CONFIG,
|
||||
KIMI_CONFIG,
|
||||
KILOCODE_CONFIG,
|
||||
CLINE_CONFIG,
|
||||
CLINEPASS_CONFIG,
|
||||
@@ -1081,13 +1081,22 @@ const PROVIDERS = {
|
||||
}),
|
||||
},
|
||||
|
||||
"kimi-coding": {
|
||||
config: KIMI_CODING_CONFIG,
|
||||
// Kimi Code device flow (CLIProxyAPI internal/auth/kimi). Id is `kimi`;
|
||||
// `kimi-coding` remains an alias key so old UI/API routes still resolve.
|
||||
kimi: {
|
||||
config: KIMI_CONFIG,
|
||||
flowType: "device_code",
|
||||
requestDeviceCode: async (config) => {
|
||||
const { buildKimiHeaders } = await import("open-sse/config/appConstants.js");
|
||||
const deviceId = crypto.randomUUID();
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
...buildKimiHeaders(deviceId),
|
||||
};
|
||||
const response = await fetch(config.deviceCodeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
||||
headers,
|
||||
body: new URLSearchParams({ client_id: config.clientId }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -1095,21 +1104,30 @@ const PROVIDERS = {
|
||||
throw new Error(`Device code request failed: ${error}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const authorizeDeviceUrl = config.authorizeDeviceUrl || "https://www.kimi.com/code/authorize_device";
|
||||
return {
|
||||
device_code: data.device_code,
|
||||
user_code: data.user_code,
|
||||
verification_uri: data.verification_uri || "https://www.kimi.com/code/authorize_device",
|
||||
verification_uri: data.verification_uri || authorizeDeviceUrl,
|
||||
verification_uri_complete:
|
||||
data.verification_uri_complete ||
|
||||
`https://www.kimi.com/code/authorize_device?user_code=${data.user_code}`,
|
||||
`${authorizeDeviceUrl}?user_code=${data.user_code}`,
|
||||
expires_in: data.expires_in,
|
||||
interval: data.interval || 5,
|
||||
_kimiDeviceId: deviceId,
|
||||
};
|
||||
},
|
||||
pollToken: async (config, deviceCode) => {
|
||||
pollToken: async (config, deviceCode, _codeVerifier, extraData) => {
|
||||
const { buildKimiHeaders } = await import("open-sse/config/appConstants.js");
|
||||
const deviceId = extraData?._kimiDeviceId;
|
||||
const headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
...buildKimiHeaders(deviceId),
|
||||
};
|
||||
const response = await fetch(config.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
||||
headers,
|
||||
body: new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
client_id: config.clientId,
|
||||
@@ -1119,19 +1137,26 @@ const PROVIDERS = {
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (e) {
|
||||
const text = await response.text();
|
||||
data = { error: "invalid_response", error_description: text };
|
||||
} catch {
|
||||
data = { error: "invalid_response", error_description: "non-json token response" };
|
||||
}
|
||||
return { ok: response.ok, data };
|
||||
// CLIProxyAPI: Kimi returns 200 for pending states with error field
|
||||
if (data.error === "authorization_pending" || data.error === "slow_down") {
|
||||
return { ok: true, data };
|
||||
}
|
||||
if (data.access_token && deviceId) data._kimiDeviceId = deviceId;
|
||||
return { ok: response.ok || !!data.access_token || !!data.error, data };
|
||||
},
|
||||
mapTokens: (tokens) => ({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
providerSpecificData: {
|
||||
authMethod: "device_code",
|
||||
...(tokens._kimiDeviceId ? { deviceId: tokens._kimiDeviceId } : {}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
kilocode: {
|
||||
config: KILOCODE_CONFIG,
|
||||
flowType: "device_code",
|
||||
@@ -1520,7 +1545,9 @@ const PROVIDERS = {
|
||||
* Get provider handler
|
||||
*/
|
||||
export function getProvider(name) {
|
||||
const provider = PROVIDERS[name];
|
||||
// Legacy kimi-coding → kimi (dual-auth merge)
|
||||
const key = name === "kimi-coding" ? "kimi" : name;
|
||||
const provider = PROVIDERS[key];
|
||||
if (!provider) {
|
||||
throw new Error(`Unknown provider: ${name}`);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
|
||||
"github",
|
||||
"qwen",
|
||||
"kiro",
|
||||
"kimi",
|
||||
"kimi-coding",
|
||||
"kilocode",
|
||||
"codebuddy-cn",
|
||||
@@ -206,6 +207,8 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
|
||||
_qoderMachineId: data._qoderMachineId,
|
||||
_qoderVerifier: data.codeVerifier,
|
||||
}
|
||||
: (provider === "kimi" || provider === "kimi-coding")
|
||||
? { _kimiDeviceId: data._kimiDeviceId }
|
||||
: null;
|
||||
startPolling(
|
||||
data.device_code,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"kr": "kiro",
|
||||
"cu": "cursor",
|
||||
"kc": "kilocode",
|
||||
"kmc": "kimi-coding",
|
||||
"kmc": "kimi",
|
||||
"cl": "cline",
|
||||
"oc": "opencode",
|
||||
"ocg": "opencode-go",
|
||||
@@ -117,6 +117,7 @@
|
||||
"cursor": "cu",
|
||||
"deepgram": "deepgram",
|
||||
"deepseek": "deepseek",
|
||||
"featherless": "featherless",
|
||||
"fireworks": "fireworks",
|
||||
"gemini": "gemini",
|
||||
"gemini-cli": "gc",
|
||||
@@ -132,7 +133,6 @@
|
||||
"kilocode": "kc",
|
||||
"kimchi": "kimchi",
|
||||
"kimi": "kimi",
|
||||
"kimi-coding": "kmc",
|
||||
"kiro": "kr",
|
||||
"mimo-free": "mmf",
|
||||
"minimax": "minimax",
|
||||
@@ -149,6 +149,7 @@
|
||||
"opencode-go": "opencode-go",
|
||||
"openrouter": "openrouter",
|
||||
"perplexity": "perplexity",
|
||||
"perplexity-agent": "perplexity-agent",
|
||||
"perplexity-web": "perplexity-web",
|
||||
"qoder": "qd",
|
||||
"qwen": "qw",
|
||||
@@ -188,6 +189,7 @@
|
||||
"edge-tts",
|
||||
"elevenlabs-tts-models",
|
||||
"fal-ai",
|
||||
"featherless",
|
||||
"fireworks",
|
||||
"gc",
|
||||
"gcli",
|
||||
@@ -206,7 +208,6 @@
|
||||
"kc",
|
||||
"kimchi",
|
||||
"kimi",
|
||||
"kmc",
|
||||
"kr",
|
||||
"local-device",
|
||||
"minimax",
|
||||
@@ -226,6 +227,7 @@
|
||||
"openrouter-tts-models",
|
||||
"openrouter-tts-voices",
|
||||
"perplexity",
|
||||
"perplexity-agent",
|
||||
"perplexity-web",
|
||||
"qd",
|
||||
"qw",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"alicode-intl": {
|
||||
"baseUrl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions",
|
||||
"baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"headers": {},
|
||||
"quirks": {
|
||||
"preserveCacheControl": true
|
||||
@@ -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://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": {
|
||||
@@ -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": {},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user