UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

609 lines (608 loc) 27.8 kB
import { c as normalizeOptionalString, f as normalizeStringifiedOptionalString } from "./string-coerce-mnp54Vah.js"; import { C as resolveExpiresAtMsFromDurationMs } from "./number-coercion-CJQ8TR--.js"; import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js"; import { n as normalizeAgentModelRefForConfig } from "./model-input-B2zxl6MM.js"; import "./agent-scope-MrLta7Pq.js"; import { a as resolveAgentDir, c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js"; import { n as resolveDefaultAgentWorkspaceDir } from "./workspace-default-B3AjhIK3.js"; import { r as resolveProviderIdForAuth } from "./provider-auth-aliases-BNZrcvHv.js"; import { t as parseDurationMs } from "./parse-duration-oxu_S07c.js"; import { c as loadAuthProfileStoreForRuntime } from "./store-C8spD0DG.js"; import { a as resolvePluginSetupRegistry, i as resolvePluginSetupProvider } from "./setup-registry-HspW94zx.js"; import { n as resolvePluginProviders } from "./providers.runtime-DOSqHxyZ.js"; import "./auth-profiles-84rzaGag.js"; import { r as externalCliDiscoveryForProviderAuth } from "./external-cli-discovery-Cr-vJMRB.js"; import { n as normalizeSecretInput } from "./normalize-secret-input-OwRUfByL.js"; import { n as listProfilesForProvider } from "./profile-list-DI8_o0Rw.js"; import { i as removeProviderAuthProfilesWithLock, r as promoteAuthProfileInOrder, s as upsertAuthProfileWithLock } from "./profiles-BMWtmBgj.js"; import { n as clearAuthProfileCooldown } from "./usage-K7-k2Oys.js"; import { o as normalizeProviderId } from "./model-selection-normalize-roKDxQ9_.js"; import "./workspace-B0bmoo98.js"; import { n as validateAnthropicSetupToken } from "./provider-auth-token-BA6tpOZK.js"; import { t as applyAuthProfileConfig } from "./provider-auth-helpers-BgLOn-Ws.js"; import { n as stylePromptMessage, t as stylePromptHint } from "./prompt-style-BQVvtDcR.js"; import { t as createClackPrompter } from "./clack-prompter-fc9v8HJv.js"; import { r as logConfigUpdated } from "./logging-Cr7I5jF_.js"; import { c as resolveKnownAgentId, d as updateConfig, o as loadValidConfigOrThrow } from "./shared-B3B7qn2l.js"; import { a as restorePriorAgentsDefaultsModelUnlessOptIn, i as resolveProviderMatch, n as applyProviderAuthConfigPatch, r as pickAuthMethod, t as applyDefaultModel } from "./provider-auth-choice-helpers-CewNDs91.js"; import { t as createVpsAwareOAuthHandlers } from "./provider-oauth-flow-DZMQU2CA.js"; import { t as isRemoteEnvironment } from "./remote-env-DH8t4vX-.js"; import { r as repairCodexRuntimePluginInstallForModelSelection } from "./codex-runtime-plugin-install-BG-9pbTp.js"; import { r as repairCopilotRuntimePluginInstallForModelSelection } from "./copilot-runtime-plugin-install-DCkDR2Sd.js"; import { cancel, confirm, isCancel, password, select, text } from "@clack/prompts"; //#region src/commands/models/auth.ts /** Commands for adding, pasting, and logging into provider model auth profiles. */ function resolveManualTokenExpiryMs(expiresIn) { const normalizedExpiresIn = normalizeStringifiedOptionalString(expiresIn); if (!normalizedExpiresIn) return; const expires = resolveExpiresAtMsFromDurationMs(parseDurationMs(normalizedExpiresIn, { defaultUnit: "d" })); if (expires === void 0) throw new Error("Invalid expiry duration: resulting token expiry is outside Date range."); return expires; } function guardCancel(value) { if (typeof value === "symbol" || isCancel(value)) { cancel("Cancelled."); process.exit(0); } return value; } const confirm$1 = async (params) => guardCancel(await confirm({ ...params, message: stylePromptMessage(params.message) })); const text$1 = async (params) => guardCancel(await text({ ...params, message: stylePromptMessage(params.message) })); const password$1 = async (params) => guardCancel(await password({ ...params, message: stylePromptMessage(params.message) })); const select$1 = async (params) => guardCancel(await select({ ...params, message: stylePromptMessage(params.message), options: params.options.map((opt) => opt.hint === void 0 ? opt : { ...opt, hint: stylePromptHint(opt.hint) }) })); async function readPipedStdin() { process.stdin.setEncoding("utf8"); let input = ""; for await (const chunk of process.stdin) input += String(chunk); return input; } async function readPastedSecret(params) { const promptParams = { message: params.message, validate: params.validate }; const normalized = normalizeSecretInput(process.stdin.isTTY ? await (params.masked ? password$1(promptParams) : text$1(promptParams)) : await readPipedStdin()); const validationMessage = params.validate?.(normalized); if (validationMessage) throw new Error(validationMessage); return normalized; } function resolveDefaultTokenProfileId(provider) { return `${normalizeProviderId(provider)}:manual`; } function normalizeManualAuthProvider(provider) { const normalized = normalizeProviderId(provider); return normalized === "openai" ? "openai" : normalized; } function isOpenAIProvider(provider) { return normalizeManualAuthProvider(provider) === "openai"; } function stripBearerPrefix(value) { return value.trim().replace(/^Bearer\s+/i, "").trim(); } function looksLikeOpenAIApiKey(value) { return /^sk-[A-Za-z0-9_-]{8,}$/.test(value.trim()); } function looksLikeJwtToken(value) { const parts = stripBearerPrefix(value).split("."); return parts.length === 3 && parts.every((part) => /^[A-Za-z0-9_-]{8,}$/.test(part)); } function looksLikeStructuredCredential(value) { const trimmed = value.trim(); return trimmed.startsWith("{") || trimmed.startsWith("["); } function validateOpenAICodexApiKeyInput(value) { const trimmed = value.trim(); if (!trimmed) return "Required"; if (looksLikeOpenAIApiKey(trimmed)) return; if (looksLikeJwtToken(trimmed) || looksLikeStructuredCredential(trimmed)) return `That looks like token or OAuth material, not an OpenAI API key. Use ${formatCliCommand("openclaw models auth paste-token --provider openai")} for token auth material.`; return "That does not look like an OpenAI API key."; } function listProvidersWithAuthMethods(providers) { return providers.filter((provider) => provider.auth.length > 0); } function listTokenAuthMethods(provider) { return provider.auth.filter((method) => method.kind === "token"); } function listProvidersWithTokenMethods(providers) { return providers.filter((provider) => listTokenAuthMethods(provider).length > 0); } function mergeSetupProviders(providers, setupProviders) { if (setupProviders.length === 0) return [...providers]; const setupById = new Map(setupProviders.map((provider) => [normalizeProviderId(provider.id), provider])); const merged = providers.map((provider) => setupById.get(normalizeProviderId(provider.id)) ?? provider); const existing = new Set(merged.map((provider) => normalizeProviderId(provider.id))); for (const provider of setupProviders) if (!existing.has(normalizeProviderId(provider.id))) merged.push(provider); return merged; } function preferSetupAuthProviders(params) { const requestedProvider = params.requestedProvider ? normalizeManualAuthProvider(params.requestedProvider) : void 0; if (requestedProvider) { const setupProvider = resolvePluginSetupProvider({ provider: requestedProvider, config: params.config, workspaceDir: params.workspaceDir }); return setupProvider ? [setupProvider] : [...params.providers]; } const setupProviders = resolvePluginSetupRegistry({ config: params.config, workspaceDir: params.workspaceDir }).providers.map((entry) => entry.provider); return mergeSetupProviders(params.providers, setupProviders); } async function resolveModelsAuthContext(params) { const config = await loadValidConfigOrThrow(); const agentId = resolveKnownAgentId({ cfg: config, rawAgentId: params?.rawAgentId }) ?? resolveDefaultAgentId(config); const agentDir = resolveAgentDir(config, agentId); const workspaceDir = resolveAgentWorkspaceDir(config, agentId) ?? resolveDefaultAgentWorkspaceDir(); const requestedProvider = params?.requestedProvider?.trim(); const providerRef = requestedProvider ? normalizeManualAuthProvider(requestedProvider) : void 0; return { config, agentDir, workspaceDir, providers: preferSetupAuthProviders({ providers: resolvePluginProviders({ config, workspaceDir, mode: "setup", includeUntrustedWorkspacePlugins: false, bundledProviderVitestCompat: true, ...providerRef ? { providerRefs: [providerRef], activate: true } : {} }), config, workspaceDir, requestedProvider: providerRef }) }; } async function resolveModelsAuthAgentDir(rawAgentId) { const config = await loadValidConfigOrThrow(); return resolveAgentDir(config, resolveKnownAgentId({ cfg: config, rawAgentId }) ?? resolveDefaultAgentId(config)); } function resolveRequestedProviderOrThrow(providers, rawProvider) { const requested = rawProvider?.trim(); if (!requested) return null; const matched = resolveProviderMatch(providers, requested); if (matched) return matched; const available = providers.map((provider) => provider.id).filter(Boolean).toSorted((a, b) => a.localeCompare(b)); const availableText = available.length > 0 ? available.join(", ") : "(none)"; throw new Error(`Unknown provider "${requested}". Loaded providers: ${availableText}. Verify plugins via \`${formatCliCommand("openclaw plugins list --json")}\`.`); } function resolveTokenMethodOrThrow(provider, rawMethod) { const tokenMethods = listTokenAuthMethods(provider); if (rawMethod?.trim()) { const matched = pickAuthMethod(provider, rawMethod); if (matched && matched.kind === "token") return matched; const available = tokenMethods.map((method) => method.id).join(", ") || "(none)"; throw new Error(`Unknown token auth method "${rawMethod}" for provider "${provider.id}". Available token methods: ${available}.`); } return null; } async function pickProviderAuthMethod(params) { const rawRequestedMethod = params.requestedMethod?.trim(); if (rawRequestedMethod) return pickAuthMethod(params.provider, rawRequestedMethod); const oauthMethod = params.provider.auth.find((method) => method.kind === "oauth"); if (oauthMethod) return oauthMethod; if (params.provider.auth.length === 1) return params.provider.auth[0] ?? null; return await params.prompter.select({ message: `Auth method for ${params.provider.label}`, options: params.provider.auth.map((method) => ({ value: method.id, label: method.label, hint: method.hint })) }).then((id) => params.provider.auth.find((method) => method.id === id) ?? null); } async function pickProviderTokenMethod(params) { const explicitTokenMethod = resolveTokenMethodOrThrow(params.provider, params.requestedMethod); if (explicitTokenMethod) return explicitTokenMethod; const tokenMethods = listTokenAuthMethods(params.provider); if (tokenMethods.length === 0) return null; const setupTokenMethod = tokenMethods.find((method) => method.id === "setup-token"); if (setupTokenMethod) return setupTokenMethod; if (tokenMethods.length === 1) return tokenMethods[0] ?? null; return await params.prompter.select({ message: `Token method for ${params.provider.label}`, options: tokenMethods.map((method) => ({ value: method.id, label: method.label, hint: method.hint })) }).then((id) => tokenMethods.find((method) => method.id === id) ?? null); } async function persistProviderAuthResult(params) { const defaultModel = params.result.defaultModel ? normalizeAgentModelRefForConfig(params.result.defaultModel) : void 0; const profiles = params.profiles ?? params.result.profiles; const shouldUpdateConfig = Boolean(params.result.configPatch || params.setDefault && defaultModel); for (const profile of profiles) { const configuredSelection = resolveConfiguredAuthSelectionForProvider(params.config, profile.credential.provider); await upsertAuthProfileWithLockOrThrow({ profileId: profile.profileId, credential: profile.credential, agentDir: params.agentDir }); await promoteAuthProfileInOrder({ agentDir: params.agentDir, provider: profile.credential.provider, profileId: profile.profileId, createIfMissing: configuredSelection.createIfMissing, ...configuredSelection.order ? { createFromOrder: configuredSelection.order } : {} }); } if (shouldUpdateConfig) { const updated = await updateConfig((cfg) => { const priorAgentsDefaultsModel = cfg.agents?.defaults?.model; let next = cfg; if (params.result.configPatch) next = applyProviderAuthConfigPatch(next, params.result.configPatch, { replaceDefaultModels: params.result.replaceDefaultModels }); next = restorePriorAgentsDefaultsModelUnlessOptIn({ cfg: next, priorAgentsDefaultsModel, setDefault: params.setDefault }); if (params.setDefault && defaultModel) next = applyDefaultModel(next, defaultModel); return next; }); if (defaultModel) { const repaired = await repairCodexRuntimePluginInstallForModelSelection({ cfg: updated, model: defaultModel }); const copilotRepaired = await repairCopilotRuntimePluginInstallForModelSelection({ cfg: updated, model: defaultModel }); for (const warning of [...repaired.warnings, ...copilotRepaired.warnings]) params.runtime.error?.(warning); } logConfigUpdated(params.runtime); } for (const profile of profiles) params.runtime.log(`Auth profile: ${profile.profileId} (${profile.credential.provider}/${credentialMode(profile.credential)})`); if (defaultModel) params.runtime.log(params.setDefault ? `Default model set to ${defaultModel}` : `Default model available: ${defaultModel} (use --set-default to apply)`); if (params.result.notes && params.result.notes.length > 0) await params.prompter.note(params.result.notes.join("\n"), "Provider notes"); } function resolveConfiguredAuthSelectionForProvider(cfg, provider) { const providerAuthKey = resolveProviderIdForAuth(provider, { config: cfg }); for (const [orderProvider, profileIds] of Object.entries(cfg.auth?.order ?? {})) if (profileIds.length > 0 && resolveProviderIdForAuth(orderProvider, { config: cfg }) === providerAuthKey) return { createIfMissing: true, order: profileIds }; const profileIds = Object.entries(cfg.auth?.profiles ?? {}).filter(([, profile]) => resolveProviderIdForAuth(profile.provider, { config: cfg }) === providerAuthKey).map(([profileId]) => profileId); return profileIds.length > 0 ? { createIfMissing: true, order: profileIds } : { createIfMissing: false }; } async function runProviderAuthMethod(params) { const selectedProviderId = normalizeProviderId(params.provider.id); await clearStaleProfileLockouts(selectedProviderId, params.agentDir); const result = await params.method.run({ config: params.config, env: process.env, agentDir: params.agentDir, workspaceDir: params.workspaceDir, prompter: params.prompter, runtime: params.runtime, allowSecretRefPrompt: false, isRemote: isRemoteEnvironment(), openUrl: async (url) => { const { openUrl } = await import("./onboard-helpers-CfAsmV3U.js"); await openUrl(url); }, oauth: { createVpsAwareHandlers: (runtimeParams) => createVpsAwareOAuthHandlers(runtimeParams) } }); const resultProviderIds = new Set(result.profiles.map((profile) => normalizeProviderId(profile.credential.provider))); for (const providerId of resultProviderIds) if (providerId && providerId !== selectedProviderId) await clearStaleProfileLockouts(providerId, params.agentDir); await persistProviderAuthResult({ result, profiles: resolveLoginProfiles({ result, requestedProfileId: params.profileId }), config: params.config, agentDir: params.agentDir, runtime: params.runtime, prompter: params.prompter, setDefault: params.setDefault }); } /** Runs an interactive provider setup-token auth flow. */ async function modelsAuthSetupTokenCommand(opts, runtime) { if (!process.stdin.isTTY) throw new Error(`setup-token requires an interactive TTY. In automation, use ${formatCliCommand("openclaw models auth paste-token --provider <provider>")} instead.`); const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({ requestedProvider: opts.provider, rawAgentId: opts.agent }); const tokenProviders = listProvidersWithTokenMethods(providers); if (tokenProviders.length === 0) throw new Error(`No provider token-auth plugins found. Install one via \`${formatCliCommand("openclaw plugins install")}\`.`); const provider = resolveRequestedProviderOrThrow(tokenProviders, opts.provider) ?? tokenProviders[0] ?? null; if (!provider) throw new Error(`No token-capable provider is available. Run ${formatCliCommand("openclaw plugins list")} to verify provider plugins are installed.`); if (!opts.yes) { if (!await confirm$1({ message: `Continue with ${provider.label} token auth?`, initialValue: true })) return; } const prompter = createClackPrompter(); const method = await pickProviderTokenMethod({ provider, prompter }); if (!method) throw new Error(`Provider "${provider.id}" does not expose a token auth method.`); await runProviderAuthMethod({ config, agentDir, workspaceDir, provider, method, runtime, prompter }); } /** Reads a pasted bearer/setup token and stores it as an auth profile. */ async function modelsAuthPasteTokenCommand(opts, runtime) { const agentDir = await resolveModelsAuthAgentDir(opts.agent); const rawProvider = normalizeOptionalString(opts.provider); if (!rawProvider) throw new Error(`Missing --provider. Run ${formatCliCommand("openclaw models status")} or ${formatCliCommand("openclaw plugins list")} to choose a provider.`); const provider = normalizeManualAuthProvider(rawProvider); const profileId = normalizeOptionalString(opts.profileId) || resolveDefaultTokenProfileId(provider); const validateTokenInput = (value) => { const trimmed = value?.trim(); if (!trimmed) return "Required"; if (provider === "anthropic") return validateAnthropicSetupToken(trimmed.replaceAll(/\s+/g, "")); if (isOpenAIProvider(provider) && looksLikeOpenAIApiKey(trimmed)) return `That looks like an OpenAI API key. Use ${formatCliCommand("openclaw models auth paste-api-key --provider openai")} for API-key auth.`; }; const tokenInput = await readPastedSecret({ message: `Paste token for ${provider}`, masked: false, validate: validateTokenInput }); const token = provider === "anthropic" ? tokenInput.replaceAll(/\s+/g, "").trim() : normalizeOptionalString(tokenInput) ?? ""; const expires = resolveManualTokenExpiryMs(opts.expiresIn); await upsertAuthProfileWithLockOrThrow({ profileId, credential: { type: "token", provider, token, ...expires ? { expires } : {} }, agentDir }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "token" })); logConfigUpdated(runtime); runtime.log(`Auth profile: ${profileId} (${provider}/token)`); if (provider === "anthropic") { runtime.log("Anthropic setup-token auth is supported in OpenClaw."); runtime.log("OpenClaw prefers Claude CLI reuse when it is available on the host."); runtime.log("Anthropic staff told us this OpenClaw path is allowed again."); } } /** Reads a pasted API key and stores it as an auth profile. */ async function modelsAuthPasteApiKeyCommand(opts, runtime) { const agentDir = await resolveModelsAuthAgentDir(opts.agent); const rawProvider = normalizeOptionalString(opts.provider); if (!rawProvider) throw new Error(`Missing --provider. Run ${formatCliCommand("openclaw models status")} or ${formatCliCommand("openclaw plugins list")} to choose a provider.`); const provider = normalizeManualAuthProvider(rawProvider); const profileId = normalizeOptionalString(opts.profileId) || resolveDefaultTokenProfileId(provider); await upsertAuthProfileWithLockOrThrow({ profileId, credential: { type: "api_key", provider, key: await readPastedSecret({ message: `Paste API key for ${provider}`, masked: true, validate: (value) => { const trimmed = value?.trim(); if (!trimmed) return "Required"; if (isOpenAIProvider(provider)) return validateOpenAICodexApiKeyInput(trimmed); } }) }, agentDir }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "api_key" })); logConfigUpdated(runtime); runtime.log(`Auth profile: ${profileId} (${provider}/api_key)`); } async function upsertAuthProfileWithLockOrThrow(params) { if (!await upsertAuthProfileWithLock(params)) throw new Error("Failed to update auth profile store; the auth store lock may be busy. Wait a moment and retry."); } /** Interactive helper for adding token auth profiles, with provider/method prompts. */ async function modelsAuthAddCommand(opts, runtime) { const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({ rawAgentId: opts.agent }); const tokenProviders = listProvidersWithTokenMethods(providers); const provider = await select$1({ message: "Token provider", options: [...tokenProviders.map((providerPlugin) => ({ value: providerPlugin.id, label: providerPlugin.id, hint: providerPlugin.docsPath ? `Docs: ${providerPlugin.docsPath}` : void 0 })), { value: "custom", label: "custom (type provider id)" }] }); const providerId = provider === "custom" ? normalizeProviderId(await text$1({ message: "Provider id", validate: (value) => value?.trim() ? void 0 : "Required" })) : provider; const providerPlugin = provider === "custom" ? null : resolveRequestedProviderOrThrow(tokenProviders, providerId); if (providerPlugin) { const tokenMethods = listTokenAuthMethods(providerPlugin); const methodId = tokenMethods.length > 0 ? await select$1({ message: "Token method", options: [...tokenMethods.map((method) => ({ value: method.id, label: method.label, hint: method.hint })), { value: "paste", label: "paste token" }] }) : "paste"; if (methodId !== "paste") { const prompter = createClackPrompter(); const method = tokenMethods.find((candidate) => candidate.id === methodId); if (!method) throw new Error(`Unknown token auth method "${methodId}". Run ${formatCliCommand("openclaw models auth login --provider " + providerPlugin.id)} to choose interactively.`); await runProviderAuthMethod({ config, agentDir, workspaceDir, provider: providerPlugin, method, runtime, prompter }); return; } } await modelsAuthPasteTokenCommand({ provider: providerId, profileId: (await text$1({ message: "Profile id", initialValue: resolveDefaultTokenProfileId(providerId), validate: (value) => value?.trim() ? void 0 : "Required" })).trim(), expiresIn: await confirm$1({ message: "Does this token expire?", initialValue: false }) ? (await text$1({ message: "Expires in (duration)", initialValue: "365d", validate: (value) => { try { parseDurationMs(value ?? "", { defaultUnit: "d" }); return; } catch { return "Invalid duration (e.g. 365d, 12h, 30m)"; } } })).trim() : void 0, agent: opts.agent }, runtime); } /** * Clear stale cooldown/disabled state for all profiles matching a provider. * When a user explicitly runs `models auth login`, they intend to fix auth — * stale `auth_permanent` / `billing` lockouts should not persist across * a deliberate re-authentication attempt. */ async function clearStaleProfileLockouts(provider, agentDir) { try { const store = loadAuthProfileStoreForRuntime(agentDir, { externalCli: externalCliDiscoveryForProviderAuth({ provider }) }); const profileIds = listProfilesForProvider(store, provider); for (const profileId of profileIds) await clearAuthProfileCooldown({ store, profileId, agentDir }); } catch {} } /** Resolves a requested login provider or throws with available provider details. */ function resolveRequestedLoginProviderOrThrow(providers, rawProvider) { return resolveRequestedProviderOrThrow(providers, rawProvider); } function credentialMode(credential) { if (credential.type === "api_key") return "api_key"; if (credential.type === "token") return "token"; return "oauth"; } /** Applies an optional profile-id override to a single returned login profile. */ function resolveLoginProfiles(params) { const requestedProfileId = params.requestedProfileId?.trim(); if (!requestedProfileId) return params.result.profiles; if (params.result.profiles.length !== 1) throw new Error("--profile-id requires exactly one returned auth profile from the selected auth method."); const [profile] = params.result.profiles; return [{ ...profile, profileId: requestedProfileId }]; } function maybeLogOpenAICodexNativeSearchTip(runtime, providerId) { if (providerId !== "openai") return; runtime.log("Tip: Codex-capable models can use native Codex web search. Enable it with openclaw configure --section web (recommended mode: cached). Docs: https://docs.openclaw.ai/tools/web"); } /** Runs interactive provider auth login and persists returned profiles. */ async function modelsAuthLoginCommand(opts, runtime) { if (!process.stdin.isTTY) throw new Error(`models auth login requires an interactive TTY. In automation, use ${formatCliCommand("openclaw models auth paste-token --provider <provider>")} when token auth is available.`); const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({ requestedProvider: opts.provider, rawAgentId: opts.agent }); const prompter = createClackPrompter(); const authProviders = listProvidersWithAuthMethods(providers); if (authProviders.length === 0) throw new Error(`No provider plugins found. Install one via \`${formatCliCommand("openclaw plugins install")}\`.`); const selectedProvider = resolveRequestedLoginProviderOrThrow(authProviders, opts.provider ? normalizeManualAuthProvider(opts.provider) : void 0) ?? await prompter.select({ message: "Select a provider", options: authProviders.map((provider) => ({ value: provider.id, label: provider.label, hint: provider.docsPath ? `Docs: ${provider.docsPath}` : void 0 })) }).then((id) => resolveProviderMatch(authProviders, id)); if (!selectedProvider) throw new Error(`Unknown provider. Run ${formatCliCommand("openclaw models status")} or ${formatCliCommand("openclaw plugins list")} to see available provider plugins.`); const chosenMethod = await pickProviderAuthMethod({ provider: selectedProvider, requestedMethod: opts.method, prompter }); if (!chosenMethod) throw new Error(`Unknown auth method. Run ${formatCliCommand("openclaw models auth login --provider " + selectedProvider.id)} without --method to choose interactively.`); if (opts.force) try { if (!await removeProviderAuthProfilesWithLock({ provider: selectedProvider.id, agentDir })) throw new Error("profile store update failed"); runtime.log(`Removed cached auth profiles for provider "${selectedProvider.id}" (--force). Running fresh auth flow.`); } catch (err) { const message = err instanceof Error ? err.message : String(err); throw new Error(`Could not clear cached profiles for "${selectedProvider.id}" before re-login: ${message}. Re-login was not started because --force must remove cached profiles first.`, { cause: err }); } await runProviderAuthMethod({ config, agentDir, workspaceDir, provider: selectedProvider, method: chosenMethod, runtime, prompter, profileId: opts.profileId, setDefault: opts.setDefault }); maybeLogOpenAICodexNativeSearchTip(runtime, selectedProvider.id); } //#endregion export { modelsAuthAddCommand, modelsAuthLoginCommand, modelsAuthPasteApiKeyCommand, modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand, resolveLoginProfiles, resolveRequestedLoginProviderOrThrow };