UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

649 lines (648 loc) 29.9 kB
import { D as resolveExpiresAtMsFromDurationMs } from "./number-coercion-CLj0HTDM.js"; import "./src-vebZIeLe.js"; import { t as expectDefined } from "./expect-CyE8FADM.js"; import { l as normalizeOptionalString, p as normalizeStringifiedOptionalString } from "./string-coerce-CIXf7egm.js"; import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { m as resolveAgentWorkspaceDir } from "./agent-scope-config-DcbEhP0R.js"; import { n as resolveDefaultAgentWorkspaceDir } from "./workspace-default-DPT1Dhad.js"; import { t as parseDurationMs } from "./parse-duration-CuuCHKpt.js"; import { c as normalizeProviderId } from "./model-ref-shared-Dz7QU0Lx.js"; import { n as normalizeAgentModelRefForConfig } from "./model-input-BuGMCNOz.js"; import "./agent-scope-DbtJyKUL.js"; import { n as resolveProviderIdForAuth } from "./provider-auth-aliases-DhA9c2am.js"; import { n as normalizeSecretInput } from "./normalize-secret-input-Df_qhWv_.js"; import { i as resolvePluginSetupRegistry, r as resolvePluginSetupProviderCore } from "./setup-registry-DZrwT-ft.js"; import { n as resolvePluginProvidersCore } from "./providers.runtime-DCovueHC.js"; import { f as upsertAuthProfileWithLockOrThrow, o as removeProviderAuthProfilesWithLock, r as promoteAuthProfileInOrder, u as upsertAuthProfileAfterLoginWithLockOrThrow } from "./profiles-DOTqXcYA.js"; import "./workspace-ConDEamr.js"; import "./auth-profiles-BdUEhE7u.js"; import { s as isCliProvider } from "./model-selection-di2kjKCB.js"; import { n as stylePromptMessage } from "./prompt-style-BQVvtDcR.js"; import { n as validateAnthropicSetupToken } from "./provider-auth-token-BocDZcXC.js"; import { t as applyAuthProfileConfig } from "./provider-auth-helpers-CaYTVMoC.js"; import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.js"; import { n as prepareProviderAuthProfilesForPersistence } from "./provider-auth-persistence-BgUvISv3.js"; import { t as isRemoteEnvironment } from "./remote-env-Ci47uLxB.js"; import { a as restorePriorAgentsDefaultsModelUnlessOptIn, i as resolveProviderMatch, n as applyProviderAuthConfigPatch, r as pickAuthMethod, t as applyDefaultModel } from "./provider-auth-choice-helpers-CHV8Vtpf.js"; import { t as createVpsAwareOAuthHandlers } from "./provider-oauth-flow-DL7WaUgl.js"; import { t as styleSelectParams } from "./prompt-select-styled-params-CvMQXWIw.js"; import { t as createClackPrompter } from "./clack-prompter-BSpWH_ak.js"; import { r as logConfigUpdated } from "./logging-Idqd9ylL.js"; import { c as updateConfig, r as loadValidConfigOrThrow, s as resolveModelsTargetAgent } from "./shared-CZFRGTsE.js"; import { a as repairCopilotRuntimePluginInstallForModelSelection, i as repairCodexRuntimePluginInstallForModelSelection } from "./runtime-plugin-install-C2YSxeNS.js"; import "./codex-runtime-plugin-install-B2_P31Ru.js"; import { t as refreshRunningGatewayAuthState } from "./auth-refresh-DkA9S7Se.js"; import "./copilot-runtime-plugin-install-B2_P31Ru.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 durationMs = parseDurationMs(normalizedExpiresIn, { defaultUnit: "d" }); const expires = resolveExpiresAtMsFromDurationMs(durationMs); 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(styleSelectParams(params))); const MODELS_AUTH_STDIN_MAX_BYTES = 1048576; async function readPipedStdin() { return (await readByteStreamWithLimit(process.stdin, { maxBytes: MODELS_AUTH_STDIN_MAX_BYTES, onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`Piped auth input exceeds ${maxBytes} bytes.`) })).toString("utf8"); } async function readPastedSecret(params) { const promptParams = { message: params.message, validate: params.validate }; const input = process.stdin.isTTY ? await (params.masked ? password$1(promptParams) : text$1(promptParams)) : await readPipedStdin(); const normalized = normalizeSecretInput(input); 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); if (normalized === "openai-codex" || normalized === "codex-cli") throw new Error(`"${normalized}" is a legacy provider ID; use --provider openai.`); return normalized === "openai" || normalized === "codex" ? "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 = resolvePluginSetupProviderCore({ 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 = params?.config ?? await loadValidConfigOrThrow(); const { agentId, agentDir } = await resolveModelsAuthAgent(params?.rawAgentId, config); const workspaceDir = resolveAgentWorkspaceDir(config, agentId) ?? resolveDefaultAgentWorkspaceDir(); const requestedProvider = params?.requestedProvider?.trim(); const providerRef = requestedProvider ? normalizeManualAuthProvider(requestedProvider) : void 0; return { config, agentId, agentDir, workspaceDir, providers: preferSetupAuthProviders({ providers: resolvePluginProvidersCore({ config, workspaceDir, mode: "setup", includeUntrustedWorkspacePlugins: false, ...providerRef ? { providerRefs: [providerRef] } : {} }), config, workspaceDir, requestedProvider: providerRef }) }; } async function resolveModelsAuthAgent(rawAgentId, config) { const cfg = config ?? await loadValidConfigOrThrow(); return resolveModelsTargetAgent(cfg, rawAgentId ?? void 0, { kind: "mutation" }); } 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 persistedProfiles = []; const shouldUpdateConfig = Boolean(params.result.configPatch || params.setDefault && defaultModel); for (const candidate of profiles) { const prepared = prepareProviderAuthProfilesForPersistence({ profiles: [candidate], config: params.config, env: params.env }); const profile = expectDefined(prepared.profiles[0], "prepared auth profile"); const configuredSelection = resolveConfiguredAuthSelectionForProvider(params.config, profile.credential.provider); try { await upsertAuthProfileAfterLoginWithLockOrThrow({ profileId: profile.profileId, credential: profile.credential, agentDir: params.agentDir }); } catch (error) { try { prepared.rollback(); } catch (rollbackError) { throw new AggregateError([error, rollbackError], "Provider auth persistence failed and protected-store rollback could not be confirmed.", { cause: rollbackError }); } throw error; } persistedProfiles.push(profile); if (!(await promoteAuthProfileInOrder({ agentDir: params.agentDir, provider: profile.credential.provider, profileId: profile.profileId, createIfMissing: configuredSelection.createIfMissing, ...configuredSelection.order ? { createFromOrder: configuredSelection.order } : {} })).ok) throw new Error("The auth profile was saved, but its order could not be updated because the auth store is busy. Wait a moment, then retry the login."); } 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); } await refreshRunningGatewayAuthState(params.agentId); for (const profile of persistedProfiles) 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} (current default unchanged; run ${formatCliCommand(`openclaw models set ${defaultModel}`)} to apply)`); if (params.result.notes && params.result.notes.length > 0) await params.prompter.note(params.result.notes.join("\n"), "Provider notes"); return persistedProfiles; } 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) { params.signal?.throwIfAborted(); const result = await params.method.run({ config: params.config, env: params.env ?? process.env, agentDir: params.agentDir, workspaceDir: params.workspaceDir, prompter: params.prompter, runtime: params.runtime, allowSecretRefPrompt: false, isRemote: params.isRemote ?? isRemoteEnvironment(), signal: params.signal, openUrl: params.openUrl ?? (async (url) => { const { openUrl } = await import("./onboard-helpers-DC4-A9ie.js"); await openUrl(url); }), oauth: { createVpsAwareHandlers: (runtimeParams) => createVpsAwareOAuthHandlers(runtimeParams) } }); params.signal?.throwIfAborted(); return { result, profiles: await persistProviderAuthResult({ result, profiles: resolveLoginProfiles({ result, requestedProfileId: params.profileId }), config: params.config, agentId: params.agentId, agentDir: params.agentDir, runtime: params.runtime, prompter: params.prompter, setDefault: params.setDefault, env: params.env ?? process.env }) }; } /** 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, agentId, 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, agentId, 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 { agentId, agentDir } = await resolveModelsAuthAgent(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: true, 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" })); await refreshRunningGatewayAuthState(agentId); 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 { agentId, agentDir } = await resolveModelsAuthAgent(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 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); } }); await upsertAuthProfileWithLockOrThrow({ profileId, credential: { type: "api_key", provider, key }, agentDir }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "api_key" })); await refreshRunningGatewayAuthState(agentId); logConfigUpdated(runtime); runtime.log(`Auth profile: ${profileId} (${provider}/api_key)`); } /** Interactive helper for adding token auth profiles, with provider/method prompts. */ async function modelsAuthAddCommand(opts, runtime) { const { config, agentId, 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, agentId, agentDir, workspaceDir, provider: providerPlugin, method, runtime, prompter }); return; } } const profileIdDefault = resolveDefaultTokenProfileId(providerId); await modelsAuthPasteTokenCommand({ provider: providerId, profileId: (await text$1({ message: "Profile id", initialValue: profileIdDefault, 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); } /** 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 [{ ...expectDefined(profile, "auth profile"), profileId: requestedProfileId }]; } function maybeLogOpenAICodexNativeSearchTip(runtime, providerId) { if (providerId !== "openai") return; runtime.log(`Tip: Codex-capable models can use native Codex web search. Configure the \`web_search\` tool with \`${formatCliCommand("openclaw configure --section web")}\`. Docs: https://docs.openclaw.ai/tools/web`); } async function runModelsAuthLoginFlowCore(opts) { const requestedProviderId = opts.provider ? normalizeManualAuthProvider(opts.provider) : void 0; let context = await resolveModelsAuthContext({ requestedProvider: requestedProviderId, rawAgentId: opts.agent, config: opts.config }); const prompter = opts.prompter; let authProviders = listProvidersWithAuthMethods(context.providers); let requestedProvider = requestedProviderId ? resolveProviderMatch(authProviders, requestedProviderId) : null; const useProviderPicker = requestedProviderId !== void 0 && requestedProvider === null && isCliProvider(requestedProviderId, context.config); if (useProviderPicker) { context = await resolveModelsAuthContext({ rawAgentId: opts.agent, config: context.config }); authProviders = listProvidersWithAuthMethods(context.providers); } if (authProviders.length === 0) throw new Error(`No provider plugins found. Install one via \`${formatCliCommand("openclaw plugins install")}\`.`); if (useProviderPicker) await prompter.note(`Provider "${requestedProviderId}" uses its own CLI login. Select a provider with an OpenClaw auth flow.`, "Provider auth"); else if (requestedProviderId && !requestedProvider) requestedProvider = resolveRequestedLoginProviderOrThrow(authProviders, requestedProviderId); await prompter.note([ "Scope: System / agent", `Agent: ${context.agentId}`, "Location: the machine running OpenClaw", `For personal model accounts on a Gateway, run ${formatCliCommand("openclaw models accounts login --help")}.` ].join("\n"), "Provider sign-in"); const selectedProvider = requestedProvider ?? 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: context.agentDir })) throw new Error("auth store is busy; close other OpenClaw commands using this state directory and retry"); opts.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 }); } const { result, profiles } = await runProviderAuthMethod({ config: context.config, agentId: context.agentId, agentDir: context.agentDir, workspaceDir: context.workspaceDir, provider: selectedProvider, method: chosenMethod, runtime: opts.runtime, prompter, profileId: opts.profileId, setDefault: opts.setDefault, env: opts.env, isRemote: opts.isRemote, signal: opts.signal, openUrl: opts.openUrl }); maybeLogOpenAICodexNativeSearchTip(opts.runtime, selectedProvider.id); return { providerId: selectedProvider.id, methodId: chosenMethod.id, ...result.defaultModel ? { defaultModel: result.defaultModel } : {}, profiles: profiles.map((profile) => ({ profileId: profile.profileId, provider: profile.credential.provider, mode: credentialMode(profile.credential) })) }; } 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.`); await runModelsAuthLoginFlowCore({ ...opts, runtime, prompter: createClackPrompter() }); } //#endregion export { modelsAuthAddCommand, modelsAuthLoginCommand, modelsAuthPasteApiKeyCommand, modelsAuthPasteTokenCommand, modelsAuthSetupTokenCommand, resolveLoginProfiles, resolveRequestedLoginProviderOrThrow, runModelsAuthLoginFlowCore };