UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

88 lines (87 loc) 4.11 kB
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import "./model-ref-shared-Dz7QU0Lx.js"; import { t as modelKey } from "./model-key-CMdQNkZf.js"; import { i as readPromotionClaims, n as markPromotionSlugsNotified, r as maybeRefreshPromotionsFeed, t as listLivePromotionEntries } from "./promotions-feed-BPY4OXVb.js"; //#region src/commands/models/list.promotions.ts /** Promotion decorations for `models list`: claim tags + passive discovery. */ const PROMOTIONS_SECTION_MAX_ENTRIES = 3; /** Starts the passive feed refresh so callers can overlap it with model row construction. */ function startPromotionsFeedRefresh(nowMs = Date.now()) { return { nowMs, statePromise: maybeRefreshPromotionsFeed({ nowMs }).catch(() => void 0) }; } /** * Tag configured rows that were registered by `promos claim`, flipping to * "promo ended" once the window passes so users learn why a model stopped * serving. Reads only local provenance; never the network. */ function applyPromotionClaimTags(rows, nowMs = Date.now()) { const claims = readPromotionClaims(); if (claims.length === 0) return; const endsByKey = /* @__PURE__ */ new Map(); for (const claim of claims) for (const key of claim.modelKeys) { const prev = endsByKey.get(key); if (prev === void 0 || claim.endsAtMs > prev) endsByKey.set(key, claim.endsAtMs); } for (const row of rows) { const endsAtMs = endsByKey.get(row.key); if (endsAtMs === void 0) continue; row.tags.push(endsAtMs < nowMs ? "promo ended" : "promo"); } } function canonicalPromotionModelKey(entry, modelRef) { const provider = entry.provider?.trim(); const prefix = provider ? `${provider}/` : ""; if (!provider || !modelRef.startsWith(prefix) || modelRef.length <= prefix.length) return; return modelKey(provider, modelRef.slice(prefix.length)); } /** * Passive discovery: cadence-gated feed refresh (fail-silent), an * "Available via promotion" group for live offers whose models are not * configured yet, and a one-time notice per newly seen offer. Callers gate * machine outputs (`--json`/`--plain`) — this only ever writes human text. * `configuredKeys` must be the user's configured model set, not the * rendered rows — filtered or `--all` listings show a different set. */ async function printAvailablePromotionsSection(params) { const { refresh } = params; const nowMs = refresh.nowMs; const state = await refresh.statePromise; if (!state) return; const live = listLivePromotionEntries(state, nowMs); if (live.length === 0) return; const unclaimed = live.filter((entry) => entry.models.some((model) => { const key = canonicalPromotionModelKey(entry, model.modelRef); return key !== void 0 && !params.configuredKeys.has(key); })); const { runtime } = params; const safe = sanitizeTerminalText; if (unclaimed.length > 0) { runtime.log(""); runtime.log("Available via promotion:"); for (const entry of unclaimed.slice(0, PROMOTIONS_SECTION_MAX_ENTRIES)) { const sponsor = entry.sponsor ? ` — ${safe(entry.sponsor)}` : ""; runtime.log(` ${safe(entry.title)}${sponsor} (ends ${new Date(entry.endsAt).toLocaleDateString()})`); for (const model of entry.models) { const alias = model.alias ? ` (${safe(model.alias)})` : ""; runtime.log(` · ${safe(model.modelRef)}${alias}`); } runtime.log(` Claim: ${formatCliCommand(`openclaw promos claim ${safe(entry.slug)}`)}`); } if (unclaimed.length > PROMOTIONS_SECTION_MAX_ENTRIES) { const more = unclaimed.length - PROMOTIONS_SECTION_MAX_ENTRIES; runtime.log(` …and ${more} more: ${formatCliCommand("openclaw promos list")}`); } } const unseen = live.filter((entry) => !state.notifiedSlugs.has(entry.slug)); if (unseen.length > 0) { runtime.log(""); runtime.log(`🎁 New promotional model offers available — ${formatCliCommand("openclaw promos list")} for details.`); markPromotionSlugsNotified(unseen.map((entry) => entry.slug)); } } //#endregion export { applyPromotionClaimTags, printAvailablePromotionsSection, startPromotionsFeedRefresh };