From 9183081bf1aaed88ffbd9f62b8fbb5ffcae00fa6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 16 Mar 2026 21:19:37 -0700 Subject: [PATCH] refactor: move provider auth helpers into plugin layer --- .../auth-choice.apply.api-key-providers.ts | 10 +- src/commands/auth-choice.apply.oauth.ts | 2 +- .../auth-choice.apply.plugin-provider.test.ts | 2 +- .../auth-choice.apply.plugin-provider.ts | 2 +- src/commands/auth-credentials.ts | 195 +-------- src/commands/auth-profile-config.ts | 75 +--- src/commands/models/auth.ts | 2 +- src/commands/ollama-setup.ts | 2 +- src/commands/onboard-auth.config-core.ts | 2 +- src/commands/onboard-auth.config-litellm.ts | 4 +- src/commands/onboard-auth.config-shared.ts | 228 +---------- src/commands/onboard-auth.credentials.ts | 385 ++---------------- .../local/auth-choice.ts | 8 +- src/commands/self-hosted-provider-setup.ts | 2 +- src/plugin-sdk/provider-auth.ts | 3 +- src/plugin-sdk/provider-onboard.ts | 2 +- src/plugins/provider-api-key-auth.runtime.ts | 3 +- src/providers/github-copilot-auth.ts | 2 +- 18 files changed, 68 insertions(+), 861 deletions(-) diff --git a/src/commands/auth-choice.apply.api-key-providers.ts b/src/commands/auth-choice.apply.api-key-providers.ts index 3ff35a46365..0d508ff687f 100644 --- a/src/commands/auth-choice.apply.api-key-providers.ts +++ b/src/commands/auth-choice.apply.api-key-providers.ts @@ -1,14 +1,10 @@ import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js"; +import { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; +import { LITELLM_DEFAULT_MODEL_REF, setLitellmApiKey } from "../plugins/provider-auth-storage.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 { - applyAuthProfileConfig, - applyLitellmConfig, - applyLitellmProviderConfig, - LITELLM_DEFAULT_MODEL_REF, - setLitellmApiKey, -} from "./onboard-auth.js"; +import { applyLitellmConfig, applyLitellmProviderConfig } from "./onboard-auth.config-litellm.js"; import type { SecretInputMode } from "./onboard-types.js"; type ApiKeyProviderConfigApplier = ( diff --git a/src/commands/auth-choice.apply.oauth.ts b/src/commands/auth-choice.apply.oauth.ts index 0e9a5523ce0..a2a3104e447 100644 --- a/src/commands/auth-choice.apply.oauth.ts +++ b/src/commands/auth-choice.apply.oauth.ts @@ -1,8 +1,8 @@ +import { applyAuthProfileConfig, writeOAuthCredentials } from "../plugins/provider-auth-helpers.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { loginChutes } from "./chutes-oauth.js"; import { isRemoteEnvironment } from "./oauth-env.js"; import { createVpsAwareOAuthHandlers } from "./oauth-flow.js"; -import { applyAuthProfileConfig, writeOAuthCredentials } from "./onboard-auth.js"; import { openUrl } from "./onboard-helpers.js"; export async function applyAuthChoiceOAuth( diff --git a/src/commands/auth-choice.apply.plugin-provider.test.ts b/src/commands/auth-choice.apply.plugin-provider.test.ts index 27615989d1d..1e731fde48f 100644 --- a/src/commands/auth-choice.apply.plugin-provider.test.ts +++ b/src/commands/auth-choice.apply.plugin-provider.test.ts @@ -44,7 +44,7 @@ vi.mock("../agents/agent-paths.js", () => ({ })); const applyAuthProfileConfig = vi.hoisted(() => vi.fn((config) => config)); -vi.mock("./onboard-auth.js", () => ({ +vi.mock("../plugins/provider-auth-helpers.js", () => ({ applyAuthProfileConfig, })); diff --git a/src/commands/auth-choice.apply.plugin-provider.ts b/src/commands/auth-choice.apply.plugin-provider.ts index da125a4065d..afdad97ecec 100644 --- a/src/commands/auth-choice.apply.plugin-provider.ts +++ b/src/commands/auth-choice.apply.plugin-provider.ts @@ -7,11 +7,11 @@ import { import { upsertAuthProfile } from "../agents/auth-profiles.js"; import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js"; import { enablePluginInConfig } from "../plugins/enable.js"; +import { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; import type { ProviderAuthMethod } from "../plugins/types.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { isRemoteEnvironment } from "./oauth-env.js"; import { createVpsAwareOAuthHandlers } from "./oauth-flow.js"; -import { applyAuthProfileConfig } from "./onboard-auth.js"; import { openUrl } from "./onboard-helpers.js"; import type { OnboardOptions } from "./onboard-types.js"; import { diff --git a/src/commands/auth-credentials.ts b/src/commands/auth-credentials.ts index 4ee69149a92..94e320f48db 100644 --- a/src/commands/auth-credentials.ts +++ b/src/commands/auth-credentials.ts @@ -1,189 +1,6 @@ -import fs from "node:fs"; -import path from "node:path"; -import type { OAuthCredentials } from "@mariozechner/pi-ai"; -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; -import { upsertAuthProfile } from "../agents/auth-profiles.js"; -import { resolveStateDir } from "../config/paths.js"; -import { - coerceSecretRef, - DEFAULT_SECRET_PROVIDER_ALIAS, - type SecretInput, - type SecretRef, -} from "../config/types.secrets.js"; -import { PROVIDER_ENV_VARS } from "../secrets/provider-env-vars.js"; -import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; -import type { SecretInputMode } from "./onboard-types.js"; - -const ENV_REF_PATTERN = /^\$\{([A-Z][A-Z0-9_]*)\}$/; - -const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); - -export type ApiKeyStorageOptions = { - secretInputMode?: SecretInputMode; -}; - -export type WriteOAuthCredentialsOptions = { - syncSiblingAgents?: boolean; -}; - -function buildEnvSecretRef(id: string): SecretRef { - return { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id }; -} - -function parseEnvSecretRef(value: string): SecretRef | null { - const match = ENV_REF_PATTERN.exec(value); - if (!match) { - return null; - } - return buildEnvSecretRef(match[1]); -} - -function resolveProviderDefaultEnvSecretRef(provider: string): SecretRef { - const envVars = PROVIDER_ENV_VARS[provider]; - const envVar = envVars?.find((candidate) => candidate.trim().length > 0); - if (!envVar) { - throw new Error( - `Provider "${provider}" does not have a default env var mapping for secret-input-mode=ref.`, - ); - } - return buildEnvSecretRef(envVar); -} - -function resolveApiKeySecretInput( - provider: string, - input: SecretInput, - options?: ApiKeyStorageOptions, -): SecretInput { - const coercedRef = coerceSecretRef(input); - if (coercedRef) { - return coercedRef; - } - const normalized = normalizeSecretInput(input); - const inlineEnvRef = parseEnvSecretRef(normalized); - if (inlineEnvRef) { - return inlineEnvRef; - } - if (options?.secretInputMode === "ref") { - return resolveProviderDefaultEnvSecretRef(provider); - } - return normalized; -} - -export function buildApiKeyCredential( - provider: string, - input: SecretInput, - metadata?: Record, - options?: ApiKeyStorageOptions, -): { - type: "api_key"; - provider: string; - key?: string; - keyRef?: SecretRef; - metadata?: Record; -} { - const secretInput = resolveApiKeySecretInput(provider, input, options); - if (typeof secretInput === "string") { - return { - type: "api_key", - provider, - key: secretInput, - ...(metadata ? { metadata } : {}), - }; - } - return { - type: "api_key", - provider, - keyRef: secretInput, - ...(metadata ? { metadata } : {}), - }; -} - -/** Resolve real path, returning null if the target doesn't exist. */ -function safeRealpathSync(dir: string): string | null { - try { - return fs.realpathSync(path.resolve(dir)); - } catch { - return null; - } -} - -function resolveSiblingAgentDirs(primaryAgentDir: string): string[] { - const normalized = path.resolve(primaryAgentDir); - const parentOfAgent = path.dirname(normalized); - const candidateAgentsRoot = path.dirname(parentOfAgent); - const looksLikeStandardLayout = - path.basename(normalized) === "agent" && path.basename(candidateAgentsRoot) === "agents"; - - const agentsRoot = looksLikeStandardLayout - ? candidateAgentsRoot - : path.join(resolveStateDir(), "agents"); - - const entries = (() => { - try { - return fs.readdirSync(agentsRoot, { withFileTypes: true }); - } catch { - return []; - } - })(); - const discovered = entries - .filter((entry) => entry.isDirectory() || entry.isSymbolicLink()) - .map((entry) => path.join(agentsRoot, entry.name, "agent")); - - const seen = new Set(); - const result: string[] = []; - for (const dir of [normalized, ...discovered]) { - const real = safeRealpathSync(dir); - if (real && !seen.has(real)) { - seen.add(real); - result.push(real); - } - } - return result; -} - -export async function writeOAuthCredentials( - provider: string, - creds: OAuthCredentials, - agentDir?: string, - options?: WriteOAuthCredentialsOptions, -): Promise { - const email = - typeof creds.email === "string" && creds.email.trim() ? creds.email.trim() : "default"; - const profileId = `${provider}:${email}`; - const resolvedAgentDir = path.resolve(resolveAuthAgentDir(agentDir)); - const targetAgentDirs = options?.syncSiblingAgents - ? resolveSiblingAgentDirs(resolvedAgentDir) - : [resolvedAgentDir]; - - const credential = { - type: "oauth" as const, - provider, - ...creds, - }; - - upsertAuthProfile({ - profileId, - credential, - agentDir: resolvedAgentDir, - }); - - if (options?.syncSiblingAgents) { - const primaryReal = safeRealpathSync(resolvedAgentDir); - for (const targetAgentDir of targetAgentDirs) { - const targetReal = safeRealpathSync(targetAgentDir); - if (targetReal && primaryReal && targetReal === primaryReal) { - continue; - } - try { - upsertAuthProfile({ - profileId, - credential, - agentDir: targetAgentDir, - }); - } catch { - // Best-effort: sibling sync failure must not block primary setup. - } - } - } - return profileId; -} +export { + buildApiKeyCredential, + type ApiKeyStorageOptions, + writeOAuthCredentials, + type WriteOAuthCredentialsOptions, +} from "../plugins/provider-auth-helpers.js"; diff --git a/src/commands/auth-profile-config.ts b/src/commands/auth-profile-config.ts index 90be398f5b0..c3879e01846 100644 --- a/src/commands/auth-profile-config.ts +++ b/src/commands/auth-profile-config.ts @@ -1,74 +1 @@ -import { normalizeProviderIdForAuth } from "../agents/provider-id.js"; -import type { OpenClawConfig } from "../config/config.js"; - -export function applyAuthProfileConfig( - cfg: OpenClawConfig, - params: { - profileId: string; - provider: string; - mode: "api_key" | "oauth" | "token"; - email?: string; - preferProfileFirst?: boolean; - }, -): OpenClawConfig { - const normalizedProvider = normalizeProviderIdForAuth(params.provider); - const profiles = { - ...cfg.auth?.profiles, - [params.profileId]: { - provider: params.provider, - mode: params.mode, - ...(params.email ? { email: params.email } : {}), - }, - }; - - const configuredProviderProfiles = Object.entries(cfg.auth?.profiles ?? {}) - .filter(([, profile]) => normalizeProviderIdForAuth(profile.provider) === normalizedProvider) - .map(([profileId, profile]) => ({ profileId, mode: profile.mode })); - - // Maintain `auth.order` when it already exists. Additionally, if we detect - // mixed auth modes for the same provider (e.g. legacy oauth + newly selected - // api_key), create an explicit order to keep the newly selected profile first. - const existingProviderOrder = cfg.auth?.order?.[params.provider]; - const preferProfileFirst = params.preferProfileFirst ?? true; - const reorderedProviderOrder = - existingProviderOrder && preferProfileFirst - ? [ - params.profileId, - ...existingProviderOrder.filter((profileId) => profileId !== params.profileId), - ] - : existingProviderOrder; - const hasMixedConfiguredModes = configuredProviderProfiles.some( - ({ profileId, mode }) => profileId !== params.profileId && mode !== params.mode, - ); - const derivedProviderOrder = - existingProviderOrder === undefined && preferProfileFirst && hasMixedConfiguredModes - ? [ - params.profileId, - ...configuredProviderProfiles - .map(({ profileId }) => profileId) - .filter((profileId) => profileId !== params.profileId), - ] - : undefined; - const order = - existingProviderOrder !== undefined - ? { - ...cfg.auth?.order, - [params.provider]: reorderedProviderOrder?.includes(params.profileId) - ? reorderedProviderOrder - : [...(reorderedProviderOrder ?? []), params.profileId], - } - : derivedProviderOrder - ? { - ...cfg.auth?.order, - [params.provider]: derivedProviderOrder, - } - : cfg.auth?.order; - return { - ...cfg, - auth: { - ...cfg.auth, - profiles, - ...(order ? { order } : {}), - }, - }; -} +export { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index 6001ede2ea4..c3de2dd06dc 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -23,6 +23,7 @@ import { formatCliCommand } from "../../cli/command-format.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; import type { OpenClawConfig } from "../../config/config.js"; import { logConfigUpdated } from "../../config/logging.js"; +import { applyAuthProfileConfig } from "../../plugins/provider-auth-helpers.js"; import { resolvePluginProviders } from "../../plugins/providers.js"; import type { ProviderAuthMethod, @@ -34,7 +35,6 @@ import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style import { createClackPrompter } from "../../wizard/clack-prompter.js"; import { isRemoteEnvironment } from "../oauth-env.js"; import { createVpsAwareOAuthHandlers } from "../oauth-flow.js"; -import { applyAuthProfileConfig } from "../onboard-auth.js"; import { openUrl } from "../onboard-helpers.js"; import { applyDefaultModel, diff --git a/src/commands/ollama-setup.ts b/src/commands/ollama-setup.ts index 4557f606bb6..31499d3f0a6 100644 --- a/src/commands/ollama-setup.ts +++ b/src/commands/ollama-setup.ts @@ -8,10 +8,10 @@ import { type OllamaModelWithContext, } from "../agents/ollama-models.js"; import type { OpenClawConfig } from "../config/config.js"; +import { applyAgentDefaultModelPrimary } from "../plugins/provider-onboarding-config.js"; import type { RuntimeEnv } from "../runtime.js"; import { WizardCancelledError, type WizardPrompter } from "../wizard/prompts.js"; import { isRemoteEnvironment } from "./oauth-env.js"; -import { applyAgentDefaultModelPrimary } from "./onboard-auth.config-shared.js"; import { openUrl } from "./onboard-helpers.js"; import type { OnboardMode, OnboardOptions } from "./onboard-types.js"; diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 7a78df71144..65b4fd40cf0 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -10,7 +10,7 @@ export { LITELLM_BASE_URL, LITELLM_DEFAULT_MODEL_ID, } from "./onboard-auth.config-litellm.js"; -export { applyAuthProfileConfig } from "./auth-profile-config.js"; +export { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; export { applyHuggingfaceConfig, applyHuggingfaceProviderConfig, diff --git a/src/commands/onboard-auth.config-litellm.ts b/src/commands/onboard-auth.config-litellm.ts index ec1ba251056..2dd60bab894 100644 --- a/src/commands/onboard-auth.config-litellm.ts +++ b/src/commands/onboard-auth.config-litellm.ts @@ -1,9 +1,9 @@ import type { OpenClawConfig } from "../config/config.js"; +import { LITELLM_DEFAULT_MODEL_REF } from "../plugins/provider-auth-storage.js"; import { applyAgentDefaultModelPrimary, applyProviderConfigWithDefaultModel, -} from "./onboard-auth.config-shared.js"; -import { LITELLM_DEFAULT_MODEL_REF } from "./onboard-auth.credentials.js"; +} from "../plugins/provider-onboarding-config.js"; export const LITELLM_BASE_URL = "http://localhost:4000"; export const LITELLM_DEFAULT_MODEL_ID = "claude-opus-4-6"; diff --git a/src/commands/onboard-auth.config-shared.ts b/src/commands/onboard-auth.config-shared.ts index 9e70eaac192..7c278eec644 100644 --- a/src/commands/onboard-auth.config-shared.ts +++ b/src/commands/onboard-auth.config-shared.ts @@ -1,221 +1,7 @@ -import { findNormalizedProviderKey } from "../agents/provider-id.js"; -import type { OpenClawConfig } from "../config/config.js"; -import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js"; -import type { - ModelApi, - ModelDefinitionConfig, - ModelProviderConfig, -} from "../config/types.models.js"; - -function extractAgentDefaultModelFallbacks(model: unknown): string[] | undefined { - if (!model || typeof model !== "object") { - return undefined; - } - if (!("fallbacks" in model)) { - return undefined; - } - const fallbacks = (model as { fallbacks?: unknown }).fallbacks; - return Array.isArray(fallbacks) ? fallbacks.map((v) => String(v)) : undefined; -} - -export function applyOnboardAuthAgentModelsAndProviders( - cfg: OpenClawConfig, - params: { - agentModels: Record; - providers: Record; - }, -): OpenClawConfig { - return { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - models: params.agentModels, - }, - }, - models: { - mode: cfg.models?.mode ?? "merge", - providers: params.providers, - }, - }; -} - -export function applyAgentDefaultModelPrimary( - cfg: OpenClawConfig, - primary: string, -): OpenClawConfig { - const existingFallbacks = extractAgentDefaultModelFallbacks(cfg.agents?.defaults?.model); - return { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - model: { - ...(existingFallbacks ? { fallbacks: existingFallbacks } : undefined), - primary, - }, - }, - }, - }; -} - -export function applyProviderConfigWithDefaultModels( - cfg: OpenClawConfig, - params: { - agentModels: Record; - providerId: string; - api: ModelApi; - baseUrl: string; - defaultModels: ModelDefinitionConfig[]; - defaultModelId?: string; - }, -): OpenClawConfig { - const providerState = resolveProviderModelMergeState(cfg, params.providerId); - - const defaultModels = params.defaultModels; - const defaultModelId = params.defaultModelId ?? defaultModels[0]?.id; - const hasDefaultModel = defaultModelId - ? providerState.existingModels.some((model) => model.id === defaultModelId) - : true; - const mergedModels = - providerState.existingModels.length > 0 - ? hasDefaultModel || defaultModels.length === 0 - ? providerState.existingModels - : [...providerState.existingModels, ...defaultModels] - : defaultModels; - return applyProviderConfigWithMergedModels(cfg, { - agentModels: params.agentModels, - providerId: params.providerId, - providerState, - api: params.api, - baseUrl: params.baseUrl, - mergedModels, - fallbackModels: defaultModels, - }); -} - -export function applyProviderConfigWithDefaultModel( - cfg: OpenClawConfig, - params: { - agentModels: Record; - providerId: string; - api: ModelApi; - baseUrl: string; - defaultModel: ModelDefinitionConfig; - defaultModelId?: string; - }, -): OpenClawConfig { - return applyProviderConfigWithDefaultModels(cfg, { - agentModels: params.agentModels, - providerId: params.providerId, - api: params.api, - baseUrl: params.baseUrl, - defaultModels: [params.defaultModel], - defaultModelId: params.defaultModelId ?? params.defaultModel.id, - }); -} - -export function applyProviderConfigWithModelCatalog( - cfg: OpenClawConfig, - params: { - agentModels: Record; - providerId: string; - api: ModelApi; - baseUrl: string; - catalogModels: ModelDefinitionConfig[]; - }, -): OpenClawConfig { - const providerState = resolveProviderModelMergeState(cfg, params.providerId); - const catalogModels = params.catalogModels; - const mergedModels = - providerState.existingModels.length > 0 - ? [ - ...providerState.existingModels, - ...catalogModels.filter( - (model) => !providerState.existingModels.some((existing) => existing.id === model.id), - ), - ] - : catalogModels; - return applyProviderConfigWithMergedModels(cfg, { - agentModels: params.agentModels, - providerId: params.providerId, - providerState, - api: params.api, - baseUrl: params.baseUrl, - mergedModels, - fallbackModels: catalogModels, - }); -} - -type ProviderModelMergeState = { - providers: Record; - existingProvider?: ModelProviderConfig; - existingModels: ModelDefinitionConfig[]; -}; - -function resolveProviderModelMergeState( - cfg: OpenClawConfig, - providerId: string, -): ProviderModelMergeState { - const providers = { ...cfg.models?.providers } as Record; - const existingProviderKey = findNormalizedProviderKey(providers, providerId); - const existingProvider = - existingProviderKey !== undefined - ? (providers[existingProviderKey] as ModelProviderConfig | undefined) - : undefined; - const existingModels: ModelDefinitionConfig[] = Array.isArray(existingProvider?.models) - ? existingProvider.models - : []; - if (existingProviderKey && existingProviderKey !== providerId) { - delete providers[existingProviderKey]; - } - return { providers, existingProvider, existingModels }; -} - -function applyProviderConfigWithMergedModels( - cfg: OpenClawConfig, - params: { - agentModels: Record; - providerId: string; - providerState: ProviderModelMergeState; - api: ModelApi; - baseUrl: string; - mergedModels: ModelDefinitionConfig[]; - fallbackModels: ModelDefinitionConfig[]; - }, -): OpenClawConfig { - params.providerState.providers[params.providerId] = buildProviderConfig({ - existingProvider: params.providerState.existingProvider, - api: params.api, - baseUrl: params.baseUrl, - mergedModels: params.mergedModels, - fallbackModels: params.fallbackModels, - }); - return applyOnboardAuthAgentModelsAndProviders(cfg, { - agentModels: params.agentModels, - providers: params.providerState.providers, - }); -} - -function buildProviderConfig(params: { - existingProvider: ModelProviderConfig | undefined; - api: ModelApi; - baseUrl: string; - mergedModels: ModelDefinitionConfig[]; - fallbackModels: ModelDefinitionConfig[]; -}): ModelProviderConfig { - const { apiKey: existingApiKey, ...existingProviderRest } = (params.existingProvider ?? {}) as { - apiKey?: string; - }; - const normalizedApiKey = typeof existingApiKey === "string" ? existingApiKey.trim() : undefined; - - return { - ...existingProviderRest, - baseUrl: params.baseUrl, - api: params.api, - ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), - models: params.mergedModels.length > 0 ? params.mergedModels : params.fallbackModels, - }; -} +export { + applyAgentDefaultModelPrimary, + applyOnboardAuthAgentModelsAndProviders, + applyProviderConfigWithDefaultModel, + applyProviderConfigWithDefaultModels, + applyProviderConfigWithModelCatalog, +} from "../plugins/provider-onboarding-config.js"; diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 4377a8b4de3..578ad17859d 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -1,358 +1,43 @@ -import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; -import { upsertAuthProfile } from "../agents/auth-profiles.js"; -import type { SecretInput } from "../config/types.secrets.js"; -import { KILOCODE_DEFAULT_MODEL_REF } from "../providers/kilocode-shared.js"; -import { +export { buildApiKeyCredential, type ApiKeyStorageOptions, + HUGGINGFACE_DEFAULT_MODEL_REF, + KILOCODE_DEFAULT_MODEL_REF, + LITELLM_DEFAULT_MODEL_REF, + OPENROUTER_DEFAULT_MODEL_REF, + setAnthropicApiKey, + setByteplusApiKey, + setCloudflareAiGatewayConfig, + setGeminiApiKey, + setHuggingfaceApiKey, + setKilocodeApiKey, + setKimiCodingApiKey, + setLitellmApiKey, + setMinimaxApiKey, + setMistralApiKey, + setModelStudioApiKey, + setMoonshotApiKey, + setOpenaiApiKey, + setOpencodeGoApiKey, + setOpencodeZenApiKey, + setOpenrouterApiKey, + setQianfanApiKey, + setSyntheticApiKey, + setTogetherApiKey, + setVeniceApiKey, + setVercelAiGatewayApiKey, + setVolcengineApiKey, + setXaiApiKey, + setXiaomiApiKey, + setZaiApiKey, + TOGETHER_DEFAULT_MODEL_REF, + VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, writeOAuthCredentials, type WriteOAuthCredentialsOptions, -} from "./auth-credentials.js"; + XIAOMI_DEFAULT_MODEL_REF, + ZAI_DEFAULT_MODEL_REF, +} from "../plugins/provider-auth-storage.js"; export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "../agents/cloudflare-ai-gateway.js"; export { MISTRAL_DEFAULT_MODEL_REF } from "../../extensions/mistral/onboard.js"; export { MODELSTUDIO_DEFAULT_MODEL_REF } from "../../extensions/modelstudio/onboard.js"; export { XAI_DEFAULT_MODEL_REF } from "../../extensions/xai/onboard.js"; -export { KILOCODE_DEFAULT_MODEL_REF }; -export { - buildApiKeyCredential, - type ApiKeyStorageOptions, - writeOAuthCredentials, - type WriteOAuthCredentialsOptions, -}; - -const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); - -export async function setAnthropicApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "anthropic:default", - credential: buildApiKeyCredential("anthropic", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setOpenaiApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "openai:default", - credential: buildApiKeyCredential("openai", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setGeminiApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "google:default", - credential: buildApiKeyCredential("google", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setMinimaxApiKey( - key: SecretInput, - agentDir?: string, - profileId: string = "minimax:default", - options?: ApiKeyStorageOptions, -) { - const provider = profileId.split(":")[0] ?? "minimax"; - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId, - credential: buildApiKeyCredential(provider, key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setMoonshotApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "moonshot:default", - credential: buildApiKeyCredential("moonshot", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setKimiCodingApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "kimi:default", - credential: buildApiKeyCredential("kimi", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setVolcengineApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "volcengine:default", - credential: buildApiKeyCredential("volcengine", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setByteplusApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "byteplus:default", - credential: buildApiKeyCredential("byteplus", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setSyntheticApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "synthetic:default", - credential: buildApiKeyCredential("synthetic", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setVeniceApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "venice:default", - credential: buildApiKeyCredential("venice", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export const ZAI_DEFAULT_MODEL_REF = "zai/glm-5"; -export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash"; -export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; -export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1"; -export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5"; -export const LITELLM_DEFAULT_MODEL_REF = "litellm/claude-opus-4-6"; -export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.6"; - -export async function setZaiApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Write to resolved agent dir so gateway finds credentials on startup. - upsertAuthProfile({ - profileId: "zai:default", - credential: buildApiKeyCredential("zai", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setXiaomiApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "xiaomi:default", - credential: buildApiKeyCredential("xiaomi", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setOpenrouterApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - // Never persist the literal "undefined" (e.g. when prompt returns undefined and caller used String(key)). - const safeKey = typeof key === "string" && key === "undefined" ? "" : key; - upsertAuthProfile({ - profileId: "openrouter:default", - credential: buildApiKeyCredential("openrouter", safeKey, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setCloudflareAiGatewayConfig( - accountId: string, - gatewayId: string, - apiKey: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - const normalizedAccountId = accountId.trim(); - const normalizedGatewayId = gatewayId.trim(); - upsertAuthProfile({ - profileId: "cloudflare-ai-gateway:default", - credential: buildApiKeyCredential( - "cloudflare-ai-gateway", - apiKey, - { - accountId: normalizedAccountId, - gatewayId: normalizedGatewayId, - }, - options, - ), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setLitellmApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "litellm:default", - credential: buildApiKeyCredential("litellm", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setVercelAiGatewayApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "vercel-ai-gateway:default", - credential: buildApiKeyCredential("vercel-ai-gateway", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setOpencodeZenApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - await setSharedOpencodeApiKey(key, agentDir, options); -} - -export async function setOpencodeGoApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - await setSharedOpencodeApiKey(key, agentDir, options); -} - -async function setSharedOpencodeApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - const resolvedAgentDir = resolveAuthAgentDir(agentDir); - for (const provider of ["opencode", "opencode-go"] as const) { - upsertAuthProfile({ - profileId: `${provider}:default`, - credential: buildApiKeyCredential(provider, key, undefined, options), - agentDir: resolvedAgentDir, - }); - } -} - -export async function setTogetherApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "together:default", - credential: buildApiKeyCredential("together", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setHuggingfaceApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "huggingface:default", - credential: buildApiKeyCredential("huggingface", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export function setQianfanApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "qianfan:default", - credential: buildApiKeyCredential("qianfan", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export function setModelStudioApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "modelstudio:default", - credential: buildApiKeyCredential("modelstudio", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export function setXaiApiKey(key: SecretInput, agentDir?: string, options?: ApiKeyStorageOptions) { - upsertAuthProfile({ - profileId: "xai:default", - credential: buildApiKeyCredential("xai", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setMistralApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "mistral:default", - credential: buildApiKeyCredential("mistral", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} - -export async function setKilocodeApiKey( - key: SecretInput, - agentDir?: string, - options?: ApiKeyStorageOptions, -) { - upsertAuthProfile({ - profileId: "kilocode:default", - credential: buildApiKeyCredential("kilocode", key, undefined, options), - agentDir: resolveAuthAgentDir(agentDir), - }); -} diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index c52be44afda..85322122e1f 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -1,15 +1,13 @@ import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js"; import type { OpenClawConfig } from "../../../config/config.js"; import type { SecretInput } from "../../../config/types.secrets.js"; +import { applyAuthProfileConfig } from "../../../plugins/provider-auth-helpers.js"; +import { setCloudflareAiGatewayConfig } from "../../../plugins/provider-auth-storage.js"; import type { RuntimeEnv } from "../../../runtime.js"; import { resolveDefaultSecretProviderAlias } from "../../../secrets/ref-contract.js"; import { normalizeSecretInputModeInput } from "../../auth-choice.apply-helpers.js"; import { normalizeApiKeyTokenProviderAuthChoice } from "../../auth-choice.apply.api-providers.js"; -import { - applyAuthProfileConfig, - applyCloudflareAiGatewayConfig, - setCloudflareAiGatewayConfig, -} from "../../onboard-auth.js"; +import { applyCloudflareAiGatewayConfig } from "../../onboard-auth.config-gateways.js"; import { applyCustomApiConfig, CustomApiError, diff --git a/src/commands/self-hosted-provider-setup.ts b/src/commands/self-hosted-provider-setup.ts index ec2d8c683e3..e7851fdf550 100644 --- a/src/commands/self-hosted-provider-setup.ts +++ b/src/commands/self-hosted-provider-setup.ts @@ -6,6 +6,7 @@ import { SELF_HOSTED_DEFAULT_MAX_TOKENS, } from "../agents/self-hosted-provider-defaults.js"; import type { OpenClawConfig } from "../config/config.js"; +import { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; import type { ProviderDiscoveryContext, ProviderAuthResult, @@ -13,7 +14,6 @@ import type { ProviderNonInteractiveApiKeyResult, } from "../plugins/types.js"; import type { WizardPrompter } from "../wizard/prompts.js"; -import { applyAuthProfileConfig } from "./auth-profile-config.js"; export { SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 40669e51d97..bb0c307c294 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -29,8 +29,7 @@ export { resolveSecretInputModeForEnvSelection, } from "../commands/auth-choice.apply-helpers.js"; export { buildTokenProfileId, validateAnthropicSetupToken } from "../commands/auth-token.js"; -export { buildApiKeyCredential } from "../commands/onboard-auth.credentials.js"; -export { applyAuthProfileConfig } from "../commands/onboard-auth.js"; +export { applyAuthProfileConfig, buildApiKeyCredential } from "../plugins/provider-auth-helpers.js"; export { githubCopilotLoginCommand } from "../providers/github-copilot-auth.js"; export { loginOpenAICodexOAuth } from "../commands/openai-codex-oauth.js"; export { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js"; diff --git a/src/plugin-sdk/provider-onboard.ts b/src/plugin-sdk/provider-onboard.ts index b2175f092fe..89b219bedbc 100644 --- a/src/plugin-sdk/provider-onboard.ts +++ b/src/plugin-sdk/provider-onboard.ts @@ -12,5 +12,5 @@ export { applyProviderConfigWithDefaultModel, applyProviderConfigWithDefaultModels, applyProviderConfigWithModelCatalog, -} from "../commands/onboard-auth.config-shared.js"; +} from "../plugins/provider-onboarding-config.js"; export { ensureModelAllowlistEntry } from "../commands/model-allowlist.js"; diff --git a/src/plugins/provider-api-key-auth.runtime.ts b/src/plugins/provider-api-key-auth.runtime.ts index 010e2b3e16e..dade8720478 100644 --- a/src/plugins/provider-api-key-auth.runtime.ts +++ b/src/plugins/provider-api-key-auth.runtime.ts @@ -1,8 +1,7 @@ import { normalizeApiKeyInput, validateApiKeyInput } from "../commands/auth-choice.api-key.js"; import { ensureApiKeyFromOptionEnvOrPrompt } from "../commands/auth-choice.apply-helpers.js"; -import { buildApiKeyCredential } from "../commands/auth-credentials.js"; import { applyPrimaryModel } from "../commands/model-picker.js"; -import { applyAuthProfileConfig } from "../commands/onboard-auth.js"; +import { applyAuthProfileConfig, buildApiKeyCredential } from "./provider-auth-helpers.js"; export { applyAuthProfileConfig, diff --git a/src/providers/github-copilot-auth.ts b/src/providers/github-copilot-auth.ts index d4ffb926a5f..efc3cb8dbb5 100644 --- a/src/providers/github-copilot-auth.ts +++ b/src/providers/github-copilot-auth.ts @@ -1,8 +1,8 @@ import { intro, note, outro, spinner } from "@clack/prompts"; import { ensureAuthProfileStore, upsertAuthProfile } from "../agents/auth-profiles.js"; import { updateConfig } from "../commands/models/shared.js"; -import { applyAuthProfileConfig } from "../commands/onboard-auth.js"; import { logConfigUpdated } from "../config/logging.js"; +import { applyAuthProfileConfig } from "../plugins/provider-auth-helpers.js"; import type { RuntimeEnv } from "../runtime.js"; import { stylePromptTitle } from "../terminal/prompt-style.js";