feat: add support for Moonshot API key for China endpoint

This commit is contained in:
Liu Weizhan
2026-02-02 20:25:14 +08:00
committed by Peter Steinberger
parent 9f16de2533
commit 1c6b25ddbb
13 changed files with 143 additions and 12 deletions

View File

@@ -61,6 +61,7 @@ describe("buildAuthChoiceOptions", () => {
});
expect(options.some((opt) => opt.value === "moonshot-api-key")).toBe(true);
expect(options.some((opt) => opt.value === "moonshot-api-key-cn")).toBe(true);
expect(options.some((opt) => opt.value === "kimi-code-api-key")).toBe(true);
});

View File

@@ -56,9 +56,9 @@ const AUTH_CHOICE_GROUP_DEFS: {
},
{
value: "moonshot",
label: "Moonshot AI",
hint: "Kimi K2 + Kimi Coding",
choices: ["moonshot-api-key", "kimi-code-api-key"],
label: "Moonshot AI (Kimi K2.5)",
hint: "Kimi K2.5 + Kimi Coding",
choices: ["moonshot-api-key", "moonshot-api-key-cn", "kimi-code-api-key"],
},
{
value: "google",
@@ -146,8 +146,15 @@ export function buildAuthChoiceOptions(params: {
value: "ai-gateway-api-key",
label: "Vercel AI Gateway API key",
});
options.push({ value: "moonshot-api-key", label: "Moonshot AI API key" });
options.push({ value: "kimi-code-api-key", label: "Kimi Coding API key" });
options.push({
value: "moonshot-api-key",
label: "Kimi API key (.ai)",
});
options.push({
value: "moonshot-api-key-cn",
label: "Kimi API key (.cn)",
});
options.push({ value: "kimi-code-api-key", label: "Kimi Code API key (subscription)" });
options.push({ value: "synthetic-api-key", label: "Synthetic API key" });
options.push({
value: "venice-api-key",

View File

@@ -16,6 +16,7 @@ import {
applyKimiCodeConfig,
applyKimiCodeProviderConfig,
applyMoonshotConfig,
applyMoonshotConfigCn,
applyMoonshotProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
@@ -276,6 +277,61 @@ export async function applyAuthChoiceApiProviders(
return { config: nextConfig, agentModelOverride };
}
if (authChoice === "moonshot-api-key-cn") {
let hasCredential = false;
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "moonshot") {
await setMoonshotApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
hasCredential = true;
}
const envKey = resolveEnvApiKey("moonshot");
if (envKey) {
const useExisting = await params.prompter.confirm({
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true,
});
if (useExisting) {
await setMoonshotApiKey(envKey.apiKey, params.agentDir);
hasCredential = true;
}
}
if (!hasCredential) {
const key = await params.prompter.text({
message: "Enter Moonshot API key (.cn)",
validate: validateApiKeyInput,
});
await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "moonshot:default",
provider: "moonshot",
mode: "api_key",
});
{
const applied = await applyDefaultModelChoice({
config: nextConfig,
setDefaultModel: params.setDefaultModel,
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMoonshotConfigCn,
applyProviderConfig: (cfg) => applyMoonshotProviderConfigCnShim(cfg),
noteAgentModel,
prompter: params.prompter,
});
nextConfig = applied.config;
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
}
return { config: nextConfig, agentModelOverride };
}
function applyMoonshotProviderConfigCnShim(
cfg: Parameters<typeof applyMoonshotProviderConfig>[0],
) {
// For now, provider-level CN behavior is fully handled inside applyMoonshotConfigCn.
// Keep a thin shim to satisfy the applyDefaultModelChoice signature.
return applyMoonshotProviderConfig(cfg);
}
if (authChoice === "kimi-code-api-key") {
let hasCredential = false;
const tokenProvider = params.opts?.tokenProvider?.trim().toLowerCase();

View File

@@ -13,6 +13,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
"openrouter-api-key": "openrouter",
"ai-gateway-api-key": "vercel-ai-gateway",
"moonshot-api-key": "moonshot",
"moonshot-api-key-cn": "moonshot",
"kimi-code-api-key": "kimi-coding",
"gemini-api-key": "google",
"google-antigravity": "google-antigravity",

View File

@@ -22,6 +22,7 @@ import {
buildMoonshotModelDefinition,
KIMI_CODING_MODEL_REF,
MOONSHOT_BASE_URL,
MOONSHOT_CN_BASE_URL,
MOONSHOT_DEFAULT_MODEL_ID,
MOONSHOT_DEFAULT_MODEL_REF,
} from "./onboard-auth.models.js";
@@ -137,10 +138,17 @@ export function applyOpenrouterConfig(cfg: OpenClawConfig): OpenClawConfig {
}
export function applyMoonshotProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_BASE_URL);
}
function applyMoonshotProviderConfigWithBaseUrl(
cfg: OpenClawConfig,
baseUrl: string,
): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[MOONSHOT_DEFAULT_MODEL_REF] = {
...models[MOONSHOT_DEFAULT_MODEL_REF],
alias: models[MOONSHOT_DEFAULT_MODEL_REF]?.alias ?? "Kimi K2",
alias: models[MOONSHOT_DEFAULT_MODEL_REF]?.alias ?? "Kimi",
};
const providers = { ...cfg.models?.providers };
@@ -157,7 +165,7 @@ export function applyMoonshotProviderConfig(cfg: OpenClawConfig): OpenClawConfig
const normalizedApiKey = resolvedApiKey?.trim();
providers.moonshot = {
...existingProviderRest,
baseUrl: MOONSHOT_BASE_URL,
baseUrl,
api: "openai-completions",
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
models: mergedModels.length > 0 ? mergedModels : [defaultModel],
@@ -201,6 +209,28 @@ export function applyMoonshotConfig(cfg: OpenClawConfig): OpenClawConfig {
};
}
export function applyMoonshotConfigCn(cfg: OpenClawConfig): OpenClawConfig {
const next = applyMoonshotProviderConfigWithBaseUrl(cfg, MOONSHOT_CN_BASE_URL);
const existingModel = next.agents?.defaults?.model;
return {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
model: {
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
? {
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
}
: undefined),
primary: MOONSHOT_DEFAULT_MODEL_REF,
},
},
},
};
}
export function applyKimiCodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[KIMI_CODING_MODEL_REF] = {

View File

@@ -8,7 +8,8 @@ export const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000;
export const DEFAULT_MINIMAX_MAX_TOKENS = 8192;
export const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
export const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2-0905-preview";
export const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1";
export const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5";
export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`;
export const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000;
export const MOONSHOT_DEFAULT_MAX_TOKENS = 8192;
@@ -83,7 +84,7 @@ export function buildMinimaxApiModelDefinition(modelId: string): ModelDefinition
export function buildMoonshotModelDefinition(): ModelDefinitionConfig {
return {
id: MOONSHOT_DEFAULT_MODEL_ID,
name: "Kimi K2 0905 Preview",
name: "Kimi K2.5",
reasoning: false,
input: ["text"],
cost: MOONSHOT_DEFAULT_COST,

View File

@@ -8,6 +8,7 @@ export {
applyKimiCodeConfig,
applyKimiCodeProviderConfig,
applyMoonshotConfig,
applyMoonshotConfigCn,
applyMoonshotProviderConfig,
applyOpenrouterConfig,
applyOpenrouterProviderConfig,
@@ -58,6 +59,7 @@ export {
buildMinimaxModelDefinition,
buildMoonshotModelDefinition,
DEFAULT_MINIMAX_BASE_URL,
MOONSHOT_CN_BASE_URL,
KIMI_CODING_MODEL_ID,
KIMI_CODING_MODEL_REF,
MINIMAX_API_BASE_URL,

View File

@@ -14,6 +14,7 @@ import {
applyMinimaxApiConfig,
applyMinimaxConfig,
applyMoonshotConfig,
applyMoonshotConfigCn,
applyOpencodeZenConfig,
applyOpenrouterConfig,
applySyntheticConfig,
@@ -303,6 +304,29 @@ export async function applyNonInteractiveAuthChoice(params: {
return applyMoonshotConfig(nextConfig);
}
if (authChoice === "moonshot-api-key-cn") {
const resolved = await resolveNonInteractiveApiKey({
provider: "moonshot",
cfg: baseConfig,
flagValue: opts.moonshotApiKey,
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
runtime,
});
if (!resolved) {
return null;
}
if (resolved.source !== "profile") {
await setMoonshotApiKey(resolved.key);
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "moonshot:default",
provider: "moonshot",
mode: "api_key",
});
return applyMoonshotConfigCn(nextConfig);
}
if (authChoice === "kimi-code-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "kimi-coding",

View File

@@ -14,6 +14,7 @@ export type AuthChoice =
| "openrouter-api-key"
| "ai-gateway-api-key"
| "moonshot-api-key"
| "moonshot-api-key-cn"
| "kimi-code-api-key"
| "synthetic-api-key"
| "venice-api-key"