UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

252 lines (251 loc) • 12.6 kB
import { n as sanitizeTerminalText } from "./safe-text-SY_CvCi3.mjs"; import { n as formatConfigIssueLines } from "./issue-format-L1jxHK43.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { c as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-DWiOhaNI.mjs"; import { r as enablePluginWithCapabilityConsent } from "./enable-C2KEdu16.mjs"; import { u as readConfigFileSnapshotForWrite } from "./io.runtime-BsUXSsD4.mjs"; import { r as replaceConfigFile } from "./mutate-DJq1JylX.mjs"; import "./config-DLycWFvB.mjs"; import { a as hasAvailableAuthForProvider } from "./model-auth-CU3YlZuV.mjs"; import { r as resolveManifestProviderAuthChoice } from "./provider-auth-choices-DMYmVIgL.mjs"; import { r as resolveProviderInstallCatalogEntry } from "./provider-install-catalog-CCgeBGs_.mjs"; import { t as ClawHubRequestError } from "./clawhub-client-D-mEumEV.mjs"; import { r as promptYesNo } from "./prompt-Dtcnfqvp.mjs"; import { t as createPluginCapabilityConsentPrompter } from "./plugin-capability-consent-B7boFiKO.mjs"; import { t as applyAuthChoiceLoadedPluginProvider } from "./provider-auth-choice-oOVfdOEn.mjs"; import { t as createClackPrompter } from "./clack-prompter-D76Rt341.mjs"; import { f as upsertCanonicalModelConfigEntry, t as applyDefaultModelPrimaryUpdate, u as updateConfig } from "./shared-zVYpFkgx.mjs"; import { a as repairCopilotRuntimePluginInstallForModelSelection, i as repairCodexRuntimePluginInstallForModelSelection } from "./runtime-plugin-install-D-P8jolo.mjs"; import "./codex-runtime-plugin-install-BgK2UV0f.mjs"; import { t as normalizeAlias } from "./alias-name-dY2or4SK.mjs"; import "./copilot-runtime-plugin-install-BgK2UV0f.mjs"; import { n as recordPromotionClaim, r as fetchClawHubPromotion, t as markPromotionSlugsNotified } from "./promotions-feed-D9IPRe55.mjs"; //#region src/commands/promos/claim.ts 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 knownPackages = new Set(authChoice?.packageNames ?? []); const unsupported = promotion.pluginNames?.find((name) => !knownPackages.has(name)); if (unsupported === void 0) return; const authChoiceLabel = authChoice ? `auth choice "${authChoice.entry.choiceId}"` : "a missing auth choice"; throw new Error(`Promotion "${promotion.slug}" requires plugin package "${unsupported}", but ${authChoiceLabel} does not provide it in this OpenClaw version. Update OpenClaw and retry.`); } async function readValidConfigWriteSnapshot() { const prepared = await readConfigFileSnapshotForWrite(); const { snapshot } = prepared; if (!snapshot.valid) { const issues = formatConfigIssueLines(snapshot.issues, "-").join("\n"); throw new Error(`Invalid config at ${snapshot.path}\n${issues}`); } return prepared; } async function ensureProviderAuth(params) { const { promotion, provider, authChoice, prepared, opts, runtime } = params; const { snapshot, writeOptions } = prepared; 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 }); if (!applied || applied.retrySelection || !await hasAvailableAuthForProvider({ provider, cfg: applied.config })) throw new Error(`Authentication for "${provider}" was not completed; nothing was changed.`); await replaceConfigFile({ sourceConfig: applied.config, baseHash: snapshot.hash, writeOptions }); } 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 prepared = await readValidConfigWriteSnapshot(); const { snapshot } = prepared; const authChoice = resolveAuthChoice(promotion, provider, snapshot.runtimeConfig ?? snapshot.config); requirePromotionPlugins(promotion, authChoice); await ensureProviderAuth({ promotion, provider, authChoice, prepared, 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; }); await recordPromotionClaim({ slug: promotion.slug, provider, modelKeys: [...new Set(registered)], endsAtMs: promotion.endsAt, claimedAtMs: Date.now() }); await 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 };