From fc60f4923afd98bd3fbe3e9c9f25c7662273355e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 11:16:17 +0000 Subject: [PATCH] refactor(auth-choice): unify api-key resolution flows --- src/commands/auth-choice.apply-helpers.ts | 153 +++ .../auth-choice.apply.api-providers.ts | 1066 ++++++----------- src/commands/auth-choice.apply.huggingface.ts | 68 +- src/commands/auth-choice.apply.minimax.ts | 151 ++- 4 files changed, 622 insertions(+), 816 deletions(-) diff --git a/src/commands/auth-choice.apply-helpers.ts b/src/commands/auth-choice.apply-helpers.ts index 8a10d830eec..8e7e0853567 100644 --- a/src/commands/auth-choice.apply-helpers.ts +++ b/src/commands/auth-choice.apply-helpers.ts @@ -1,4 +1,8 @@ +import { resolveEnvApiKey } from "../agents/model-auth.js"; +import type { WizardPrompter } from "../wizard/prompts.js"; +import { formatApiKeyPreview } from "./auth-choice.api-key.js"; import type { ApplyAuthChoiceParams } from "./auth-choice.apply.js"; +import { applyDefaultModelChoice } from "./auth-choice.default-model.js"; export function createAuthChoiceAgentModelNoter( params: ApplyAuthChoiceParams, @@ -13,3 +17,152 @@ export function createAuthChoiceAgentModelNoter( ); }; } + +export interface ApplyAuthChoiceModelState { + config: ApplyAuthChoiceParams["config"]; + agentModelOverride: string | undefined; +} + +export function createAuthChoiceModelStateBridge(bindings: { + getConfig: () => ApplyAuthChoiceParams["config"]; + setConfig: (config: ApplyAuthChoiceParams["config"]) => void; + getAgentModelOverride: () => string | undefined; + setAgentModelOverride: (model: string | undefined) => void; +}): ApplyAuthChoiceModelState { + return { + get config() { + return bindings.getConfig(); + }, + set config(config) { + bindings.setConfig(config); + }, + get agentModelOverride() { + return bindings.getAgentModelOverride(); + }, + set agentModelOverride(model) { + bindings.setAgentModelOverride(model); + }, + }; +} + +export function createAuthChoiceDefaultModelApplier( + params: ApplyAuthChoiceParams, + state: ApplyAuthChoiceModelState, +): ( + options: Omit< + Parameters[0], + "config" | "setDefaultModel" | "noteAgentModel" | "prompter" + >, +) => Promise { + const noteAgentModel = createAuthChoiceAgentModelNoter(params); + + return async (options) => { + const applied = await applyDefaultModelChoice({ + config: state.config, + setDefaultModel: params.setDefaultModel, + noteAgentModel, + prompter: params.prompter, + ...options, + }); + state.config = applied.config; + state.agentModelOverride = applied.agentModelOverride ?? state.agentModelOverride; + }; +} + +export function normalizeTokenProviderInput( + tokenProvider: string | null | undefined, +): string | undefined { + const normalized = String(tokenProvider ?? "") + .trim() + .toLowerCase(); + return normalized || undefined; +} + +export async function maybeApplyApiKeyFromOption(params: { + token: string | undefined; + tokenProvider: string | undefined; + expectedProviders: string[]; + normalize: (value: string) => string; + setCredential: (apiKey: string) => Promise; +}): Promise { + const tokenProvider = normalizeTokenProviderInput(params.tokenProvider); + const expectedProviders = params.expectedProviders + .map((provider) => normalizeTokenProviderInput(provider)) + .filter((provider): provider is string => Boolean(provider)); + if (!params.token || !tokenProvider || !expectedProviders.includes(tokenProvider)) { + return undefined; + } + const apiKey = params.normalize(params.token); + await params.setCredential(apiKey); + return apiKey; +} + +export async function ensureApiKeyFromOptionEnvOrPrompt(params: { + token: string | undefined; + tokenProvider: string | undefined; + expectedProviders: string[]; + provider: string; + envLabel: string; + promptMessage: string; + normalize: (value: string) => string; + validate: (value: string) => string | undefined; + prompter: WizardPrompter; + setCredential: (apiKey: string) => Promise; + noteMessage?: string; + noteTitle?: string; +}): Promise { + const optionApiKey = await maybeApplyApiKeyFromOption({ + token: params.token, + tokenProvider: params.tokenProvider, + expectedProviders: params.expectedProviders, + normalize: params.normalize, + setCredential: params.setCredential, + }); + if (optionApiKey) { + return optionApiKey; + } + + if (params.noteMessage) { + await params.prompter.note(params.noteMessage, params.noteTitle); + } + + return await ensureApiKeyFromEnvOrPrompt({ + provider: params.provider, + envLabel: params.envLabel, + promptMessage: params.promptMessage, + normalize: params.normalize, + validate: params.validate, + prompter: params.prompter, + setCredential: params.setCredential, + }); +} + +export async function ensureApiKeyFromEnvOrPrompt(params: { + provider: string; + envLabel: string; + promptMessage: string; + normalize: (value: string) => string; + validate: (value: string) => string | undefined; + prompter: WizardPrompter; + setCredential: (apiKey: string) => Promise; +}): Promise { + const envKey = resolveEnvApiKey(params.provider); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing ${params.envLabel} (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await params.setCredential(envKey.apiKey); + return envKey.apiKey; + } + } + + const key = await params.prompter.text({ + message: params.promptMessage, + validate: params.validate, + }); + const apiKey = params.normalize(String(key ?? "")); + await params.setCredential(apiKey); + return apiKey; +} diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index dd574b988fd..430e32650a1 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -5,11 +5,16 @@ import { normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js"; -import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js"; +import { + createAuthChoiceAgentModelNoter, + createAuthChoiceDefaultModelApplier, + createAuthChoiceModelStateBridge, + ensureApiKeyFromOptionEnvOrPrompt, + normalizeTokenProviderInput, +} from "./auth-choice.apply-helpers.js"; import { applyAuthChoiceHuggingface } from "./auth-choice.apply.huggingface.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyAuthChoiceOpenRouter } from "./auth-choice.apply.openrouter.js"; -import { applyDefaultModelChoice } from "./auth-choice.default-model.js"; import { applyGoogleGeminiModelDefault, GOOGLE_GEMINI_DEFAULT_MODEL, @@ -67,86 +72,300 @@ import { setZaiApiKey, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js"; +import type { AuthChoice } from "./onboard-types.js"; import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js"; import { detectZaiEndpoint } from "./zai-endpoint-detect.js"; +const API_KEY_TOKEN_PROVIDER_AUTH_CHOICE: Record = { + openrouter: "openrouter-api-key", + litellm: "litellm-api-key", + "vercel-ai-gateway": "ai-gateway-api-key", + "cloudflare-ai-gateway": "cloudflare-ai-gateway-api-key", + moonshot: "moonshot-api-key", + "kimi-code": "kimi-code-api-key", + "kimi-coding": "kimi-code-api-key", + google: "gemini-api-key", + zai: "zai-api-key", + xiaomi: "xiaomi-api-key", + synthetic: "synthetic-api-key", + venice: "venice-api-key", + together: "together-api-key", + huggingface: "huggingface-api-key", + opencode: "opencode-zen", + qianfan: "qianfan-api-key", +}; + +const ZAI_AUTH_CHOICE_ENDPOINT: Partial< + Record +> = { + "zai-coding-global": "coding-global", + "zai-coding-cn": "coding-cn", + "zai-global": "global", + "zai-cn": "cn", +}; + +type ApiKeyProviderConfigApplier = ( + config: ApplyAuthChoiceParams["config"], +) => ApplyAuthChoiceParams["config"]; + +type SimpleApiKeyProviderFlow = { + provider: Parameters[0]["provider"]; + profileId: string; + expectedProviders: string[]; + envLabel: string; + promptMessage: string; + setCredential: (apiKey: string, agentDir?: string) => void | Promise; + 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> = { + "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, + }, + "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"], + envLabel: "OPENCODE_API_KEY", + promptMessage: "Enter OpenCode Zen API key", + setCredential: setOpencodeZenApiKey, + defaultModel: OPENCODE_ZEN_DEFAULT_MODEL, + applyDefaultConfig: applyOpencodeZenConfig, + applyProviderConfig: applyOpencodeZenProviderConfig, + noteDefault: OPENCODE_ZEN_DEFAULT_MODEL, + noteMessage: [ + "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"), + noteTitle: "OpenCode Zen", + }, + "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", + }, + "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( params: ApplyAuthChoiceParams, ): Promise { let nextConfig = params.config; let agentModelOverride: string | undefined; const noteAgentModel = createAuthChoiceAgentModelNoter(params); + const applyProviderDefaultModel = createAuthChoiceDefaultModelApplier( + params, + createAuthChoiceModelStateBridge({ + getConfig: () => nextConfig, + setConfig: (config) => (nextConfig = config), + getAgentModelOverride: () => agentModelOverride, + setAgentModelOverride: (model) => (agentModelOverride = model), + }), + ); 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"; + const normalizedTokenProvider = normalizeTokenProviderInput(params.opts?.tokenProvider); + if (authChoice === "apiKey" && params.opts?.tokenProvider) { + if (normalizedTokenProvider !== "anthropic" && normalizedTokenProvider !== "openai") { + authChoice = API_KEY_TOKEN_PROVIDER_AUTH_CHOICE[normalizedTokenProvider ?? ""] ?? authChoice; } } - async function ensureMoonshotApiKeyCredential(promptMessage: string): Promise { - let hasCredential = false; + async function applyApiKeyProviderWithDefaultModel({ + provider, + profileId, + expectedProviders, + envLabel, + promptMessage, + setCredential, + defaultModel, + applyDefaultConfig, + applyProviderConfig, + noteMessage, + noteTitle, + tokenProvider = normalizedTokenProvider, + normalize = normalizeApiKeyInput, + validate = validateApiKeyInput, + noteDefault = defaultModel, + }: { + provider: Parameters[0]["provider"]; + profileId: string; + expectedProviders: string[]; + envLabel: string; + promptMessage: string; + setCredential: (apiKey: string) => void | Promise; + 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 { + await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + provider, + tokenProvider, + expectedProviders, + envLabel, + promptMessage, + setCredential: async (apiKey) => { + await setCredential(apiKey); + }, + noteMessage, + noteTitle, + normalize, + validate, + prompter: params.prompter, + }); - if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "moonshot") { - await setMoonshotApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); - hasCredential = true; - } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId, + provider, + mode: "api_key", + }); + await applyProviderDefaultModel({ + defaultModel, + applyDefaultConfig, + applyProviderConfig, + noteDefault, + }); - 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); - } + return { config: nextConfig, agentModelOverride }; } if (authChoice === "openrouter-api-key") { @@ -159,41 +378,30 @@ export async function applyAuthChoiceApiProviders( 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") { + let hasCredential = Boolean(existingProfileId && existingCred?.type === "api_key"); + if (hasCredential && existingProfileId) { 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; - } + await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + tokenProvider: normalizedTokenProvider, + expectedProviders: ["litellm"], + provider: "litellm", + envLabel: "LITELLM_API_KEY", + promptMessage: "Enter LiteLLM API key", + normalize: normalizeApiKeyInput, + validate: validateApiKeyInput, + prompter: params.prompter, + setCredential: async (apiKey) => setLitellmApiKey(apiKey, params.agentDir), + 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, @@ -201,75 +409,38 @@ export async function applyAuthChoiceApiProviders( mode: "api_key", }); } - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, + await applyProviderDefaultModel({ 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 simpleApiKeyProviderFlow = SIMPLE_API_KEY_PROVIDER_FLOWS[authChoice]; + if (simpleApiKeyProviderFlow) { + return await applyApiKeyProviderWithDefaultModel({ + provider: simpleApiKeyProviderFlow.provider, + profileId: simpleApiKeyProviderFlow.profileId, + expectedProviders: simpleApiKeyProviderFlow.expectedProviders, + envLabel: simpleApiKeyProviderFlow.envLabel, + promptMessage: simpleApiKeyProviderFlow.promptMessage, + setCredential: async (apiKey) => + simpleApiKeyProviderFlow.setCredential(apiKey, params.agentDir), + 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, }); - { - 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() ?? ""; @@ -291,215 +462,73 @@ export async function applyAuthChoiceApiProviders( }; const optsApiKey = normalizeApiKeyInput(params.opts?.cloudflareAiGatewayApiKey ?? ""); - if (!hasCredential && accountId && gatewayId && optsApiKey) { - await setCloudflareAiGatewayConfig(accountId, gatewayId, optsApiKey, params.agentDir); - hasCredential = true; + let resolvedApiKey = ""; + if (accountId && gatewayId && optsApiKey) { + resolvedApiKey = optsApiKey; } const envKey = resolveEnvApiKey("cloudflare-ai-gateway"); - if (!hasCredential && envKey) { + if (!resolvedApiKey && 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; + resolvedApiKey = normalizeApiKeyInput(envKey.apiKey); } } - if (!hasCredential && optsApiKey) { + if (!resolvedApiKey && optsApiKey) { await ensureAccountGateway(); - await setCloudflareAiGatewayConfig(accountId, gatewayId, optsApiKey, params.agentDir); - hasCredential = true; + resolvedApiKey = optsApiKey; } - if (!hasCredential) { + if (!resolvedApiKey) { 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; + resolvedApiKey = normalizeApiKeyInput(String(key ?? "")); } - 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"); + await setCloudflareAiGatewayConfig(accountId, gatewayId, resolvedApiKey, params.agentDir); nextConfig = applyAuthProfileConfig(nextConfig, { - profileId: "moonshot:default", - provider: "moonshot", + profileId: "cloudflare-ai-gateway:default", + provider: "cloudflare-ai-gateway", 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", + await applyProviderDefaultModel({ + 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, }); - { - 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); - } + await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + provider: "google", + tokenProvider: normalizedTokenProvider, + expectedProviders: ["google"], + envLabel: "GEMINI_API_KEY", + promptMessage: "Enter Gemini API key", + normalize: normalizeApiKeyInput, + validate: validateApiKeyInput, + prompter: params.prompter, + setCredential: async (apiKey) => setGeminiApiKey(apiKey, params.agentDir), + }); nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "google:default", provider: "google", @@ -528,47 +557,20 @@ export async function applyAuthChoiceApiProviders( 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"; - } + let endpoint = ZAI_AUTH_CHOICE_ENDPOINT[authChoice]; - // 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); - } + const apiKey = await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + provider: "zai", + tokenProvider: normalizedTokenProvider, + expectedProviders: ["zai"], + envLabel: "ZAI_API_KEY", + promptMessage: "Enter Z.AI API key", + normalize: normalizeApiKeyInput, + validate: validateApiKeyInput, + prompter: params.prompter, + setCredential: async (apiKey) => setZaiApiKey(apiKey, params.agentDir), + }); // zai-api-key: auto-detect endpoint + choose a working default model. let modelIdOverride: string | undefined; @@ -615,9 +617,7 @@ export async function applyAuthChoiceApiProviders( }); const defaultModel = modelIdOverride ? `zai/${modelIdOverride}` : ZAI_DEFAULT_MODEL_REF; - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, + await applyProviderDefaultModel({ defaultModel, applyDefaultConfig: (config) => applyZaiConfig(config, { @@ -630,328 +630,14 @@ export async function applyAuthChoiceApiProviders( ...(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; } diff --git a/src/commands/auth-choice.apply.huggingface.ts b/src/commands/auth-choice.apply.huggingface.ts index c1210921b7b..3f4c980879f 100644 --- a/src/commands/auth-choice.apply.huggingface.ts +++ b/src/commands/auth-choice.apply.huggingface.ts @@ -2,13 +2,11 @@ import { discoverHuggingfaceModels, isHuggingfacePolicyLocked, } from "../agents/huggingface-models.js"; -import { resolveEnvApiKey } from "../agents/model-auth.js"; +import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js"; import { - formatApiKeyPreview, - normalizeApiKeyInput, - validateApiKeyInput, -} from "./auth-choice.api-key.js"; -import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js"; + createAuthChoiceAgentModelNoter, + ensureApiKeyFromOptionEnvOrPrompt, +} from "./auth-choice.apply-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyDefaultModelChoice } from "./auth-choice.default-model.js"; import { ensureModelAllowlistEntry } from "./model-allowlist.js"; @@ -30,47 +28,23 @@ export async function applyAuthChoiceHuggingface( let agentModelOverride: string | undefined; const noteAgentModel = createAuthChoiceAgentModelNoter(params); - let hasCredential = false; - let hfKey = ""; - - if (!hasCredential && params.opts?.token && params.opts.tokenProvider === "huggingface") { - hfKey = normalizeApiKeyInput(params.opts.token); - await setHuggingfaceApiKey(hfKey, params.agentDir); - hasCredential = true; - } - - if (!hasCredential) { - await params.prompter.note( - [ - "Hugging Face Inference Providers offer OpenAI-compatible chat completions.", - "Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').", - ].join("\n"), - "Hugging Face", - ); - } - - if (!hasCredential) { - const envKey = resolveEnvApiKey("huggingface"); - if (envKey) { - const useExisting = await params.prompter.confirm({ - message: `Use existing Hugging Face token (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, - initialValue: true, - }); - if (useExisting) { - hfKey = envKey.apiKey; - await setHuggingfaceApiKey(hfKey, params.agentDir); - hasCredential = true; - } - } - } - if (!hasCredential) { - const key = await params.prompter.text({ - message: "Enter Hugging Face API key (HF token)", - validate: validateApiKeyInput, - }); - hfKey = normalizeApiKeyInput(String(key ?? "")); - await setHuggingfaceApiKey(hfKey, params.agentDir); - } + const hfKey = await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + tokenProvider: params.opts?.tokenProvider, + expectedProviders: ["huggingface"], + provider: "huggingface", + envLabel: "Hugging Face token", + promptMessage: "Enter Hugging Face API key (HF token)", + normalize: normalizeApiKeyInput, + validate: validateApiKeyInput, + prompter: params.prompter, + setCredential: async (apiKey) => setHuggingfaceApiKey(apiKey, params.agentDir), + noteMessage: [ + "Hugging Face Inference Providers offer OpenAI-compatible chat completions.", + "Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').", + ].join("\n"), + noteTitle: "Hugging Face", + }); nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "huggingface:default", provider: "huggingface", diff --git a/src/commands/auth-choice.apply.minimax.ts b/src/commands/auth-choice.apply.minimax.ts index 5afd52b21c6..d7c99ff8f0d 100644 --- a/src/commands/auth-choice.apply.minimax.ts +++ b/src/commands/auth-choice.apply.minimax.ts @@ -1,13 +1,11 @@ -import { resolveEnvApiKey } from "../agents/model-auth.js"; +import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js"; import { - formatApiKeyPreview, - normalizeApiKeyInput, - validateApiKeyInput, -} from "./auth-choice.api-key.js"; -import { createAuthChoiceAgentModelNoter } from "./auth-choice.apply-helpers.js"; + createAuthChoiceDefaultModelApplier, + createAuthChoiceModelStateBridge, + ensureApiKeyFromOptionEnvOrPrompt, +} from "./auth-choice.apply-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyAuthChoicePluginProvider } from "./auth-choice.apply.plugin-provider.js"; -import { applyDefaultModelChoice } from "./auth-choice.default-model.js"; import { applyAuthProfileConfig, applyMinimaxApiConfig, @@ -24,31 +22,64 @@ export async function applyAuthChoiceMiniMax( ): Promise { let nextConfig = params.config; let agentModelOverride: string | undefined; + const applyProviderDefaultModel = createAuthChoiceDefaultModelApplier( + params, + createAuthChoiceModelStateBridge({ + getConfig: () => nextConfig, + setConfig: (config) => (nextConfig = config), + getAgentModelOverride: () => agentModelOverride, + setAgentModelOverride: (model) => (agentModelOverride = model), + }), + ); const ensureMinimaxApiKey = async (opts: { profileId: string; promptMessage: string; }): Promise => { - let hasCredential = false; - const envKey = resolveEnvApiKey("minimax"); - if (envKey) { - const useExisting = await params.prompter.confirm({ - message: `Use existing MINIMAX_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, - initialValue: true, - }); - if (useExisting) { - await setMinimaxApiKey(envKey.apiKey, params.agentDir, opts.profileId); - hasCredential = true; - } - } - if (!hasCredential) { - const key = await params.prompter.text({ - message: opts.promptMessage, - validate: validateApiKeyInput, - }); - await setMinimaxApiKey(normalizeApiKeyInput(String(key)), params.agentDir, opts.profileId); - } + await ensureApiKeyFromOptionEnvOrPrompt({ + token: params.opts?.token, + tokenProvider: params.opts?.tokenProvider, + expectedProviders: ["minimax", "minimax-cn"], + provider: "minimax", + envLabel: "MINIMAX_API_KEY", + promptMessage: opts.promptMessage, + normalize: normalizeApiKeyInput, + validate: validateApiKeyInput, + prompter: params.prompter, + setCredential: async (apiKey) => setMinimaxApiKey(apiKey, params.agentDir, opts.profileId), + }); + }; + const applyMinimaxApiVariant = async (opts: { + profileId: string; + provider: "minimax" | "minimax-cn"; + promptMessage: string; + modelRefPrefix: "minimax" | "minimax-cn"; + modelId: string; + applyDefaultConfig: ( + config: ApplyAuthChoiceParams["config"], + modelId: string, + ) => ApplyAuthChoiceParams["config"]; + applyProviderConfig: ( + config: ApplyAuthChoiceParams["config"], + modelId: string, + ) => ApplyAuthChoiceParams["config"]; + }): Promise => { + await ensureMinimaxApiKey({ + profileId: opts.profileId, + promptMessage: opts.promptMessage, + }); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: opts.profileId, + provider: opts.provider, + mode: "api_key", + }); + const modelRef = `${opts.modelRefPrefix}/${opts.modelId}`; + await applyProviderDefaultModel({ + defaultModel: modelRef, + applyDefaultConfig: (config) => opts.applyDefaultConfig(config, opts.modelId), + applyProviderConfig: (config) => opts.applyProviderConfig(config, opts.modelId), + }); + return { config: nextConfig, agentModelOverride }; }; - const noteAgentModel = createAuthChoiceAgentModelNoter(params); if (params.authChoice === "minimax-portal") { // Let user choose between Global/CN endpoints const endpoint = await params.prompter.select({ @@ -73,74 +104,36 @@ export async function applyAuthChoiceMiniMax( params.authChoice === "minimax-api" || params.authChoice === "minimax-api-lightning" ) { - const modelId = - params.authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5"; - await ensureMinimaxApiKey({ - profileId: "minimax:default", - promptMessage: "Enter MiniMax API key", - }); - nextConfig = applyAuthProfileConfig(nextConfig, { + return await applyMinimaxApiVariant({ profileId: "minimax:default", provider: "minimax", - mode: "api_key", + promptMessage: "Enter MiniMax API key", + modelRefPrefix: "minimax", + modelId: + params.authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5", + applyDefaultConfig: applyMinimaxApiConfig, + applyProviderConfig: applyMinimaxApiProviderConfig, }); - { - const modelRef = `minimax/${modelId}`; - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, - defaultModel: modelRef, - applyDefaultConfig: (config) => applyMinimaxApiConfig(config, modelId), - applyProviderConfig: (config) => applyMinimaxApiProviderConfig(config, modelId), - noteAgentModel, - prompter: params.prompter, - }); - nextConfig = applied.config; - agentModelOverride = applied.agentModelOverride ?? agentModelOverride; - } - return { config: nextConfig, agentModelOverride }; } if (params.authChoice === "minimax-api-key-cn") { - const modelId = "MiniMax-M2.5"; - await ensureMinimaxApiKey({ - profileId: "minimax-cn:default", - promptMessage: "Enter MiniMax China API key", - }); - nextConfig = applyAuthProfileConfig(nextConfig, { + return await applyMinimaxApiVariant({ profileId: "minimax-cn:default", provider: "minimax-cn", - mode: "api_key", + promptMessage: "Enter MiniMax China API key", + modelRefPrefix: "minimax-cn", + modelId: "MiniMax-M2.5", + applyDefaultConfig: applyMinimaxApiConfigCn, + applyProviderConfig: applyMinimaxApiProviderConfigCn, }); - { - const modelRef = `minimax-cn/${modelId}`; - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, - defaultModel: modelRef, - applyDefaultConfig: (config) => applyMinimaxApiConfigCn(config, modelId), - applyProviderConfig: (config) => applyMinimaxApiProviderConfigCn(config, modelId), - noteAgentModel, - prompter: params.prompter, - }); - nextConfig = applied.config; - agentModelOverride = applied.agentModelOverride ?? agentModelOverride; - } - return { config: nextConfig, agentModelOverride }; } if (params.authChoice === "minimax") { - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, + await applyProviderDefaultModel({ defaultModel: "lmstudio/minimax-m2.1-gs32", applyDefaultConfig: applyMinimaxConfig, applyProviderConfig: applyMinimaxProviderConfig, - noteAgentModel, - prompter: params.prompter, }); - nextConfig = applied.config; - agentModelOverride = applied.agentModelOverride ?? agentModelOverride; return { config: nextConfig, agentModelOverride }; }