mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-17 21:10:54 +00:00
965 lines
33 KiB
TypeScript
965 lines
33 KiB
TypeScript
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
|
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
|
|
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
|
import {
|
|
formatApiKeyPreview,
|
|
normalizeApiKeyInput,
|
|
validateApiKeyInput,
|
|
} from "./auth-choice.api-key.js";
|
|
import { applyAuthChoiceHuggingface } from "./auth-choice.apply.huggingface.js";
|
|
import { applyAuthChoiceOpenRouter } from "./auth-choice.apply.openrouter.js";
|
|
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
|
import {
|
|
applyGoogleGeminiModelDefault,
|
|
GOOGLE_GEMINI_DEFAULT_MODEL,
|
|
} from "./google-gemini-model-default.js";
|
|
import {
|
|
applyAuthProfileConfig,
|
|
applyCloudflareAiGatewayConfig,
|
|
applyCloudflareAiGatewayProviderConfig,
|
|
applyQianfanConfig,
|
|
applyQianfanProviderConfig,
|
|
applyKimiCodeConfig,
|
|
applyKimiCodeProviderConfig,
|
|
applyLitellmConfig,
|
|
applyLitellmProviderConfig,
|
|
applyMoonshotConfig,
|
|
applyMoonshotConfigCn,
|
|
applyMoonshotProviderConfig,
|
|
applyMoonshotProviderConfigCn,
|
|
applyOpencodeZenConfig,
|
|
applyOpencodeZenProviderConfig,
|
|
applySyntheticConfig,
|
|
applySyntheticProviderConfig,
|
|
applyTogetherConfig,
|
|
applyTogetherProviderConfig,
|
|
applyVeniceConfig,
|
|
applyVeniceProviderConfig,
|
|
applyVercelAiGatewayConfig,
|
|
applyVercelAiGatewayProviderConfig,
|
|
applyXiaomiConfig,
|
|
applyXiaomiProviderConfig,
|
|
applyZaiConfig,
|
|
applyZaiProviderConfig,
|
|
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
LITELLM_DEFAULT_MODEL_REF,
|
|
QIANFAN_DEFAULT_MODEL_REF,
|
|
KIMI_CODING_MODEL_REF,
|
|
MOONSHOT_DEFAULT_MODEL_REF,
|
|
SYNTHETIC_DEFAULT_MODEL_REF,
|
|
TOGETHER_DEFAULT_MODEL_REF,
|
|
VENICE_DEFAULT_MODEL_REF,
|
|
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
XIAOMI_DEFAULT_MODEL_REF,
|
|
setCloudflareAiGatewayConfig,
|
|
setQianfanApiKey,
|
|
setGeminiApiKey,
|
|
setLitellmApiKey,
|
|
setKimiCodingApiKey,
|
|
setMoonshotApiKey,
|
|
setOpencodeZenApiKey,
|
|
setSyntheticApiKey,
|
|
setTogetherApiKey,
|
|
setVeniceApiKey,
|
|
setVercelAiGatewayApiKey,
|
|
setXiaomiApiKey,
|
|
setZaiApiKey,
|
|
ZAI_DEFAULT_MODEL_REF,
|
|
} from "./onboard-auth.js";
|
|
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
|
|
import { detectZaiEndpoint } from "./zai-endpoint-detect.js";
|
|
|
|
export async function applyAuthChoiceApiProviders(
|
|
params: ApplyAuthChoiceParams,
|
|
): Promise<ApplyAuthChoiceResult | null> {
|
|
let nextConfig = params.config;
|
|
let agentModelOverride: string | undefined;
|
|
const noteAgentModel = async (model: string) => {
|
|
if (!params.agentId) {
|
|
return;
|
|
}
|
|
await params.prompter.note(
|
|
`Default model set to ${model} for agent "${params.agentId}".`,
|
|
"Model configured",
|
|
);
|
|
};
|
|
|
|
let authChoice = params.authChoice;
|
|
if (
|
|
authChoice === "apiKey" &&
|
|
params.opts?.tokenProvider &&
|
|
params.opts.tokenProvider !== "anthropic" &&
|
|
params.opts.tokenProvider !== "openai"
|
|
) {
|
|
if (params.opts.tokenProvider === "openrouter") {
|
|
authChoice = "openrouter-api-key";
|
|
} else if (params.opts.tokenProvider === "litellm") {
|
|
authChoice = "litellm-api-key";
|
|
} else if (params.opts.tokenProvider === "vercel-ai-gateway") {
|
|
authChoice = "ai-gateway-api-key";
|
|
} else if (params.opts.tokenProvider === "cloudflare-ai-gateway") {
|
|
authChoice = "cloudflare-ai-gateway-api-key";
|
|
} else if (params.opts.tokenProvider === "moonshot") {
|
|
authChoice = "moonshot-api-key";
|
|
} else if (
|
|
params.opts.tokenProvider === "kimi-code" ||
|
|
params.opts.tokenProvider === "kimi-coding"
|
|
) {
|
|
authChoice = "kimi-code-api-key";
|
|
} else if (params.opts.tokenProvider === "google") {
|
|
authChoice = "gemini-api-key";
|
|
} else if (params.opts.tokenProvider === "zai") {
|
|
authChoice = "zai-api-key";
|
|
} else if (params.opts.tokenProvider === "xiaomi") {
|
|
authChoice = "xiaomi-api-key";
|
|
} else if (params.opts.tokenProvider === "synthetic") {
|
|
authChoice = "synthetic-api-key";
|
|
} else if (params.opts.tokenProvider === "venice") {
|
|
authChoice = "venice-api-key";
|
|
} else if (params.opts.tokenProvider === "together") {
|
|
authChoice = "together-api-key";
|
|
} else if (params.opts.tokenProvider === "huggingface") {
|
|
authChoice = "huggingface-api-key";
|
|
} else if (params.opts.tokenProvider === "opencode") {
|
|
authChoice = "opencode-zen";
|
|
} else if (params.opts.tokenProvider === "qianfan") {
|
|
authChoice = "qianfan-api-key";
|
|
}
|
|
}
|
|
|
|
async function ensureMoonshotApiKeyCredential(promptMessage: string): Promise<void> {
|
|
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: promptMessage,
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setMoonshotApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
}
|
|
|
|
if (authChoice === "openrouter-api-key") {
|
|
return applyAuthChoiceOpenRouter(params);
|
|
}
|
|
|
|
if (authChoice === "litellm-api-key") {
|
|
const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
|
|
const profileOrder = resolveAuthProfileOrder({ cfg: nextConfig, store, provider: "litellm" });
|
|
const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
|
|
const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
|
|
let profileId = "litellm:default";
|
|
let hasCredential = false;
|
|
|
|
if (existingProfileId && existingCred?.type === "api_key") {
|
|
profileId = existingProfileId;
|
|
hasCredential = true;
|
|
}
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "litellm") {
|
|
await setLitellmApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
"LiteLLM provides a unified API to 100+ LLM providers.\nGet your API key from your LiteLLM proxy or https://litellm.ai\nDefault proxy runs on http://localhost:4000",
|
|
"LiteLLM",
|
|
);
|
|
const envKey = resolveEnvApiKey("litellm");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing LITELLM_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setLitellmApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter LiteLLM API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setLitellmApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (hasCredential) {
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId,
|
|
provider: "litellm",
|
|
mode: "api_key",
|
|
});
|
|
}
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: LITELLM_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyLitellmConfig,
|
|
applyProviderConfig: applyLitellmProviderConfig,
|
|
noteDefault: LITELLM_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "ai-gateway-api-key") {
|
|
let hasCredential = false;
|
|
|
|
if (
|
|
!hasCredential &&
|
|
params.opts?.token &&
|
|
params.opts?.tokenProvider === "vercel-ai-gateway"
|
|
) {
|
|
await setVercelAiGatewayApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setVercelAiGatewayApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Vercel AI Gateway API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "vercel-ai-gateway:default",
|
|
provider: "vercel-ai-gateway",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyVercelAiGatewayConfig,
|
|
applyProviderConfig: applyVercelAiGatewayProviderConfig,
|
|
noteDefault: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "cloudflare-ai-gateway-api-key") {
|
|
let hasCredential = false;
|
|
let accountId = params.opts?.cloudflareAiGatewayAccountId?.trim() ?? "";
|
|
let gatewayId = params.opts?.cloudflareAiGatewayGatewayId?.trim() ?? "";
|
|
|
|
const ensureAccountGateway = async () => {
|
|
if (!accountId) {
|
|
const value = await params.prompter.text({
|
|
message: "Enter Cloudflare Account ID",
|
|
validate: (val) => (String(val ?? "").trim() ? undefined : "Account ID is required"),
|
|
});
|
|
accountId = String(value ?? "").trim();
|
|
}
|
|
if (!gatewayId) {
|
|
const value = await params.prompter.text({
|
|
message: "Enter Cloudflare AI Gateway ID",
|
|
validate: (val) => (String(val ?? "").trim() ? undefined : "Gateway ID is required"),
|
|
});
|
|
gatewayId = String(value ?? "").trim();
|
|
}
|
|
};
|
|
|
|
const optsApiKey = normalizeApiKeyInput(params.opts?.cloudflareAiGatewayApiKey ?? "");
|
|
if (!hasCredential && accountId && gatewayId && optsApiKey) {
|
|
await setCloudflareAiGatewayConfig(accountId, gatewayId, optsApiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("cloudflare-ai-gateway");
|
|
if (!hasCredential && envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing CLOUDFLARE_AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await ensureAccountGateway();
|
|
await setCloudflareAiGatewayConfig(
|
|
accountId,
|
|
gatewayId,
|
|
normalizeApiKeyInput(envKey.apiKey),
|
|
params.agentDir,
|
|
);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
|
|
if (!hasCredential && optsApiKey) {
|
|
await ensureAccountGateway();
|
|
await setCloudflareAiGatewayConfig(accountId, gatewayId, optsApiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await ensureAccountGateway();
|
|
const key = await params.prompter.text({
|
|
message: "Enter Cloudflare AI Gateway API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setCloudflareAiGatewayConfig(
|
|
accountId,
|
|
gatewayId,
|
|
normalizeApiKeyInput(String(key ?? "")),
|
|
params.agentDir,
|
|
);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (hasCredential) {
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "cloudflare-ai-gateway:default",
|
|
provider: "cloudflare-ai-gateway",
|
|
mode: "api_key",
|
|
});
|
|
}
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: (cfg) =>
|
|
applyCloudflareAiGatewayConfig(cfg, {
|
|
accountId: accountId || params.opts?.cloudflareAiGatewayAccountId,
|
|
gatewayId: gatewayId || params.opts?.cloudflareAiGatewayGatewayId,
|
|
}),
|
|
applyProviderConfig: (cfg) =>
|
|
applyCloudflareAiGatewayProviderConfig(cfg, {
|
|
accountId: accountId || params.opts?.cloudflareAiGatewayAccountId,
|
|
gatewayId: gatewayId || params.opts?.cloudflareAiGatewayGatewayId,
|
|
}),
|
|
noteDefault: CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "moonshot-api-key") {
|
|
await ensureMoonshotApiKeyCredential("Enter Moonshot API key");
|
|
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: applyMoonshotConfig,
|
|
applyProviderConfig: applyMoonshotProviderConfig,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "moonshot-api-key-cn") {
|
|
await ensureMoonshotApiKeyCredential("Enter Moonshot API key (.cn)");
|
|
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: applyMoonshotProviderConfigCn,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "kimi-code-api-key") {
|
|
let hasCredential = false;
|
|
const tokenProvider = params.opts?.tokenProvider?.trim().toLowerCase();
|
|
if (
|
|
!hasCredential &&
|
|
params.opts?.token &&
|
|
(tokenProvider === "kimi-code" || tokenProvider === "kimi-coding")
|
|
) {
|
|
await setKimiCodingApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
[
|
|
"Kimi Coding uses a dedicated endpoint and API key.",
|
|
"Get your API key at: https://www.kimi.com/code/en",
|
|
].join("\n"),
|
|
"Kimi Coding",
|
|
);
|
|
}
|
|
const envKey = resolveEnvApiKey("kimi-coding");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing KIMI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setKimiCodingApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Kimi Coding API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setKimiCodingApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "kimi-coding:default",
|
|
provider: "kimi-coding",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: KIMI_CODING_MODEL_REF,
|
|
applyDefaultConfig: applyKimiCodeConfig,
|
|
applyProviderConfig: applyKimiCodeProviderConfig,
|
|
noteDefault: KIMI_CODING_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "gemini-api-key") {
|
|
let hasCredential = false;
|
|
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "google") {
|
|
await setGeminiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("google");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setGeminiApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Gemini API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setGeminiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "google:default",
|
|
provider: "google",
|
|
mode: "api_key",
|
|
});
|
|
if (params.setDefaultModel) {
|
|
const applied = applyGoogleGeminiModelDefault(nextConfig);
|
|
nextConfig = applied.next;
|
|
if (applied.changed) {
|
|
await params.prompter.note(
|
|
`Default model set to ${GOOGLE_GEMINI_DEFAULT_MODEL}`,
|
|
"Model configured",
|
|
);
|
|
}
|
|
} else {
|
|
agentModelOverride = GOOGLE_GEMINI_DEFAULT_MODEL;
|
|
await noteAgentModel(GOOGLE_GEMINI_DEFAULT_MODEL);
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (
|
|
authChoice === "zai-api-key" ||
|
|
authChoice === "zai-coding-global" ||
|
|
authChoice === "zai-coding-cn" ||
|
|
authChoice === "zai-global" ||
|
|
authChoice === "zai-cn"
|
|
) {
|
|
let endpoint: "global" | "cn" | "coding-global" | "coding-cn" | undefined;
|
|
if (authChoice === "zai-coding-global") {
|
|
endpoint = "coding-global";
|
|
} else if (authChoice === "zai-coding-cn") {
|
|
endpoint = "coding-cn";
|
|
} else if (authChoice === "zai-global") {
|
|
endpoint = "global";
|
|
} else if (authChoice === "zai-cn") {
|
|
endpoint = "cn";
|
|
}
|
|
|
|
// Input API key
|
|
let hasCredential = false;
|
|
let apiKey = "";
|
|
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
|
|
apiKey = normalizeApiKeyInput(params.opts.token);
|
|
await setZaiApiKey(apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("zai");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
apiKey = envKey.apiKey;
|
|
await setZaiApiKey(apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Z.AI API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
apiKey = normalizeApiKeyInput(String(key ?? ""));
|
|
await setZaiApiKey(apiKey, params.agentDir);
|
|
}
|
|
|
|
// zai-api-key: auto-detect endpoint + choose a working default model.
|
|
let modelIdOverride: string | undefined;
|
|
if (!endpoint) {
|
|
const detected = await detectZaiEndpoint({ apiKey });
|
|
if (detected) {
|
|
endpoint = detected.endpoint;
|
|
modelIdOverride = detected.modelId;
|
|
await params.prompter.note(detected.note, "Z.AI endpoint");
|
|
} else {
|
|
endpoint = await params.prompter.select({
|
|
message: "Select Z.AI endpoint",
|
|
options: [
|
|
{
|
|
value: "coding-global",
|
|
label: "Coding-Plan-Global",
|
|
hint: "GLM Coding Plan Global (api.z.ai)",
|
|
},
|
|
{
|
|
value: "coding-cn",
|
|
label: "Coding-Plan-CN",
|
|
hint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
|
},
|
|
{
|
|
value: "global",
|
|
label: "Global",
|
|
hint: "Z.AI Global (api.z.ai)",
|
|
},
|
|
{
|
|
value: "cn",
|
|
label: "CN",
|
|
hint: "Z.AI CN (open.bigmodel.cn)",
|
|
},
|
|
],
|
|
initialValue: "global",
|
|
});
|
|
}
|
|
}
|
|
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "zai:default",
|
|
provider: "zai",
|
|
mode: "api_key",
|
|
});
|
|
|
|
const defaultModel = modelIdOverride ? `zai/${modelIdOverride}` : ZAI_DEFAULT_MODEL_REF;
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel,
|
|
applyDefaultConfig: (config) =>
|
|
applyZaiConfig(config, {
|
|
endpoint,
|
|
...(modelIdOverride ? { modelId: modelIdOverride } : {}),
|
|
}),
|
|
applyProviderConfig: (config) =>
|
|
applyZaiProviderConfig(config, {
|
|
endpoint,
|
|
...(modelIdOverride ? { modelId: modelIdOverride } : {}),
|
|
}),
|
|
noteDefault: defaultModel,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "xiaomi-api-key") {
|
|
let hasCredential = false;
|
|
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "xiaomi") {
|
|
await setXiaomiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("xiaomi");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing XIAOMI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setXiaomiApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Xiaomi API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setXiaomiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "xiaomi:default",
|
|
provider: "xiaomi",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyXiaomiConfig,
|
|
applyProviderConfig: applyXiaomiProviderConfig,
|
|
noteDefault: XIAOMI_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "synthetic-api-key") {
|
|
if (params.opts?.token && params.opts?.tokenProvider === "synthetic") {
|
|
await setSyntheticApiKey(String(params.opts.token ?? "").trim(), params.agentDir);
|
|
} else {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Synthetic API key",
|
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
});
|
|
await setSyntheticApiKey(String(key ?? "").trim(), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "synthetic:default",
|
|
provider: "synthetic",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applySyntheticConfig,
|
|
applyProviderConfig: applySyntheticProviderConfig,
|
|
noteDefault: SYNTHETIC_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "venice-api-key") {
|
|
let hasCredential = false;
|
|
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "venice") {
|
|
await setVeniceApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
[
|
|
"Venice AI provides privacy-focused inference with uncensored models.",
|
|
"Get your API key at: https://venice.ai/settings/api",
|
|
"Supports 'private' (fully private) and 'anonymized' (proxy) modes.",
|
|
].join("\n"),
|
|
"Venice AI",
|
|
);
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("venice");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setVeniceApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Venice AI API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setVeniceApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "venice:default",
|
|
provider: "venice",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: VENICE_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyVeniceConfig,
|
|
applyProviderConfig: applyVeniceProviderConfig,
|
|
noteDefault: VENICE_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "opencode-zen") {
|
|
let hasCredential = false;
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") {
|
|
await setOpencodeZenApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
[
|
|
"OpenCode Zen provides access to Claude, GPT, Gemini, and more models.",
|
|
"Get your API key at: https://opencode.ai/auth",
|
|
"OpenCode Zen bills per request. Check your OpenCode dashboard for details.",
|
|
].join("\n"),
|
|
"OpenCode Zen",
|
|
);
|
|
}
|
|
const envKey = resolveEnvApiKey("opencode");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setOpencodeZenApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter OpenCode Zen API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setOpencodeZenApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "opencode:default",
|
|
provider: "opencode",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
|
|
applyDefaultConfig: applyOpencodeZenConfig,
|
|
applyProviderConfig: applyOpencodeZenProviderConfig,
|
|
noteDefault: OPENCODE_ZEN_DEFAULT_MODEL,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "together-api-key") {
|
|
let hasCredential = false;
|
|
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "together") {
|
|
await setTogetherApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
[
|
|
"Together AI provides access to leading open-source models including Llama, DeepSeek, Qwen, and more.",
|
|
"Get your API key at: https://api.together.xyz/settings/api-keys",
|
|
].join("\n"),
|
|
"Together AI",
|
|
);
|
|
}
|
|
|
|
const envKey = resolveEnvApiKey("together");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing TOGETHER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
await setTogetherApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter Together AI API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
await setTogetherApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "together:default",
|
|
provider: "together",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyTogetherConfig,
|
|
applyProviderConfig: applyTogetherProviderConfig,
|
|
noteDefault: TOGETHER_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
if (authChoice === "huggingface-api-key") {
|
|
return applyAuthChoiceHuggingface({ ...params, authChoice });
|
|
}
|
|
|
|
if (authChoice === "qianfan-api-key") {
|
|
let hasCredential = false;
|
|
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "qianfan") {
|
|
setQianfanApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
|
|
if (!hasCredential) {
|
|
await params.prompter.note(
|
|
[
|
|
"Get your API key at: https://console.bce.baidu.com/qianfan/ais/console/apiKey",
|
|
"API key format: bce-v3/ALTAK-...",
|
|
].join("\n"),
|
|
"QIANFAN",
|
|
);
|
|
}
|
|
const envKey = resolveEnvApiKey("qianfan");
|
|
if (envKey) {
|
|
const useExisting = await params.prompter.confirm({
|
|
message: `Use existing QIANFAN_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
|
initialValue: true,
|
|
});
|
|
if (useExisting) {
|
|
setQianfanApiKey(envKey.apiKey, params.agentDir);
|
|
hasCredential = true;
|
|
}
|
|
}
|
|
if (!hasCredential) {
|
|
const key = await params.prompter.text({
|
|
message: "Enter QIANFAN API key",
|
|
validate: validateApiKeyInput,
|
|
});
|
|
setQianfanApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
|
|
}
|
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
profileId: "qianfan:default",
|
|
provider: "qianfan",
|
|
mode: "api_key",
|
|
});
|
|
{
|
|
const applied = await applyDefaultModelChoice({
|
|
config: nextConfig,
|
|
setDefaultModel: params.setDefaultModel,
|
|
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
|
|
applyDefaultConfig: applyQianfanConfig,
|
|
applyProviderConfig: applyQianfanProviderConfig,
|
|
noteDefault: QIANFAN_DEFAULT_MODEL_REF,
|
|
noteAgentModel,
|
|
prompter: params.prompter,
|
|
});
|
|
nextConfig = applied.config;
|
|
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
|
}
|
|
return { config: nextConfig, agentModelOverride };
|
|
}
|
|
|
|
return null;
|
|
}
|