openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
649 lines (648 loc) • 29.9 kB
JavaScript
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 };