openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
801 lines (800 loc) • 37.8 kB
JavaScript
import { l as normalizeOptionalString, p as normalizeStringifiedOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { D as resolveExpiresAtMsFromDurationMs } from "./number-coercion-CuVhdCBs.mjs";
import "./src-CdRlGRnW.mjs";
import { t as expectDefined } from "./expect-lbe3Hgrh.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import "./utils-DgZn2dLu.mjs";
import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs";
import { r as normalizeProviderId } from "./provider-id-D1VImdKk.mjs";
import { d as resolveAgentWorkspaceDir } from "./agent-scope-config-CDYpzA0r.mjs";
import { t as resolveDefaultAgentWorkspaceDir } from "./workspace-default-path-Cwkw6Ed6.mjs";
import { r as normalizeAgentModelRefForConfig } from "./model-input-C9K4a_6k.mjs";
import "./agent-scope-CG17uXFn.mjs";
import { i as resolveProviderIdForAuth } from "./provider-auth-aliases-DGVQj_99.mjs";
import "./model-ref-shared-KBg0y299.mjs";
import { t as parseDurationMs } from "./parse-duration-C4JaJvt9.mjs";
import { l as resolveSharedMainAuthAgentDir } from "./path-resolve-CIRhdX6A.mjs";
import { n as normalizeSecretInput } from "./normalize-secret-input-DBJvdk-L.mjs";
import { a as removeProviderAuthProfilesWithLock, r as promoteAuthProfileInOrder, u as upsertAuthProfileWithLockOrThrow } from "./profiles-zTWlOxGK.mjs";
import { o as listRuntimeLocalProfileIds } from "./runtime-snapshot-owner-BFahRX4y.mjs";
import { i as resolvePluginSetupRegistry, r as resolvePluginSetupProviderCore } from "./setup-registry-BwvtiOQK.mjs";
import { l as loadAuthProfileStoreWithoutExternalProfiles } from "./store-runtime-DZk7O5wt.mjs";
import { r as resolvePluginProvidersCore } from "./providers.runtime-De7Zs9_Y.mjs";
import { t as isCliProvider } from "./model-selection-cli-DjravRCg.mjs";
import "./auth-profiles-DnH-eEC0.mjs";
import "./workspace-BHjQqjjT.mjs";
import { n as stylePromptMessage } from "./prompt-style-zarsDmI2.mjs";
import { n as ProviderCredentialsSavedError, t as ProviderAuthConfigApplyError } from "./provider-auth-result-B4UlBTW7.mjs";
import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.mjs";
import { t as applyPrimaryModel } from "./provider-model-primary-CBQXA_V3.mjs";
import { i as restorePriorAgentsDefaultsModelUnlessOptIn, n as pickAuthMethod, r as resolveProviderMatch, t as applyProviderAuthConfigPatch } from "./provider-auth-choice-helpers-gVHdr-sl.mjs";
import { t as applyAuthProfileConfig } from "./provider-auth-helpers-CMstK74G.mjs";
import { t as runProviderPluginAuthMethodUnpersisted } from "./provider-auth-method-BV4ZL_DB.mjs";
import { n as persistProviderAuthProfilesAfterLogin } from "./provider-auth-persistence-0K0Bdsdu.mjs";
import { t as styleSelectParams } from "./prompt-select-styled-params-DiyhlGNl.mjs";
import { t as createClackPrompter } from "./clack-prompter-D76Rt341.mjs";
import { r as logConfigUpdated } from "./logging-BBc-wU1i.mjs";
import { i as loadValidConfigSnapshotOrThrow, l as resolveModelsTargetAgent, u as updateConfig } from "./shared-zVYpFkgx.mjs";
import { n as validateAnthropicSetupToken } from "./provider-auth-token-Bn7OhxfC.mjs";
import { t as withPluginMigrationProviders } from "./migration-provider-runtime-6qEYRpmu.mjs";
import { t as buildMigrationContext } from "./context-BCVVmNgh.mjs";
import { t as applyMigrationItemSelection } from "./item-selection-DbKxBUEq.mjs";
import { a as repairCopilotRuntimePluginInstallForModelSelection, i as repairCodexRuntimePluginInstallForModelSelection } from "./runtime-plugin-install-D-P8jolo.mjs";
import "./codex-runtime-plugin-install-BgK2UV0f.mjs";
import { n as refreshRunningGatewayAuthState, t as refreshProviderAuthAfterLogin } from "./auth-refresh-B8Oo71H-.mjs";
import { a as validateOpenAICodexApiKeyInput, i as resolveDefaultTokenProfileId, n as looksLikeOpenAIApiKey, r as normalizeManualAuthProvider, t as saveModelProviderApiKey } from "./auth-api-key-RLwyyGUN.mjs";
import { a as withoutProviderModelPolicy, i as prepareProviderModelAccess, n as applyProviderLoginDefaultModel, r as completeProviderModelAccess } from "./auth-model-policy-Bqa9LJZ9.mjs";
import { n as writeProviderAuthConfig, t as createProviderAuthConfigPatch } from "./provider-auth-config-DmqVZIe0.mjs";
import "./copilot-runtime-plugin-install-BgK2UV0f.mjs";
import path from "node:path";
import { cancel, confirm, isCancel, password, select, text } from "@clack/prompts";
//#region src/commands/models/auth-credential-import.ts
/** Imports only the credential item declared by the selected login method. */
async function tryImportProviderCredential(params) {
const spec = params.method.credentialImport;
if (!spec) return;
const provider = normalizeProviderId(params.providerId);
params.signal?.throwIfAborted();
return await withPluginMigrationProviders({
providerId: spec.migrationProviderId,
cfg: params.config
}, async (providers) => {
const owner = providers.find((candidate) => candidate.id === spec.migrationProviderId);
if (!owner) return;
const context = buildMigrationContext({
targetAgentId: params.agentId,
itemKinds: ["auth"],
includeSecrets: true,
configOverride: params.config,
providerOptions: {
allowKeychainPrompt: true,
credentialKind: spec.credentialKind,
configPatchMode: "none"
},
runtime: params.runtime
});
if (params.signal) context.signal = params.signal;
const plan = await owner.plan(context);
const candidates = plan.items.filter((item) => item.id === spec.itemId);
if (candidates.length > 1) throw new Error("The credential import has an ambiguous item identity.");
const candidate = candidates[0];
if (candidate?.status === "skipped" && candidate.details?.credentialImportUnavailable === true) return candidate.message ? { unavailableReason: candidate.message } : void 0;
if (candidate?.kind !== "auth" || candidate.status !== "planned" || candidate.details?.credentialKind !== spec.credentialKind) return;
if (typeof candidate.details.provider !== "string" || normalizeProviderId(candidate.details.provider) !== provider) throw new Error("The credential import belongs to another provider.");
params.signal?.throwIfAborted();
await params.beforePersistentEffect?.();
params.signal?.throwIfAborted();
const imported = (await owner.apply(context, applyMigrationItemSelection(plan, [spec.itemId]))).items.find((item) => item.id === spec.itemId && item.kind === "auth" && item.status === "migrated");
const profileId = imported?.details?.profileId;
if (typeof profileId !== "string" || !profileId.trim() || typeof imported?.details?.provider !== "string" || normalizeProviderId(imported.details.provider) !== provider || imported.details.credentialKind !== spec.credentialKind) throw new Error("The existing provider credential changed during import. Start the sign-in again.");
return {
profileId: profileId.trim(),
provider,
mode: spec.credentialKind,
configUpdated: imported.details.configUpdated === true
};
});
}
//#endregion
//#region src/commands/models/auth-relogin-identity.ts
function snapshotReloginAuthProfiles(params) {
if (!params.matchesPersonalAccount) return;
const storeAgentDir = path.resolve(params.agentDir) === path.resolve(resolveSharedMainAuthAgentDir()) ? void 0 : params.agentDir;
const store = loadAuthProfileStoreWithoutExternalProfiles(storeAgentDir);
return Object.fromEntries(listRuntimeLocalProfileIds(store).flatMap((profileId) => {
const credential = store.profiles[profileId];
return credential ? [[profileId, credential]] : [];
}));
}
function resolveReloginProfileIdentity(params) {
const requestedProfileId = params.requestedProfileId?.trim();
if (requestedProfileId) {
if (params.profiles.length !== 1) throw new Error("--profile-id requires exactly one returned auth profile from the selected auth method.");
return { profiles: [{
...expectDefined(params.profiles[0], "auth profile"),
profileId: requestedProfileId
}] };
}
const matcher = params.matchesPersonalAccount;
if (!matcher || !params.existingProfiles || params.profiles.length !== 1) return { profiles: params.profiles };
const returnedProfile = expectDefined(params.profiles[0], "auth profile");
const matchingProfileIds = [];
for (const [profileId, credential] of Object.entries(params.existingProfiles)) try {
if (matcher(returnedProfile.credential, credential)) matchingProfileIds.push(profileId);
} catch {
return { profiles: params.profiles };
}
if (matchingProfileIds.length !== 1) return { profiles: params.profiles };
const reusedProfileId = expectDefined(matchingProfileIds[0], "matched auth profile id");
return {
profiles: [{
...returnedProfile,
profileId: reusedProfileId
}],
validateCurrentCredential: (profileId, current) => {
let currentMatches = false;
if (current) try {
currentMatches = matcher(returnedProfile.credential, current);
} catch {
currentMatches = false;
}
if (profileId !== reusedProfileId || (current ? !currentMatches : !params.allowMissingReusedProfile)) throw new Error("The existing auth profile identity changed during sign-in. Start the sign-in again.");
}
};
}
//#endregion
//#region src/commands/models/auth.ts
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 password$1(promptParams) : await readPipedStdin();
const normalized = normalizeSecretInput(input);
const validationMessage = params.validate?.(normalized);
if (validationMessage) throw new Error(validationMessage);
return normalized;
}
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) {
if (params.ownerPluginId) return mergeSetupProviders(params.providers, resolvePluginSetupRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
pluginIds: [params.ownerPluginId]
}).providers.map((entry) => entry.provider));
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 configSnapshot = await loadValidConfigSnapshotOrThrow();
const config = params?.config ?? configSnapshot.runtimeConfig;
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,
configSnapshot,
agentId,
agentDir,
workspaceDir,
providers: preferSetupAuthProviders({
providers: resolvePluginProvidersCore({
config,
workspaceDir,
mode: "setup",
includeUntrustedWorkspacePlugins: false,
...params?.ownerPluginId ? { onlyPluginIds: [params.ownerPluginId] } : {},
...providerRef ? { providerRefs: [providerRef] } : {}
}),
config,
workspaceDir,
requestedProvider: providerRef,
ownerPluginId: params?.ownerPluginId
})
};
}
async function resolveModelsAuthAgent(rawAgentId, config) {
const cfg = config ?? (await loadValidConfigSnapshotOrThrow()).runtimeConfig;
return resolveModelsTargetAgent(cfg, rawAgentId ?? void 0, { kind: "mutation" });
}
function resolveRequestedLoginProviderOrThrow(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")}\`.`);
}
async function pickProviderAuthMethod(params) {
const rawRequestedMethod = params.requestedMethod?.trim();
if (rawRequestedMethod) return pickAuthMethod(params.provider, rawRequestedMethod);
const methods = params.tokenOnly ? listTokenAuthMethods(params.provider) : params.provider.auth;
if (params.tokenOnly && methods.length === 0) return null;
const preferred = methods.find((method) => params.tokenOnly ? method.id === "setup-token" : method.kind === "oauth");
if (preferred) return preferred;
if (methods.length === 1) return methods[0] ?? null;
return params.prompter.select({
message: `${params.tokenOnly ? "Token" : "Auth"} method for ${params.provider.label}`,
options: methods.map((method) => ({
value: method.id,
label: method.label,
hint: method.hint
}))
}).then((id) => methods.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 loginConfig = applyProviderAuthConfigPatch(params.config, {});
const configPatch = params.result.configPatch ? createProviderAuthConfigPatch(loginConfig, restorePriorAgentsDefaultsModelUnlessOptIn({
cfg: applyProviderAuthConfigPatch(loginConfig, profiles.length > 0 ? withoutProviderModelPolicy(params.result.configPatch, loginConfig) : params.result.configPatch, { replaceDefaultModels: params.result.replaceDefaultModels }),
priorAgentsDefaultsModel: loginConfig.agents?.defaults?.model,
setDefault: params.setDefault
})) : void 0;
const shouldUpdateConfig = isRecord(configPatch) && Object.keys(configPatch).length > 0 || Boolean(params.setDefault && defaultModel);
if (profiles.length > 0 || shouldUpdateConfig) await params.beforePersistentEffect?.();
try {
for (const candidate of profiles) {
const persisted = await persistProviderAuthProfilesAfterLogin({
profiles: [candidate],
beforeWrite: params.assertCurrent,
validateCurrentCredential: params.validateCurrentCredential,
config: params.config,
env: params.env,
agentDir: params.agentDir,
...params.env?.OPENCLAW_STATE_DIR ? { stateDir: params.env.OPENCLAW_STATE_DIR } : {}
});
const profile = expectDefined(persisted[0], "persisted auth profile");
persistedProfiles.push(profile);
params.assertCurrent?.();
await promotePersistedAuthProfile({
config: params.config,
agentDir: params.agentDir,
provider: profile.credential.provider,
profileId: profile.profileId
});
}
if (shouldUpdateConfig) {
const updated = await writeProviderAuthConfig({
config: params.config,
configSnapshot: params.configSnapshot,
configPatch,
credentialsSaved: persistedProfiles.length > 0,
writeOptions: { assertCurrent: params.assertCurrent },
finalizeConfig: (replayed, cfg) => {
const priorAgentsDefaultsModel = cfg.agents?.defaults?.model;
const next = restorePriorAgentsDefaultsModelUnlessOptIn({
cfg: replayed,
priorAgentsDefaultsModel,
setDefault: params.setDefault
});
if (params.setDefault && defaultModel) return profiles.length > 0 ? applyProviderLoginDefaultModel(next, defaultModel) : applyPrimaryModel(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);
}
const authRefresh = await refreshProviderAuthAfterLogin(params);
for (const profile of persistedProfiles) params.runtime.log(`Auth profile: ${profile.profileId} (${profile.credential.provider}/${profile.credential.type})`);
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 {
profiles: persistedProfiles,
authRefresh
};
} catch (error) {
if (persistedProfiles.length > 0 && !(error instanceof ProviderCredentialsSavedError)) throw new ProviderCredentialsSavedError(error instanceof Error ? `Provider credentials were saved, but sign-in did not finish: ${error.message}` : "Provider credentials were saved, but sign-in did not finish.", { cause: error });
throw error;
}
}
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,
storedCredential: true
}) === providerAuthKey).map(([profileId]) => profileId);
return profileIds.length > 0 ? {
createIfMissing: true,
order: profileIds
} : { createIfMissing: false };
}
async function promotePersistedAuthProfile(params) {
const selection = resolveConfiguredAuthSelectionForProvider(params.config, params.provider);
if (!(await promoteAuthProfileInOrder({
agentDir: params.agentDir,
provider: params.provider,
profileId: params.profileId,
createIfMissing: selection.createIfMissing,
...selection.order ? { createFromOrder: selection.order } : {}
})).ok) throw new ProviderCredentialsSavedError("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.");
}
async function runProviderAuthMethod(params) {
params.signal?.throwIfAborted();
params.assertCurrent?.();
const modelAccess = prepareProviderModelAccess({
config: params.config,
agentId: params.agentId,
provider: params.provider.id,
providerLabel: params.provider.label
});
const store = loadAuthProfileStoreWithoutExternalProfiles(params.agentDir);
const existingProfiles = Object.entries(store.profiles).filter(([profileId, credential]) => credential.provider === params.provider.id && (!params.profileId || profileId === params.profileId)).map(([profileId, credential]) => ({
profileId,
credential
}));
const result = await runProviderPluginAuthMethodUnpersisted({
method: params.method,
config: params.config,
existingProfiles,
credentialOnly: params.credentialOnly,
assertCurrent: params.assertCurrent,
env: params.env ?? process.env,
agentDir: params.agentDir,
workspaceDir: params.workspaceDir,
prompter: params.prompter,
runtime: params.runtime,
allowSecretRefPrompt: false,
isRemote: params.isRemote,
signal: params.signal,
openUrl: params.openUrl,
browserAuthorization: params.browserAuthorization
});
params.signal?.throwIfAborted();
const connectionResult = params.credentialOnly ? {
profiles: result.profiles,
notes: result.notes,
...result.configPatch ? { configPatch: {
...result.configPatch.models?.providers ? { models: { providers: result.configPatch.models.providers } } : {},
...result.configPatch.plugins ? { plugins: result.configPatch.plugins } : {}
} } : {}
} : result;
const resolvedIdentity = resolveReloginProfileIdentity({
profiles: connectionResult.profiles,
requestedProfileId: params.profileId,
existingProfiles: params.existingProfiles,
matchesPersonalAccount: params.method.matchesPersonalAccount,
allowMissingReusedProfile: params.allowMissingReusedProfile
});
const profiles = resolvedIdentity.profiles;
const { profiles: persistedProfiles, authRefresh } = await persistProviderAuthResult({
...params,
result: connectionResult,
profiles,
env: params.env ?? process.env,
validateCurrentCredential: resolvedIdentity.validateCurrentCredential
});
if (persistedProfiles.length > 0) await completeProviderModelAccess({
prepared: modelAccess,
prompter: params.prompter,
onRequested: params.onModelAccessRequested,
runtime: params.runtime,
assertCurrent: () => {
params.signal?.throwIfAborted();
params.assertCurrent?.();
}
}).catch((error) => {
throw new ProviderAuthConfigApplyError(error);
});
return {
result: connectionResult,
profiles: persistedProfiles,
authRefresh
};
}
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, configSnapshot, 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 = resolveRequestedLoginProviderOrThrow(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 pickProviderAuthMethod({
provider,
prompter,
tokenOnly: true
});
if (!method) throw new Error(`Provider "${provider.id}" does not expose a token auth method.`);
await runProviderAuthMethod({
config,
configSnapshot,
agentId,
agentDir,
workspaceDir,
provider,
method,
runtime,
prompter
});
}
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 (provider === "openai" && 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}`,
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, "login", runtime);
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.");
}
}
async function modelsAuthPasteApiKeyCommand(opts, runtime) {
const config = (await loadValidConfigSnapshotOrThrow()).runtimeConfig;
const { agentId, agentDir } = await resolveModelsAuthAgent(opts.agent, config);
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 key = await readPastedSecret({
message: `Paste API key for ${provider}`,
validate: (value) => {
const trimmed = value?.trim();
if (!trimmed) return "Required";
if (provider === "openai") return validateOpenAICodexApiKeyInput(trimmed);
}
});
const { profileId, warning } = await saveModelProviderApiKey({
config,
provider,
apiKey: key,
agentDir,
profileId: normalizeOptionalString(opts.profileId)
});
await refreshRunningGatewayAuthState(agentId, "login", runtime);
if (warning) runtime.error(warning);
logConfigUpdated(runtime);
runtime.log(`Auth profile: ${profileId} (${provider}/api_key)`);
}
async function modelsAuthAddCommand(opts, runtime) {
const { config, configSnapshot, 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 : resolveRequestedLoginProviderOrThrow(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,
configSnapshot,
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);
}
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) {
return runModelsAuthLoginFlow(opts, true);
}
async function runModelsAuthLoginFlowForGateway(opts) {
return runModelsAuthLoginFlow(opts, false);
}
async function runModelsAuthLoginFlow(opts, showScopeNote) {
const requestedProviderId = opts.provider ? normalizeManualAuthProvider(opts.provider) : void 0;
let context = await resolveModelsAuthContext({
requestedProvider: requestedProviderId,
rawAgentId: opts.agent,
config: opts.config,
ownerPluginId: opts.ownerPluginId
});
const prompter = opts.prompter;
let authProviders = context.providers.filter((provider) => provider.auth.length > 0);
let requestedProvider = requestedProviderId ? resolveProviderMatch(authProviders, requestedProviderId) : null;
const useProviderPicker = !opts.ownerPluginId && requestedProviderId !== void 0 && requestedProvider === null && isCliProvider(requestedProviderId, context.config);
if (useProviderPicker) {
context = await resolveModelsAuthContext({
rawAgentId: opts.agent,
config: context.config
});
authProviders = context.providers.filter((provider) => provider.auth.length > 0);
}
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);
if (showScopeNote) 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.`);
if (opts.ownerPluginId && selectedProvider.id !== opts.provider) throw new Error("The selected provider login is no longer available.");
const chosenMethod = opts.ownerPluginId ? selectedProvider.auth.find((method) => method.id === opts.method) : 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.`);
const modelAccess = prepareProviderModelAccess({
config: context.config,
agentId: context.agentId,
provider: selectedProvider.id,
providerLabel: selectedProvider.label
});
const existingProfiles = snapshotReloginAuthProfiles({
agentDir: context.agentDir,
matchesPersonalAccount: chosenMethod.matchesPersonalAccount
});
const imported = !opts.credentialOnly && !opts.force && !opts.profileId && !opts.setDefault ? await tryImportProviderCredential({
method: chosenMethod,
providerId: selectedProvider.id,
config: context.config,
agentId: context.agentId,
runtime: opts.runtime,
signal: opts.signal,
beforePersistentEffect: opts.beforePersistentEffect
}) : void 0;
if (imported && "unavailableReason" in imported) await prompter.note(imported.unavailableReason, "Existing CLI sign-in");
else if (imported) {
await promotePersistedAuthProfile({
config: context.config,
agentDir: context.agentDir,
provider: imported.provider,
profileId: imported.profileId
});
const authRefresh = await refreshProviderAuthAfterLogin({
...opts,
agentId: context.agentId
});
if (imported.configUpdated) logConfigUpdated(opts.runtime);
opts.runtime.log(`Auth profile: ${imported.profileId} (${imported.provider}/${imported.mode}, imported)`);
await completeProviderModelAccess({
prepared: modelAccess,
prompter,
onRequested: opts.onModelAccessRequested,
runtime: opts.runtime,
assertCurrent: () => {
opts.signal?.throwIfAborted();
opts.assertCurrent?.();
}
}).catch((error) => {
throw new ProviderAuthConfigApplyError(error);
});
return {
providerId: selectedProvider.id,
methodId: chosenMethod.id,
authRefresh,
imported: true,
profiles: [{
profileId: imported.profileId,
provider: imported.provider,
mode: imported.mode
}]
};
}
let forcePurgedProviderProfiles = false;
if (opts.force) {
await opts.beforePersistentEffect?.();
try {
if (!await removeProviderAuthProfilesWithLock({
cfg: context.config,
provider: selectedProvider.id,
agentDir: context.agentDir
})) throw new Error("auth store is busy; close other OpenClaw commands using this state directory and retry");
forcePurgedProviderProfiles = true;
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 });
}
await refreshRunningGatewayAuthState(context.agentId, "logout", opts.runtime);
}
const { result, profiles, authRefresh } = await runProviderAuthMethod({
...opts,
...context,
provider: selectedProvider,
method: chosenMethod,
prompter,
existingProfiles,
allowMissingReusedProfile: forcePurgedProviderProfiles
});
maybeLogOpenAICodexNativeSearchTip(opts.runtime, selectedProvider.id);
return {
providerId: selectedProvider.id,
methodId: chosenMethod.id,
authRefresh,
...result.defaultModel ? { defaultModel: result.defaultModel } : {},
profiles: profiles.map((profile) => ({
profileId: profile.profileId,
provider: profile.credential.provider,
mode: profile.credential.type
}))
};
}
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 { modelsAuthSetupTokenCommand as a, runModelsAuthLoginFlowForGateway as c, modelsAuthPasteTokenCommand as i, modelsAuthLoginCommand as n, resolveRequestedLoginProviderOrThrow as o, modelsAuthPasteApiKeyCommand as r, runModelsAuthLoginFlowCore as s, modelsAuthAddCommand as t };