UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

230 lines (229 loc) 9.08 kB
//#region src/auto-reply/reply/history.ts const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]"; const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]"; const DEFAULT_GROUP_HISTORY_LIMIT = 50; /** Maximum number of group history keys to retain (LRU eviction when exceeded). */ const MAX_HISTORY_KEYS = 1e3; /** * Evict oldest keys from a history map when it exceeds MAX_HISTORY_KEYS. * Uses Map's insertion order for LRU-like behavior. */ function evictOldHistoryKeys(historyMap, maxKeys = MAX_HISTORY_KEYS) { if (historyMap.size <= maxKeys) return; const keysToDelete = historyMap.size - maxKeys; const iterator = historyMap.keys(); for (let i = 0; i < keysToDelete; i++) { const key = iterator.next().value; if (key !== void 0) historyMap.delete(key); } } /** Wraps previous chat history and the current message in the prompt context marker format. */ function buildHistoryContext(params) { const { historyText, currentMessage } = params; const lineBreak = params.lineBreak ?? "\n"; if (!historyText.trim()) return currentMessage; return [ HISTORY_CONTEXT_MARKER, historyText, "", CURRENT_MESSAGE_MARKER, currentMessage ].join(lineBreak); } /** Appends one history entry, enforces per-session limit, and refreshes LRU key order. */ function recordChannelHistoryEntry(params) { const { historyMap, historyKey, entry } = params; if (params.limit <= 0) return []; const history = historyMap.get(historyKey) ?? []; history.push(entry); const overflowCount = history.length - params.limit; if (overflowCount > 0) history.splice(0, overflowCount); if (historyMap.has(historyKey)) historyMap.delete(historyKey); historyMap.set(historyKey, history); evictOldHistoryKeys(historyMap); return history; } function recordChannelHistoryEntryIfEnabled(params) { if (!params.entry || params.limit <= 0) return []; return recordChannelHistoryEntry({ historyMap: params.historyMap, historyKey: params.historyKey, entry: params.entry, limit: params.limit }); } /** * @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).record(...)`. * This helper remains for core internals and older plugin compatibility. */ const recordPendingHistoryEntryIfEnabled = recordChannelHistoryEntryIfEnabled; const DEFAULT_HISTORY_MEDIA_LIMIT = 4; function isLocalHistoryMediaPath(path) { if (/^[a-z]:[\\/]/i.test(path)) return true; return !/^[a-z][a-z0-9+.-]*:/i.test(path); } function isImageHistoryMediaEntry(entry) { if (entry.kind && entry.kind !== "unknown") return entry.kind === "image" || entry.kind === "sticker"; return entry.contentType?.split(";")[0]?.trim().toLowerCase().startsWith("image/") === true; } /** Filters history media to local image entries safe to re-attach to prompt context. */ function normalizeHistoryMediaEntries(params) { const limit = Math.max(0, params.limit ?? DEFAULT_HISTORY_MEDIA_LIMIT); if (limit <= 0 || !params.media?.length) return []; const out = []; const seen = /* @__PURE__ */ new Set(); for (const entry of params.media) { if (!isImageHistoryMediaEntry(entry)) continue; const path = entry.path?.trim(); if (!path || !isLocalHistoryMediaPath(path)) continue; const dedupeKey = `${entry.messageId ?? params.messageId ?? ""}\0${path}`; if (seen.has(dedupeKey)) continue; seen.add(dedupeKey); out.push({ path, contentType: entry.contentType, kind: entry.kind === "sticker" ? "sticker" : "image", messageId: entry.messageId ?? params.messageId }); if (out.length >= limit) break; } return out; } async function recordChannelHistoryEntryWithMedia(params) { if (!params.entry || params.limit <= 0) return []; if (params.shouldRecord && !params.shouldRecord()) return []; if (typeof params.media === "function") { const recordedEntry = params.entry; const history = recordChannelHistoryEntry({ historyMap: params.historyMap, historyKey: params.historyKey, entry: recordedEntry, limit: params.limit }); const resolvedMedia = await params.media(); if (params.shouldRecord && !params.shouldRecord()) return history; const media = normalizeHistoryMediaEntries({ media: resolvedMedia, limit: params.mediaLimit, messageId: params.messageId ?? params.entry.messageId }); if (media.length === 0) return history; const currentHistory = params.historyMap.get(params.historyKey); const entryIndex = currentHistory?.indexOf(recordedEntry) ?? -1; if (currentHistory && entryIndex >= 0) currentHistory[entryIndex] = { ...recordedEntry, media }; return history; } const resolvedMedia = params.media ?? void 0; if (params.shouldRecord && !params.shouldRecord()) return []; const media = normalizeHistoryMediaEntries({ media: resolvedMedia, limit: params.mediaLimit, messageId: params.messageId ?? params.entry.messageId }); const entry = media.length > 0 ? { ...params.entry, media } : params.entry; return recordChannelHistoryEntry({ historyMap: params.historyMap, historyKey: params.historyKey, entry, limit: params.limit }); } /** * @deprecated Plugin message-turn code should use * `createChannelHistoryWindow(...).recordWithMedia(...)`. */ const recordPendingHistoryEntryWithMedia = recordChannelHistoryEntryWithMedia; function buildChannelPendingHistoryContext(params) { if (params.limit <= 0) return params.currentMessage; return buildHistoryContextFromEntries({ entries: params.historyMap.get(params.historyKey) ?? [], currentMessage: params.currentMessage, formatEntry: params.formatEntry, lineBreak: params.lineBreak, excludeLast: false }); } /** * @deprecated Plugin message-turn code should use * `createChannelHistoryWindow(...).buildPendingContext(...)`. */ const buildPendingHistoryContextFromMap = buildChannelPendingHistoryContext; function buildChannelInboundHistory(params) { return buildInboundHistoryFromEntries({ entries: params.historyMap.get(params.historyKey) ?? [], limit: params.limit }); } /** * @deprecated Plugin message-turn code should use * `createChannelHistoryWindow(...).buildInboundHistory(...)`. */ const buildInboundHistoryFromMap = buildChannelInboundHistory; /** Builds structured inbound history entries from an existing window. */ function buildInboundHistoryFromEntries(params) { if (params.limit <= 0) return; if (params.entries.length === 0) return []; return params.entries.slice(-params.limit).map((entry) => { const historyEntry = { sender: entry.sender, body: entry.body, timestamp: entry.timestamp }; if (entry.messageId) historyEntry.messageId = entry.messageId; if (entry.media && entry.media.length > 0) historyEntry.media = entry.media; return historyEntry; }); } /** * @deprecated Prefer `buildHistoryContextFromEntries(...)` for existing entry * arrays, or `createChannelHistoryWindow(...)` when working from a history map. * This helper remains for older plugin compatibility. */ function buildHistoryContextFromMap(params) { if (params.limit <= 0) return params.currentMessage; return buildHistoryContextFromEntries({ entries: params.entry ? recordChannelHistoryEntry({ historyMap: params.historyMap, historyKey: params.historyKey, entry: params.entry, limit: params.limit }) : params.historyMap.get(params.historyKey) ?? [], currentMessage: params.currentMessage, formatEntry: params.formatEntry, lineBreak: params.lineBreak, excludeLast: params.excludeLast }); } function clearChannelHistory(params) { params.historyMap.set(params.historyKey, []); } function clearChannelHistoryIfEnabled(params) { if (params.limit > 0) clearChannelHistory({ historyMap: params.historyMap, historyKey: params.historyKey }); } /** * @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).clear(...)`. * This helper remains for core internals and older plugin compatibility. */ const clearHistoryEntriesIfEnabled = clearChannelHistoryIfEnabled; /** Builds prompt text from already-recorded history entries. */ function buildHistoryContextFromEntries(params) { const lineBreak = params.lineBreak ?? "\n"; const entries = params.excludeLast === false ? params.entries : params.entries.slice(0, -1); if (entries.length === 0) return params.currentMessage; return buildHistoryContext({ historyText: entries.map(params.formatEntry).join(lineBreak), currentMessage: params.currentMessage, lineBreak }); } //#endregion export { recordChannelHistoryEntryWithMedia as _, buildChannelPendingHistoryContext as a, buildHistoryContextFromMap as c, buildPendingHistoryContextFromMap as d, clearChannelHistoryIfEnabled as f, recordChannelHistoryEntryIfEnabled as g, normalizeHistoryMediaEntries as h, buildChannelInboundHistory as i, buildInboundHistoryFromEntries as l, evictOldHistoryKeys as m, DEFAULT_GROUP_HISTORY_LIMIT as n, buildHistoryContext as o, clearHistoryEntriesIfEnabled as p, HISTORY_CONTEXT_MARKER as r, buildHistoryContextFromEntries as s, CURRENT_MESSAGE_MARKER as t, buildInboundHistoryFromMap as u, recordPendingHistoryEntryIfEnabled as v, recordPendingHistoryEntryWithMedia as y };