openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
155 lines (154 loc) • 8.67 kB
JavaScript
import { n as resolvePathViaExistingAncestorSync } from "./boundary-path-CAv_plX7.mjs";
import { s as registerSecretValueForRedaction } from "./secret-redaction-registry-b2B9lcfy.mjs";
import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs";
import { r as normalizeProviderId, t as findNormalizedProviderKey } from "./provider-id-BIyeXQag.mjs";
import { C as attachRuntimeConfigWriteApplication, T as createRuntimeConfigWriteApplication } from "./io.runtime-FunTy0y1.mjs";
import { c as resolveSharedAuthStorePath } from "./path-resolve-qRO5Wefj.mjs";
import { i as isUserModelAuthProfileId } from "./profile-usage-stats-CsXf5PwL.mjs";
import { n as normalizeSecretInput } from "./normalize-secret-input-DBJvdk-L.mjs";
import { d as resolvePersistedAuthProfileOwnerAgentDir } from "./store-a3_lxCDn.mjs";
import { _ as loadCandidateAuthProfileStore, g as listCandidateAuthProfileStores, u as upsertAuthProfileWithLockOrThrow } from "./profiles-4HoYZdiM.mjs";
import { i as captureGatewayRootWorkAdmissionContinuationScope } from "./gateway-work-admission-G8XukyHo.mjs";
import { a as resolveAuthProfileOrder } from "./order-CVv16yN9.mjs";
import { b as resolveProviderEntryApiKeyProfileReference, v as resolveProviderConfigSecretInput } from "./model-auth-provider-config-D93sNwG4.mjs";
import { i as ensureAuthProfileStoreWithoutExternalProfiles } from "./store-runtime-CGMnCh1h.mjs";
import "./auth-profiles-BvOnvz5y.mjs";
import { t as applyAuthProfileConfig } from "./provider-auth-helpers-BuFDRF5X.mjs";
import { i as loadValidConfigSnapshotOrThrow, u as updateConfig } from "./shared-Dneblodi.mjs";
import { isDeepStrictEqual } from "node:util";
//#region src/commands/models/auth-manual-input.ts
/** Input contracts shared by manual token/API-key commands and the API-key writer. */
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 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.";
}
//#endregion
//#region src/commands/models/auth-api-key.ts
/** Saves a manual key without changing model selection or connection settings. */
async function saveModelProviderApiKey(params) {
const provider = normalizeManualAuthProvider(params.provider);
const key = normalizeSecretInput(params.apiKey);
registerSecretValueForRedaction(key);
const validationError = !key ? "API key is required" : provider === "openai" ? validateOpenAICodexApiKeyInput(key) : void 0;
if (validationError) throw new Error(validationError);
const config = params.config ?? (await loadValidConfigSnapshotOrThrow()).runtimeConfig;
const validateCurrentCredential = (existing) => {
if (existing?.type === "api_key" && existing.keyRef) throw new Error("This API-key profile uses an external secret reference. Remove that saved sign-in before storing an inline key.");
if (existing && (existing.type !== "api_key" || normalizeProviderId(existing.provider) !== provider)) throw new Error("The API-key profile belongs to another sign-in. Manage that saved sign-in first.");
};
const configuredKey = (cfg) => {
const id = findNormalizedProviderKey(cfg.models?.providers, provider);
const connection = id ? cfg.models?.providers?.[id] : void 0;
if (connection?.auth && connection.auth !== "api-key") throw new Error("This connection uses another sign-in method. Use its sign-in option instead.");
return id;
};
const configuredBinding = (cfg, providerId) => {
const { providerConfig, ref } = resolveProviderConfigSecretInput(cfg, providerId);
return ref ?? providerConfig?.apiKey;
};
const connectionId = params.profileId ? void 0 : configuredKey(config);
const connectionBinding = connectionId === void 0 ? void 0 : configuredBinding(config, connectionId);
const store = ensureAuthProfileStoreWithoutExternalProfiles(params.agentDir);
const replacementId = !connectionId ? resolveAuthProfileOrder({
cfg: config,
store,
provider
}).find((id) => {
const credential = store.profiles[id];
return credential?.type === "api_key" && !credential.keyRef;
}) : void 0;
const configuredReference = connectionId ? resolveProviderEntryApiKeyProfileReference({
cfg: config,
provider: connectionId,
store
}) : void 0;
const configuredProfileId = configuredReference?.kind === "profile" || configuredReference?.kind === "profile-incompatible" ? configuredReference.profileId : void 0;
const profileId = params.profileId ?? configuredProfileId ?? replacementId ?? resolveDefaultTokenProfileId(provider);
if (isUserModelAuthProfileId(profileId)) throw new Error("Personal model accounts are managed in Settings → Profile → Connected accounts.");
const agentDir = connectionId ? void 0 : store.profiles[profileId] ? resolvePersistedAuthProfileOwnerAgentDir({
agentDir: params.agentDir,
profileId
}) : params.agentDir;
const localCandidates = connectionId ? (await listCandidateAuthProfileStores({ cfg: config })).filter((candidate) => candidate.databasePath !== resolvePathViaExistingAncestorSync(resolveSharedAuthStorePath())) : [];
const validateSharedBinding = () => {
if (localCandidates.some((candidate) => loadCandidateAuthProfileStore(candidate)?.profiles[profileId])) throw new Error("An agent already overrides this shared key. Remove that agent's override before replacing the shared key.");
};
const validateReplacement = (existing) => {
validateCurrentCredential(existing);
validateSharedBinding();
};
validateReplacement(store.profiles[profileId]);
await upsertAuthProfileWithLockOrThrow({
profileId,
credential: {
type: "api_key",
provider,
key
},
agentDir,
preserveApiKeyMetadata: true,
validateCurrentCredential: validateReplacement
});
const application = createRuntimeConfigWriteApplication(captureGatewayRootWorkAdmissionContinuationScope()?.run);
let configChanged = false;
await updateConfig((current) => {
const id = params.profileId ? void 0 : configuredKey(current);
if (!params.profileId && (id !== connectionId || id !== void 0 && !isDeepStrictEqual(configuredBinding(current, id), connectionBinding))) throw new Error("The provider connection changed during the key update. Reopen the connection and save the key again");
validateSharedBinding();
let next = applyAuthProfileConfig(current, {
...current.auth?.profiles?.[profileId],
profileId,
provider,
mode: "api_key"
});
if (id && next.models?.providers?.[id]) next = {
...next,
models: {
...next.models,
providers: {
...next.models.providers,
[id]: {
...next.models.providers[id],
apiKey: profileId
}
}
}
};
configChanged = !isDeepStrictEqual(current, next);
return next;
}, void 0, void 0, attachRuntimeConfigWriteApplication({}, application)).catch((error) => {
throw new Error("API key saved, but provider settings could not be applied: " + (error instanceof Error ? error.message : String(error)) + ". Reopen Models and save the key again.", { cause: error });
});
if (configChanged && !(application.claimed && await application.result === "applied")) return {
profileId,
warning: "API key saved, but the Gateway has not confirmed applying the provider settings. Run `openclaw gateway restart` to apply them."
};
return { profileId };
}
//#endregion
export { validateOpenAICodexApiKeyInput as a, resolveDefaultTokenProfileId as i, looksLikeOpenAIApiKey as n, normalizeManualAuthProvider as r, saveModelProviderApiKey as t };