refactor: split simple api-key auth providers

This commit is contained in:
Peter Steinberger
2026-03-12 22:38:23 +00:00
parent fd2b06d463
commit 300a093121
2 changed files with 569 additions and 463 deletions

View File

@@ -0,0 +1,538 @@
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
import type { SecretInput } from "../config/types.secrets.js";
import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js";
import { ensureApiKeyFromOptionEnvOrPrompt } from "./auth-choice.apply-helpers.js";
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
import type { ApiKeyStorageOptions } from "./onboard-auth.credentials.js";
import {
applyAuthProfileConfig,
applyKilocodeConfig,
applyKilocodeProviderConfig,
applyKimiCodeConfig,
applyKimiCodeProviderConfig,
applyLitellmConfig,
applyLitellmProviderConfig,
applyMistralConfig,
applyMistralProviderConfig,
applyModelStudioConfig,
applyModelStudioConfigCn,
applyModelStudioProviderConfig,
applyModelStudioProviderConfigCn,
applyMoonshotConfig,
applyMoonshotConfigCn,
applyMoonshotProviderConfig,
applyMoonshotProviderConfigCn,
applyOpencodeGoConfig,
applyOpencodeGoProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
applyQianfanConfig,
applyQianfanProviderConfig,
applySyntheticConfig,
applySyntheticProviderConfig,
applyTogetherConfig,
applyTogetherProviderConfig,
applyVeniceConfig,
applyVeniceProviderConfig,
applyVercelAiGatewayConfig,
applyVercelAiGatewayProviderConfig,
applyXiaomiConfig,
applyXiaomiProviderConfig,
KILOCODE_DEFAULT_MODEL_REF,
KIMI_CODING_MODEL_REF,
LITELLM_DEFAULT_MODEL_REF,
MISTRAL_DEFAULT_MODEL_REF,
MODELSTUDIO_DEFAULT_MODEL_REF,
MOONSHOT_DEFAULT_MODEL_REF,
QIANFAN_DEFAULT_MODEL_REF,
setKilocodeApiKey,
setKimiCodingApiKey,
setLitellmApiKey,
setMistralApiKey,
setModelStudioApiKey,
setMoonshotApiKey,
setOpencodeGoApiKey,
setOpencodeZenApiKey,
setQianfanApiKey,
setSyntheticApiKey,
setTogetherApiKey,
setVeniceApiKey,
setVercelAiGatewayApiKey,
setXiaomiApiKey,
SYNTHETIC_DEFAULT_MODEL_REF,
TOGETHER_DEFAULT_MODEL_REF,
VENICE_DEFAULT_MODEL_REF,
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
XIAOMI_DEFAULT_MODEL_REF,
} from "./onboard-auth.js";
import type { AuthChoice, SecretInputMode } from "./onboard-types.js";
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "./opencode-go-model-default.js";
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
type ApiKeyProviderConfigApplier = (
config: ApplyAuthChoiceParams["config"],
) => ApplyAuthChoiceParams["config"];
type ApplyProviderDefaultModel = (args: {
defaultModel: string;
applyDefaultConfig: ApiKeyProviderConfigApplier;
applyProviderConfig: ApiKeyProviderConfigApplier;
noteDefault?: string;
}) => Promise<void>;
type ApplyApiKeyProviderParams = {
params: ApplyAuthChoiceParams;
authChoice: AuthChoice;
config: ApplyAuthChoiceParams["config"];
setConfig: (config: ApplyAuthChoiceParams["config"]) => void;
getConfig: () => ApplyAuthChoiceParams["config"];
normalizedTokenProvider?: string;
requestedSecretInputMode?: SecretInputMode;
applyProviderDefaultModel: ApplyProviderDefaultModel;
getAgentModelOverride: () => string | undefined;
};
type SimpleApiKeyProviderFlow = {
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
profileId: string;
expectedProviders: string[];
envLabel: string;
promptMessage: string;
setCredential: (
apiKey: SecretInput,
agentDir?: string,
options?: ApiKeyStorageOptions,
) => void | Promise<void>;
defaultModel: string;
applyDefaultConfig: ApiKeyProviderConfigApplier;
applyProviderConfig: ApiKeyProviderConfigApplier;
tokenProvider?: string;
normalize?: (value: string) => string;
validate?: (value: string) => string | undefined;
noteDefault?: string;
noteMessage?: string;
noteTitle?: string;
};
const SIMPLE_API_KEY_PROVIDER_FLOWS: Partial<Record<AuthChoice, SimpleApiKeyProviderFlow>> = {
"ai-gateway-api-key": {
provider: "vercel-ai-gateway",
profileId: "vercel-ai-gateway:default",
expectedProviders: ["vercel-ai-gateway"],
envLabel: "AI_GATEWAY_API_KEY",
promptMessage: "Enter Vercel AI Gateway API key",
setCredential: setVercelAiGatewayApiKey,
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
applyDefaultConfig: applyVercelAiGatewayConfig,
applyProviderConfig: applyVercelAiGatewayProviderConfig,
noteDefault: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
},
"moonshot-api-key": {
provider: "moonshot",
profileId: "moonshot:default",
expectedProviders: ["moonshot"],
envLabel: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key",
setCredential: setMoonshotApiKey,
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMoonshotConfig,
applyProviderConfig: applyMoonshotProviderConfig,
},
"moonshot-api-key-cn": {
provider: "moonshot",
profileId: "moonshot:default",
expectedProviders: ["moonshot"],
envLabel: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key (.cn)",
setCredential: setMoonshotApiKey,
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMoonshotConfigCn,
applyProviderConfig: applyMoonshotProviderConfigCn,
},
"kimi-code-api-key": {
provider: "kimi-coding",
profileId: "kimi-coding:default",
expectedProviders: ["kimi-code", "kimi-coding"],
envLabel: "KIMI_API_KEY",
promptMessage: "Enter Kimi Coding API key",
setCredential: setKimiCodingApiKey,
defaultModel: KIMI_CODING_MODEL_REF,
applyDefaultConfig: applyKimiCodeConfig,
applyProviderConfig: applyKimiCodeProviderConfig,
noteDefault: KIMI_CODING_MODEL_REF,
noteMessage: [
"Kimi Coding uses a dedicated endpoint and API key.",
"Get your API key at: https://www.kimi.com/code/en",
].join("\n"),
noteTitle: "Kimi Coding",
},
"xiaomi-api-key": {
provider: "xiaomi",
profileId: "xiaomi:default",
expectedProviders: ["xiaomi"],
envLabel: "XIAOMI_API_KEY",
promptMessage: "Enter Xiaomi API key",
setCredential: setXiaomiApiKey,
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
applyDefaultConfig: applyXiaomiConfig,
applyProviderConfig: applyXiaomiProviderConfig,
noteDefault: XIAOMI_DEFAULT_MODEL_REF,
},
"mistral-api-key": {
provider: "mistral",
profileId: "mistral:default",
expectedProviders: ["mistral"],
envLabel: "MISTRAL_API_KEY",
promptMessage: "Enter Mistral API key",
setCredential: setMistralApiKey,
defaultModel: MISTRAL_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMistralConfig,
applyProviderConfig: applyMistralProviderConfig,
noteDefault: MISTRAL_DEFAULT_MODEL_REF,
},
"venice-api-key": {
provider: "venice",
profileId: "venice:default",
expectedProviders: ["venice"],
envLabel: "VENICE_API_KEY",
promptMessage: "Enter Venice AI API key",
setCredential: setVeniceApiKey,
defaultModel: VENICE_DEFAULT_MODEL_REF,
applyDefaultConfig: applyVeniceConfig,
applyProviderConfig: applyVeniceProviderConfig,
noteDefault: VENICE_DEFAULT_MODEL_REF,
noteMessage: [
"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"),
noteTitle: "Venice AI",
},
"opencode-zen": {
provider: "opencode",
profileId: "opencode:default",
expectedProviders: ["opencode", "opencode-go"],
envLabel: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
setCredential: setOpencodeZenApiKey,
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
applyDefaultConfig: applyOpencodeZenConfig,
applyProviderConfig: applyOpencodeZenProviderConfig,
noteDefault: OPENCODE_ZEN_DEFAULT_MODEL,
noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.",
"Zen provides access to Claude, GPT, Gemini, and more models.",
"Get your API key at: https://opencode.ai/auth",
"Choose the Zen catalog when you want the curated multi-model proxy.",
].join("\n"),
noteTitle: "OpenCode",
},
"opencode-go": {
provider: "opencode-go",
profileId: "opencode-go:default",
expectedProviders: ["opencode", "opencode-go"],
envLabel: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
setCredential: setOpencodeGoApiKey,
defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyOpencodeGoConfig,
applyProviderConfig: applyOpencodeGoProviderConfig,
noteDefault: OPENCODE_GO_DEFAULT_MODEL_REF,
noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.",
"Go provides access to Kimi, GLM, and MiniMax models through the Go catalog.",
"Get your API key at: https://opencode.ai/auth",
"Choose the Go catalog when you want the OpenCode-hosted Kimi/GLM/MiniMax lineup.",
].join("\n"),
noteTitle: "OpenCode",
},
"together-api-key": {
provider: "together",
profileId: "together:default",
expectedProviders: ["together"],
envLabel: "TOGETHER_API_KEY",
promptMessage: "Enter Together AI API key",
setCredential: setTogetherApiKey,
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
applyDefaultConfig: applyTogetherConfig,
applyProviderConfig: applyTogetherProviderConfig,
noteDefault: TOGETHER_DEFAULT_MODEL_REF,
noteMessage: [
"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"),
noteTitle: "Together AI",
},
"qianfan-api-key": {
provider: "qianfan",
profileId: "qianfan:default",
expectedProviders: ["qianfan"],
envLabel: "QIANFAN_API_KEY",
promptMessage: "Enter QIANFAN API key",
setCredential: setQianfanApiKey,
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
applyDefaultConfig: applyQianfanConfig,
applyProviderConfig: applyQianfanProviderConfig,
noteDefault: QIANFAN_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://console.bce.baidu.com/qianfan/ais/console/apiKey",
"API key format: bce-v3/ALTAK-...",
].join("\n"),
noteTitle: "QIANFAN",
},
"kilocode-api-key": {
provider: "kilocode",
profileId: "kilocode:default",
expectedProviders: ["kilocode"],
envLabel: "KILOCODE_API_KEY",
promptMessage: "Enter Kilo Gateway API key",
setCredential: setKilocodeApiKey,
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
applyDefaultConfig: applyKilocodeConfig,
applyProviderConfig: applyKilocodeProviderConfig,
noteDefault: KILOCODE_DEFAULT_MODEL_REF,
},
"modelstudio-api-key-cn": {
provider: "modelstudio",
profileId: "modelstudio:default",
expectedProviders: ["modelstudio"],
envLabel: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (China)",
setCredential: setModelStudioApiKey,
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyModelStudioConfigCn,
applyProviderConfig: applyModelStudioProviderConfigCn,
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (China)",
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
"modelstudio-api-key": {
provider: "modelstudio",
profileId: "modelstudio:default",
expectedProviders: ["modelstudio"],
envLabel: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)",
setCredential: setModelStudioApiKey,
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyModelStudioConfig,
applyProviderConfig: applyModelStudioProviderConfig,
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding-intl.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (Global/Intl)",
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
"synthetic-api-key": {
provider: "synthetic",
profileId: "synthetic:default",
expectedProviders: ["synthetic"],
envLabel: "SYNTHETIC_API_KEY",
promptMessage: "Enter Synthetic API key",
setCredential: setSyntheticApiKey,
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
applyDefaultConfig: applySyntheticConfig,
applyProviderConfig: applySyntheticProviderConfig,
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
};
async function applyApiKeyProviderWithDefaultModel({
params,
config,
setConfig,
getConfig,
normalizedTokenProvider,
requestedSecretInputMode,
applyProviderDefaultModel,
getAgentModelOverride,
provider,
profileId,
expectedProviders,
envLabel,
promptMessage,
setCredential,
defaultModel,
applyDefaultConfig,
applyProviderConfig,
noteMessage,
noteTitle,
tokenProvider = normalizedTokenProvider,
normalize = normalizeApiKeyInput,
validate = validateApiKeyInput,
noteDefault = defaultModel,
}: ApplyApiKeyProviderParams & {
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
profileId: string;
expectedProviders: string[];
envLabel: string;
promptMessage: string;
setCredential: (apiKey: SecretInput, mode?: SecretInputMode) => void | Promise<void>;
defaultModel: string;
applyDefaultConfig: ApiKeyProviderConfigApplier;
applyProviderConfig: ApiKeyProviderConfigApplier;
noteMessage?: string;
noteTitle?: string;
tokenProvider?: string;
normalize?: (value: string) => string;
validate?: (value: string) => string | undefined;
noteDefault?: string;
}): Promise<ApplyAuthChoiceResult> {
let nextConfig = config;
await ensureApiKeyFromOptionEnvOrPrompt({
token: params.opts?.token,
provider,
tokenProvider,
secretInputMode: requestedSecretInputMode,
config: nextConfig,
expectedProviders,
envLabel,
promptMessage,
setCredential: async (apiKey, mode) => {
await setCredential(apiKey, mode);
},
noteMessage,
noteTitle,
normalize,
validate,
prompter: params.prompter,
});
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider,
mode: "api_key",
});
setConfig(nextConfig);
await applyProviderDefaultModel({
defaultModel,
applyDefaultConfig,
applyProviderConfig,
noteDefault,
});
return { config: getConfig(), agentModelOverride: getAgentModelOverride() };
}
export async function applyLiteLlmApiKeyProvider({
params,
authChoice,
config,
setConfig,
getConfig,
normalizedTokenProvider,
requestedSecretInputMode,
applyProviderDefaultModel,
getAgentModelOverride,
}: ApplyApiKeyProviderParams): Promise<ApplyAuthChoiceResult | null> {
if (authChoice !== "litellm-api-key") {
return null;
}
let nextConfig = config;
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 = Boolean(existingProfileId && existingCred?.type === "api_key");
if (hasCredential && existingProfileId) {
profileId = existingProfileId;
}
if (!hasCredential) {
await ensureApiKeyFromOptionEnvOrPrompt({
token: params.opts?.token,
tokenProvider: normalizedTokenProvider,
secretInputMode: requestedSecretInputMode,
config: nextConfig,
expectedProviders: ["litellm"],
provider: "litellm",
envLabel: "LITELLM_API_KEY",
promptMessage: "Enter LiteLLM API key",
normalize: normalizeApiKeyInput,
validate: validateApiKeyInput,
prompter: params.prompter,
setCredential: async (apiKey, mode) =>
setLitellmApiKey(apiKey, params.agentDir, { secretInputMode: mode }),
noteMessage:
"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",
noteTitle: "LiteLLM",
});
hasCredential = true;
}
if (hasCredential) {
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider: "litellm",
mode: "api_key",
});
}
setConfig(nextConfig);
await applyProviderDefaultModel({
defaultModel: LITELLM_DEFAULT_MODEL_REF,
applyDefaultConfig: applyLitellmConfig,
applyProviderConfig: applyLitellmProviderConfig,
noteDefault: LITELLM_DEFAULT_MODEL_REF,
});
return { config: getConfig(), agentModelOverride: getAgentModelOverride() };
}
export async function applySimpleAuthChoiceApiProvider({
params,
authChoice,
config,
setConfig,
getConfig,
normalizedTokenProvider,
requestedSecretInputMode,
applyProviderDefaultModel,
getAgentModelOverride,
}: ApplyApiKeyProviderParams): Promise<ApplyAuthChoiceResult | null> {
const simpleApiKeyProviderFlow = SIMPLE_API_KEY_PROVIDER_FLOWS[authChoice];
if (!simpleApiKeyProviderFlow) {
return null;
}
return await applyApiKeyProviderWithDefaultModel({
params,
authChoice,
config,
setConfig,
getConfig,
normalizedTokenProvider,
requestedSecretInputMode,
applyProviderDefaultModel,
getAgentModelOverride,
provider: simpleApiKeyProviderFlow.provider,
profileId: simpleApiKeyProviderFlow.profileId,
expectedProviders: simpleApiKeyProviderFlow.expectedProviders,
envLabel: simpleApiKeyProviderFlow.envLabel,
promptMessage: simpleApiKeyProviderFlow.promptMessage,
setCredential: async (apiKey, mode) =>
simpleApiKeyProviderFlow.setCredential(apiKey, params.agentDir, {
secretInputMode: mode ?? requestedSecretInputMode,
}),
defaultModel: simpleApiKeyProviderFlow.defaultModel,
applyDefaultConfig: simpleApiKeyProviderFlow.applyDefaultConfig,
applyProviderConfig: simpleApiKeyProviderFlow.applyProviderConfig,
noteDefault: simpleApiKeyProviderFlow.noteDefault,
noteMessage: simpleApiKeyProviderFlow.noteMessage,
noteTitle: simpleApiKeyProviderFlow.noteTitle,
tokenProvider: simpleApiKeyProviderFlow.tokenProvider,
normalize: simpleApiKeyProviderFlow.normalize,
validate: simpleApiKeyProviderFlow.validate,
});
}

View File

@@ -1,5 +1,3 @@
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
import type { SecretInput } from "../config/types.secrets.js";
import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js"; import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js";
import { import {
normalizeSecretInputModeInput, normalizeSecretInputModeInput,
@@ -8,6 +6,10 @@ import {
ensureApiKeyFromOptionEnvOrPrompt, ensureApiKeyFromOptionEnvOrPrompt,
normalizeTokenProviderInput, normalizeTokenProviderInput,
} from "./auth-choice.apply-helpers.js"; } from "./auth-choice.apply-helpers.js";
import {
applyLiteLlmApiKeyProvider,
applySimpleAuthChoiceApiProvider,
} from "./auth-choice.apply.api-key-providers.js";
import { applyAuthChoiceHuggingface } from "./auth-choice.apply.huggingface.js"; import { applyAuthChoiceHuggingface } from "./auth-choice.apply.huggingface.js";
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
import { applyAuthChoiceOpenRouter } from "./auth-choice.apply.openrouter.js"; import { applyAuthChoiceOpenRouter } from "./auth-choice.apply.openrouter.js";
@@ -15,80 +17,19 @@ import {
applyGoogleGeminiModelDefault, applyGoogleGeminiModelDefault,
GOOGLE_GEMINI_DEFAULT_MODEL, GOOGLE_GEMINI_DEFAULT_MODEL,
} from "./google-gemini-model-default.js"; } from "./google-gemini-model-default.js";
import type { ApiKeyStorageOptions } from "./onboard-auth.credentials.js";
import { import {
applyAuthProfileConfig, applyAuthProfileConfig,
applyCloudflareAiGatewayConfig, applyCloudflareAiGatewayConfig,
applyCloudflareAiGatewayProviderConfig, applyCloudflareAiGatewayProviderConfig,
applyKilocodeConfig,
applyKilocodeProviderConfig,
applyQianfanConfig,
applyQianfanProviderConfig,
applyKimiCodeConfig,
applyKimiCodeProviderConfig,
applyLitellmConfig,
applyLitellmProviderConfig,
applyMistralConfig,
applyMistralProviderConfig,
applyMoonshotConfig,
applyMoonshotConfigCn,
applyMoonshotProviderConfig,
applyMoonshotProviderConfigCn,
applyOpencodeGoConfig,
applyOpencodeGoProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
applySyntheticConfig,
applySyntheticProviderConfig,
applyTogetherConfig,
applyTogetherProviderConfig,
applyVeniceConfig,
applyVeniceProviderConfig,
applyVercelAiGatewayConfig,
applyVercelAiGatewayProviderConfig,
applyXiaomiConfig,
applyXiaomiProviderConfig,
applyZaiConfig, applyZaiConfig,
applyZaiProviderConfig, applyZaiProviderConfig,
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
KILOCODE_DEFAULT_MODEL_REF,
LITELLM_DEFAULT_MODEL_REF,
QIANFAN_DEFAULT_MODEL_REF,
KIMI_CODING_MODEL_REF,
MOONSHOT_DEFAULT_MODEL_REF,
MISTRAL_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, setCloudflareAiGatewayConfig,
setQianfanApiKey,
setGeminiApiKey, setGeminiApiKey,
setKilocodeApiKey,
setLitellmApiKey,
setKimiCodingApiKey,
setMistralApiKey,
setMoonshotApiKey,
setOpencodeGoApiKey,
setOpencodeZenApiKey,
setSyntheticApiKey,
setTogetherApiKey,
setVeniceApiKey,
setVercelAiGatewayApiKey,
setXiaomiApiKey,
setZaiApiKey, setZaiApiKey,
ZAI_DEFAULT_MODEL_REF, ZAI_DEFAULT_MODEL_REF,
MODELSTUDIO_DEFAULT_MODEL_REF,
applyModelStudioConfig,
applyModelStudioConfigCn,
applyModelStudioProviderConfig,
applyModelStudioProviderConfigCn,
setModelStudioApiKey,
} from "./onboard-auth.js"; } from "./onboard-auth.js";
import type { AuthChoice, SecretInputMode } from "./onboard-types.js"; import type { AuthChoice } from "./onboard-types.js";
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "./opencode-go-model-default.js";
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
import { detectZaiEndpoint } from "./zai-endpoint-detect.js"; import { detectZaiEndpoint } from "./zai-endpoint-detect.js";
const API_KEY_TOKEN_PROVIDER_AUTH_CHOICE: Record<string, AuthChoice> = { const API_KEY_TOKEN_PROVIDER_AUTH_CHOICE: Record<string, AuthChoice> = {
@@ -122,265 +63,6 @@ const ZAI_AUTH_CHOICE_ENDPOINT: Partial<
"zai-cn": "cn", "zai-cn": "cn",
}; };
type ApiKeyProviderConfigApplier = (
config: ApplyAuthChoiceParams["config"],
) => ApplyAuthChoiceParams["config"];
type SimpleApiKeyProviderFlow = {
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
profileId: string;
expectedProviders: string[];
envLabel: string;
promptMessage: string;
setCredential: (
apiKey: SecretInput,
agentDir?: string,
options?: ApiKeyStorageOptions,
) => void | Promise<void>;
defaultModel: string;
applyDefaultConfig: ApiKeyProviderConfigApplier;
applyProviderConfig: ApiKeyProviderConfigApplier;
tokenProvider?: string;
normalize?: (value: string) => string;
validate?: (value: string) => string | undefined;
noteDefault?: string;
noteMessage?: string;
noteTitle?: string;
};
const SIMPLE_API_KEY_PROVIDER_FLOWS: Partial<Record<AuthChoice, SimpleApiKeyProviderFlow>> = {
"ai-gateway-api-key": {
provider: "vercel-ai-gateway",
profileId: "vercel-ai-gateway:default",
expectedProviders: ["vercel-ai-gateway"],
envLabel: "AI_GATEWAY_API_KEY",
promptMessage: "Enter Vercel AI Gateway API key",
setCredential: setVercelAiGatewayApiKey,
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
applyDefaultConfig: applyVercelAiGatewayConfig,
applyProviderConfig: applyVercelAiGatewayProviderConfig,
noteDefault: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
},
"moonshot-api-key": {
provider: "moonshot",
profileId: "moonshot:default",
expectedProviders: ["moonshot"],
envLabel: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key",
setCredential: setMoonshotApiKey,
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMoonshotConfig,
applyProviderConfig: applyMoonshotProviderConfig,
},
"moonshot-api-key-cn": {
provider: "moonshot",
profileId: "moonshot:default",
expectedProviders: ["moonshot"],
envLabel: "MOONSHOT_API_KEY",
promptMessage: "Enter Moonshot API key (.cn)",
setCredential: setMoonshotApiKey,
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMoonshotConfigCn,
applyProviderConfig: applyMoonshotProviderConfigCn,
},
"kimi-code-api-key": {
provider: "kimi-coding",
profileId: "kimi-coding:default",
expectedProviders: ["kimi-code", "kimi-coding"],
envLabel: "KIMI_API_KEY",
promptMessage: "Enter Kimi Coding API key",
setCredential: setKimiCodingApiKey,
defaultModel: KIMI_CODING_MODEL_REF,
applyDefaultConfig: applyKimiCodeConfig,
applyProviderConfig: applyKimiCodeProviderConfig,
noteDefault: KIMI_CODING_MODEL_REF,
noteMessage: [
"Kimi Coding uses a dedicated endpoint and API key.",
"Get your API key at: https://www.kimi.com/code/en",
].join("\n"),
noteTitle: "Kimi Coding",
},
"xiaomi-api-key": {
provider: "xiaomi",
profileId: "xiaomi:default",
expectedProviders: ["xiaomi"],
envLabel: "XIAOMI_API_KEY",
promptMessage: "Enter Xiaomi API key",
setCredential: setXiaomiApiKey,
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
applyDefaultConfig: applyXiaomiConfig,
applyProviderConfig: applyXiaomiProviderConfig,
noteDefault: XIAOMI_DEFAULT_MODEL_REF,
},
"mistral-api-key": {
provider: "mistral",
profileId: "mistral:default",
expectedProviders: ["mistral"],
envLabel: "MISTRAL_API_KEY",
promptMessage: "Enter Mistral API key",
setCredential: setMistralApiKey,
defaultModel: MISTRAL_DEFAULT_MODEL_REF,
applyDefaultConfig: applyMistralConfig,
applyProviderConfig: applyMistralProviderConfig,
noteDefault: MISTRAL_DEFAULT_MODEL_REF,
},
"venice-api-key": {
provider: "venice",
profileId: "venice:default",
expectedProviders: ["venice"],
envLabel: "VENICE_API_KEY",
promptMessage: "Enter Venice AI API key",
setCredential: setVeniceApiKey,
defaultModel: VENICE_DEFAULT_MODEL_REF,
applyDefaultConfig: applyVeniceConfig,
applyProviderConfig: applyVeniceProviderConfig,
noteDefault: VENICE_DEFAULT_MODEL_REF,
noteMessage: [
"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"),
noteTitle: "Venice AI",
},
"opencode-zen": {
provider: "opencode",
profileId: "opencode:default",
expectedProviders: ["opencode", "opencode-go"],
envLabel: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
setCredential: setOpencodeZenApiKey,
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
applyDefaultConfig: applyOpencodeZenConfig,
applyProviderConfig: applyOpencodeZenProviderConfig,
noteDefault: OPENCODE_ZEN_DEFAULT_MODEL,
noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.",
"Zen provides access to Claude, GPT, Gemini, and more models.",
"Get your API key at: https://opencode.ai/auth",
"Choose the Zen catalog when you want the curated multi-model proxy.",
].join("\n"),
noteTitle: "OpenCode",
},
"opencode-go": {
provider: "opencode-go",
profileId: "opencode-go:default",
expectedProviders: ["opencode", "opencode-go"],
envLabel: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
setCredential: setOpencodeGoApiKey,
defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyOpencodeGoConfig,
applyProviderConfig: applyOpencodeGoProviderConfig,
noteDefault: OPENCODE_GO_DEFAULT_MODEL_REF,
noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.",
"Go provides access to Kimi, GLM, and MiniMax models through the Go catalog.",
"Get your API key at: https://opencode.ai/auth",
"Choose the Go catalog when you want the OpenCode-hosted Kimi/GLM/MiniMax lineup.",
].join("\n"),
noteTitle: "OpenCode",
},
"together-api-key": {
provider: "together",
profileId: "together:default",
expectedProviders: ["together"],
envLabel: "TOGETHER_API_KEY",
promptMessage: "Enter Together AI API key",
setCredential: setTogetherApiKey,
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
applyDefaultConfig: applyTogetherConfig,
applyProviderConfig: applyTogetherProviderConfig,
noteDefault: TOGETHER_DEFAULT_MODEL_REF,
noteMessage: [
"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"),
noteTitle: "Together AI",
},
"qianfan-api-key": {
provider: "qianfan",
profileId: "qianfan:default",
expectedProviders: ["qianfan"],
envLabel: "QIANFAN_API_KEY",
promptMessage: "Enter QIANFAN API key",
setCredential: setQianfanApiKey,
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
applyDefaultConfig: applyQianfanConfig,
applyProviderConfig: applyQianfanProviderConfig,
noteDefault: QIANFAN_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://console.bce.baidu.com/qianfan/ais/console/apiKey",
"API key format: bce-v3/ALTAK-...",
].join("\n"),
noteTitle: "QIANFAN",
},
"kilocode-api-key": {
provider: "kilocode",
profileId: "kilocode:default",
expectedProviders: ["kilocode"],
envLabel: "KILOCODE_API_KEY",
promptMessage: "Enter Kilo Gateway API key",
setCredential: setKilocodeApiKey,
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
applyDefaultConfig: applyKilocodeConfig,
applyProviderConfig: applyKilocodeProviderConfig,
noteDefault: KILOCODE_DEFAULT_MODEL_REF,
},
"modelstudio-api-key-cn": {
provider: "modelstudio",
profileId: "modelstudio:default",
expectedProviders: ["modelstudio"],
envLabel: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (China)",
setCredential: setModelStudioApiKey,
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyModelStudioConfigCn,
applyProviderConfig: applyModelStudioProviderConfigCn,
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (China)",
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
"modelstudio-api-key": {
provider: "modelstudio",
profileId: "modelstudio:default",
expectedProviders: ["modelstudio"],
envLabel: "MODELSTUDIO_API_KEY",
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)",
setCredential: setModelStudioApiKey,
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
applyDefaultConfig: applyModelStudioConfig,
applyProviderConfig: applyModelStudioProviderConfig,
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
noteMessage: [
"Get your API key at: https://bailian.console.aliyun.com/",
"Endpoint: coding-intl.dashscope.aliyuncs.com",
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
].join("\n"),
noteTitle: "Alibaba Cloud Model Studio Coding Plan (Global/Intl)",
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
"synthetic-api-key": {
provider: "synthetic",
profileId: "synthetic:default",
expectedProviders: ["synthetic"],
envLabel: "SYNTHETIC_API_KEY",
promptMessage: "Enter Synthetic API key",
setCredential: setSyntheticApiKey,
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
applyDefaultConfig: applySyntheticConfig,
applyProviderConfig: applySyntheticProviderConfig,
normalize: (value) => String(value ?? "").trim(),
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
},
};
export async function applyAuthChoiceApiProviders( export async function applyAuthChoiceApiProviders(
params: ApplyAuthChoiceParams, params: ApplyAuthChoiceParams,
): Promise<ApplyAuthChoiceResult | null> { ): Promise<ApplyAuthChoiceResult | null> {
@@ -404,152 +86,38 @@ export async function applyAuthChoiceApiProviders(
} }
} }
async function applyApiKeyProviderWithDefaultModel({
provider,
profileId,
expectedProviders,
envLabel,
promptMessage,
setCredential,
defaultModel,
applyDefaultConfig,
applyProviderConfig,
noteMessage,
noteTitle,
tokenProvider = normalizedTokenProvider,
normalize = normalizeApiKeyInput,
validate = validateApiKeyInput,
noteDefault = defaultModel,
}: {
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
profileId: string;
expectedProviders: string[];
envLabel: string;
promptMessage: string;
setCredential: (apiKey: SecretInput, mode?: SecretInputMode) => void | Promise<void>;
defaultModel: string;
applyDefaultConfig: (
config: ApplyAuthChoiceParams["config"],
) => ApplyAuthChoiceParams["config"];
applyProviderConfig: (
config: ApplyAuthChoiceParams["config"],
) => ApplyAuthChoiceParams["config"];
noteMessage?: string;
noteTitle?: string;
tokenProvider?: string;
normalize?: (value: string) => string;
validate?: (value: string) => string | undefined;
noteDefault?: string;
}): Promise<ApplyAuthChoiceResult> {
await ensureApiKeyFromOptionEnvOrPrompt({
token: params.opts?.token,
provider,
tokenProvider,
secretInputMode: requestedSecretInputMode,
config: nextConfig,
expectedProviders,
envLabel,
promptMessage,
setCredential: async (apiKey, mode) => {
await setCredential(apiKey, mode);
},
noteMessage,
noteTitle,
normalize,
validate,
prompter: params.prompter,
});
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider,
mode: "api_key",
});
await applyProviderDefaultModel({
defaultModel,
applyDefaultConfig,
applyProviderConfig,
noteDefault,
});
return { config: nextConfig, agentModelOverride };
}
if (authChoice === "openrouter-api-key") { if (authChoice === "openrouter-api-key") {
return applyAuthChoiceOpenRouter(params); return applyAuthChoiceOpenRouter(params);
} }
if (authChoice === "litellm-api-key") { const litellmResult = await applyLiteLlmApiKeyProvider({
const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }); params,
const profileOrder = resolveAuthProfileOrder({ cfg: nextConfig, store, provider: "litellm" }); authChoice,
const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId])); config: nextConfig,
const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined; setConfig: (config) => (nextConfig = config),
let profileId = "litellm:default"; getConfig: () => nextConfig,
let hasCredential = Boolean(existingProfileId && existingCred?.type === "api_key"); normalizedTokenProvider,
if (hasCredential && existingProfileId) { requestedSecretInputMode,
profileId = existingProfileId; applyProviderDefaultModel,
} getAgentModelOverride: () => agentModelOverride,
});
if (!hasCredential) { if (litellmResult) {
await ensureApiKeyFromOptionEnvOrPrompt({ return litellmResult;
token: params.opts?.token,
tokenProvider: normalizedTokenProvider,
secretInputMode: requestedSecretInputMode,
config: nextConfig,
expectedProviders: ["litellm"],
provider: "litellm",
envLabel: "LITELLM_API_KEY",
promptMessage: "Enter LiteLLM API key",
normalize: normalizeApiKeyInput,
validate: validateApiKeyInput,
prompter: params.prompter,
setCredential: async (apiKey, mode) =>
setLitellmApiKey(apiKey, params.agentDir, { secretInputMode: mode }),
noteMessage:
"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",
noteTitle: "LiteLLM",
});
hasCredential = true;
}
if (hasCredential) {
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider: "litellm",
mode: "api_key",
});
}
await applyProviderDefaultModel({
defaultModel: LITELLM_DEFAULT_MODEL_REF,
applyDefaultConfig: applyLitellmConfig,
applyProviderConfig: applyLitellmProviderConfig,
noteDefault: LITELLM_DEFAULT_MODEL_REF,
});
return { config: nextConfig, agentModelOverride };
} }
const simpleApiKeyProviderFlow = SIMPLE_API_KEY_PROVIDER_FLOWS[authChoice]; const simpleProviderResult = await applySimpleAuthChoiceApiProvider({
if (simpleApiKeyProviderFlow) { params,
return await applyApiKeyProviderWithDefaultModel({ authChoice,
provider: simpleApiKeyProviderFlow.provider, config: nextConfig,
profileId: simpleApiKeyProviderFlow.profileId, setConfig: (config) => (nextConfig = config),
expectedProviders: simpleApiKeyProviderFlow.expectedProviders, getConfig: () => nextConfig,
envLabel: simpleApiKeyProviderFlow.envLabel, normalizedTokenProvider,
promptMessage: simpleApiKeyProviderFlow.promptMessage, requestedSecretInputMode,
setCredential: async (apiKey, mode) => applyProviderDefaultModel,
simpleApiKeyProviderFlow.setCredential(apiKey, params.agentDir, { getAgentModelOverride: () => agentModelOverride,
secretInputMode: mode ?? requestedSecretInputMode, });
}), if (simpleProviderResult) {
defaultModel: simpleApiKeyProviderFlow.defaultModel, return simpleProviderResult;
applyDefaultConfig: simpleApiKeyProviderFlow.applyDefaultConfig,
applyProviderConfig: simpleApiKeyProviderFlow.applyProviderConfig,
noteDefault: simpleApiKeyProviderFlow.noteDefault,
noteMessage: simpleApiKeyProviderFlow.noteMessage,
noteTitle: simpleApiKeyProviderFlow.noteTitle,
tokenProvider: simpleApiKeyProviderFlow.tokenProvider,
normalize: simpleApiKeyProviderFlow.normalize,
validate: simpleApiKeyProviderFlow.validate,
});
} }
if (authChoice === "cloudflare-ai-gateway-api-key") { if (authChoice === "cloudflare-ai-gateway-api-key") {