openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
252 lines (251 loc) • 12.6 kB
JavaScript
import { s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js";
import { n as ClawHubRequestError } from "./clawhub-client-I4w9Lg5d.js";
import { c as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-BbV7X6pV.js";
import { n as formatConfigIssueLines } from "./issue-format-CQvxWNW8.js";
import { r as enablePluginWithCapabilityConsent } from "./enable-3OwjFbBV.js";
import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js";
import "./config-Cs0XXL3x.js";
import { a as hasAvailableAuthForProvider } from "./model-auth-C48_DZ-I.js";
import { n as resolveManifestProviderAuthChoice } from "./provider-auth-choices-DsRM415K.js";
import { r as promptYesNo } from "./prompt-BvBir9Yc.js";
import { t as createPluginCapabilityConsentPrompter } from "./plugin-capability-consent-CaBGom29.js";
import { r as resolveProviderInstallCatalogEntry } from "./provider-install-catalog-BzkhdO6-.js";
import { t as applyAuthChoiceLoadedPluginProvider } from "./provider-auth-choice-DsHvi0o6.js";
import { t as createClackPrompter } from "./clack-prompter-BSpWH_ak.js";
import { c as updateConfig, t as applyDefaultModelPrimaryUpdate, u as upsertCanonicalModelConfigEntry } 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 normalizeAlias } from "./alias-name-Bwsh7Ooz.js";
import "./copilot-runtime-plugin-install-B2_P31Ru.js";
import { a as recordPromotionClaim, n as markPromotionSlugsNotified, o as fetchClawHubPromotion } from "./promotions-feed-BPY4OXVb.js";
//#region src/commands/promos/claim.ts
/** Claims a ClawHub promotion: configures provider auth and registers its models. */
function resolvePromotionModelTarget(promotion, modelRef) {
const provider = promotion.provider ?? "";
const prefix = `${provider}/`;
if (!modelRef.startsWith(prefix) || modelRef.length <= prefix.length) throw new Error(`Promotion "${promotion.slug}" lists model "${modelRef}" outside its provider "${provider}"; refusing to configure it.`);
return {
provider,
model: modelRef.slice(prefix.length)
};
}
async function fetchLivePromotion(slug) {
try {
return await fetchClawHubPromotion({ slug });
} catch (error) {
if (error instanceof ClawHubRequestError && error.status === 404) throw new Error(`Promotion "${slug}" was not found or is not live. See ${formatCliCommand("openclaw promos list")}.`, { cause: error });
throw error;
}
}
function requireLiveWindow(promotion) {
const now = Date.now();
if (now > promotion.endsAt) throw new Error(`Promotion "${promotion.slug}" ended on ${new Date(promotion.endsAt).toLocaleDateString()}.`);
if (now < promotion.startsAt || !promotion.active) throw new Error(`Promotion "${promotion.slug}" is not live yet.`);
}
function promotionClaimContract(promotion) {
return JSON.stringify({
slug: promotion.slug,
startsAt: promotion.startsAt,
endsAt: promotion.endsAt,
provider: promotion.provider ?? null,
authChoiceId: promotion.authChoiceId ?? null,
pluginNames: [...promotion.pluginNames ?? []].toSorted(),
models: promotion.models.map((model) => ({
modelRef: model.modelRef,
alias: model.alias ?? null,
suggestedDefault: Boolean(model.suggestedDefault)
}))
});
}
function requireUnchangedClaimContract(initial, revalidated) {
if (promotionClaimContract(initial) === promotionClaimContract(revalidated)) return;
throw new Error(`Promotion "${initial.slug}" changed while the claim was in progress; no promotional models were added. Any provider credentials you just configured were kept. Run ${formatCliCommand("openclaw promos list")} and retry.`);
}
function resolveManifestPluginPackageNames(pluginId, cfg) {
const snapshot = loadManifestMetadataSnapshot({ config: cfg });
return [...new Set(snapshot.manifestRegistry.plugins.filter((plugin) => plugin.id === pluginId).map((plugin) => plugin.packageName?.trim()).filter((name) => Boolean(name)))];
}
function resolveCatalogPluginPackageNames(entry) {
const npmPackage = entry.installSource?.npm?.expectedPackageName ?? entry.installSource?.npm?.packageName;
return [...new Set([npmPackage, entry.installSource?.clawhub?.packageName].filter((name) => Boolean(name)))];
}
function resolveAuthChoice(promotion, provider, cfg) {
const authChoiceId = promotion.authChoiceId?.trim();
if (!authChoiceId) return;
const manifestEntry = resolveManifestProviderAuthChoice(authChoiceId, {
config: cfg,
includeUntrustedWorkspacePlugins: false
});
const catalogEntry = manifestEntry ? void 0 : resolveProviderInstallCatalogEntry(authChoiceId, {
config: cfg,
includeUntrustedWorkspacePlugins: false
});
const entry = manifestEntry ?? catalogEntry;
if (!entry) throw new Error(`Promotion "${promotion.slug}" requires auth choice "${authChoiceId}", which this OpenClaw version does not know. Update OpenClaw and retry.`);
if (entry.providerId !== provider) throw new Error(`Promotion "${promotion.slug}" declares provider "${provider}" but its auth choice belongs to "${entry.providerId}"; refusing to configure it.`);
const packageNames = manifestEntry ? resolveManifestPluginPackageNames(manifestEntry.pluginId, cfg) : catalogEntry ? resolveCatalogPluginPackageNames(catalogEntry) : [];
return {
entry,
installed: Boolean(manifestEntry),
packageNames
};
}
function requirePromotionPlugins(promotion, authChoice) {
const declared = promotion.pluginNames ?? [];
if (declared.length === 0) return;
const knownPackages = new Set(authChoice?.packageNames ?? []);
const unsupported = declared.filter((name) => !knownPackages.has(name));
if (unsupported.length === 0) return;
const authChoiceLabel = authChoice ? `auth choice "${authChoice.entry.choiceId}"` : "a missing auth choice";
throw new Error(`Promotion "${promotion.slug}" requires plugin package "${unsupported[0]}", but ${authChoiceLabel} does not provide it in this OpenClaw version. Update OpenClaw and retry.`);
}
async function readValidConfigSnapshot() {
const snapshot = await readConfigFileSnapshot();
if (!snapshot.valid) {
const issues = formatConfigIssueLines(snapshot.issues, "-").join("\n");
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
}
return snapshot;
}
async function ensureProviderAuth(params) {
const { promotion, provider, authChoice, snapshot, opts, runtime } = params;
const catalogEntry = authChoice?.entry;
const runtimeConfig = snapshot.runtimeConfig ?? snapshot.config;
const apiKey = opts.apiKey?.trim();
if (!apiKey && (authChoice?.installed ?? true) && await hasAvailableAuthForProvider({
provider,
cfg: runtimeConfig
})) {
runtime.log(`Using your existing ${provider} credentials.`);
return;
}
if (!catalogEntry) throw new Error(`No credentials configured for provider "${provider}". Add one with ${formatCliCommand("openclaw models auth add")} and retry.`);
if (promotion.signupUrl) runtime.log(`Get a free key for this promotion: ${sanitizeTerminalText(promotion.signupUrl)}`);
if (apiKey && !catalogEntry.optionKey) throw new Error(`Auth choice "${catalogEntry.choiceId}" does not accept --api-key; run without it to authenticate interactively.`);
const applied = await applyAuthChoiceLoadedPluginProvider({
authChoice: catalogEntry.choiceId,
config: structuredClone(snapshot.sourceConfig ?? snapshot.config),
prompter: createClackPrompter(),
runtime,
setDefaultModel: false,
opts: apiKey && catalogEntry.optionKey ? { [catalogEntry.optionKey]: apiKey } : void 0
});
const authCompleted = applied && !applied.retrySelection && await hasAvailableAuthForProvider({
provider,
cfg: applied.config
});
if (!applied || !authCompleted) throw new Error(`Authentication for "${provider}" was not completed; nothing was changed.`);
await replaceConfigFile({
nextConfig: applied.config,
baseHash: snapshot.hash
});
}
function aliasTaken(models, alias) {
const lowered = alias.toLowerCase();
return Object.values(models).some((entry) => entry.alias?.toLowerCase() === lowered);
}
async function promosClaimCommand(slugRaw, opts, runtime) {
const slug = slugRaw.trim().toLowerCase();
if (!slug) throw new Error("Promotion slug required.");
let promotion = await fetchLivePromotion(slug);
requireLiveWindow(promotion);
const provider = promotion.provider?.trim();
if (!provider) throw new Error(`Promotion "${slug}" does not declare a provider; it cannot be claimed from the CLI.`);
for (const model of promotion.models) resolvePromotionModelTarget(promotion, model.modelRef);
const snapshot = await readValidConfigSnapshot();
const authChoice = resolveAuthChoice(promotion, provider, snapshot.runtimeConfig ?? snapshot.config);
requirePromotionPlugins(promotion, authChoice);
await ensureProviderAuth({
promotion,
provider,
authChoice,
snapshot,
opts,
runtime
});
const suggested = promotion.models.find((model) => model.suggestedDefault) ?? promotion.models[0];
let makeDefault = Boolean(opts.setDefault && suggested);
if (!makeDefault && suggested && process.stdin.isTTY) makeDefault = await promptYesNo(`Set ${suggested.modelRef} as your default model?`, false);
const revalidatedPromotion = await fetchLivePromotion(slug);
requireLiveWindow(revalidatedPromotion);
requireUnchangedClaimContract(promotion, revalidatedPromotion);
promotion = revalidatedPromotion;
const registered = [];
const skippedAliases = [];
const invalidAliases = [];
const updated = await updateConfig(async (cfg, context) => {
let base = cfg;
if (authChoice) {
const enabled = await enablePluginWithCapabilityConsent(base, authChoice.entry.pluginId, { onCapabilityConsent: process.stdin.isTTY ? createPluginCapabilityConsentPrompter(createClackPrompter()) : void 0 });
if (!enabled.enabled) throw new Error(`The "${authChoice.entry.pluginId}" plugin is blocked by your plugin policy (${enabled.reason ?? "disabled"}); cannot claim this promotion.`);
base = enabled.config;
}
const models = { ...base.agents?.defaults?.models };
for (const model of promotion.models) {
const target = resolvePromotionModelTarget(promotion, model.modelRef);
const key = upsertCanonicalModelConfigEntry(models, target);
let alias;
try {
alias = model.alias ? normalizeAlias(model.alias) : void 0;
} catch {
invalidAliases.push(model.alias ?? "");
}
if (alias && !models[key]?.alias) {
if (aliasTaken(models, alias)) skippedAliases.push(alias);
else models[key] = {
...models[key],
alias
};
}
registered.push(key);
}
let next = {
...base,
agents: {
...base.agents,
defaults: {
...base.agents?.defaults,
models
}
}
};
if (makeDefault && suggested) next = applyDefaultModelPrimaryUpdate({
cfg: next,
resolveCfg: context.runtimeConfig,
modelRaw: suggested.modelRef,
field: "model"
});
return next;
});
recordPromotionClaim({
slug: promotion.slug,
provider,
modelKeys: [...new Set(registered)],
endsAtMs: promotion.endsAt,
claimedAtMs: Date.now()
});
markPromotionSlugsNotified([promotion.slug]);
if (makeDefault && suggested) {
const repaired = await repairCodexRuntimePluginInstallForModelSelection({
cfg: updated,
model: suggested.modelRef
});
const copilotRepaired = await repairCopilotRuntimePluginInstallForModelSelection({
cfg: updated,
model: suggested.modelRef
});
for (const warning of [...repaired.warnings, ...copilotRepaired.warnings]) runtime.error?.(warning);
}
runtime.log(`Claimed "${sanitizeTerminalText(promotion.title)}".`);
for (const key of registered) runtime.log(` Added model: ${sanitizeTerminalText(key)}`);
for (const alias of skippedAliases) runtime.log(` Alias "${sanitizeTerminalText(alias)}" is already in use; kept your existing alias.`);
for (const alias of invalidAliases) runtime.log(` Alias "${sanitizeTerminalText(alias)}" is not a valid model alias; skipped it.`);
if (makeDefault && suggested) {
runtime.log(` Default model set to ${sanitizeTerminalText(suggested.modelRef)}.`);
runtime.log(` Revert anytime with ${formatCliCommand("openclaw models set <previous-model>")}.`);
} else if (suggested) runtime.log(` Try it: ${formatCliCommand(`openclaw models set ${suggested.modelRef}`)} (promotion ends ${new Date(promotion.endsAt).toLocaleDateString()}).`);
}
//#endregion
export { promosClaimCommand };