openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
193 lines (192 loc) • 8.15 kB
JavaScript
import { t as pruneMapToMaxSize } from "./map-size-BlrH_CnE.mjs";
//#region src/auto-reply/reply/history.ts
const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
const RECENT_HISTORY_CONTEXT_MARKER = "[Recent chat messages - for context]";
const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
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) {
pruneMapToMaxSize(historyMap, maxKeys);
}
/** 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 [
params.historyKind === "recent" ? RECENT_HISTORY_CONTEXT_MARKER : HISTORY_CONTEXT_MARKER,
historyText,
"",
CURRENT_MESSAGE_MARKER,
currentMessage
].join(lineBreak);
}
/** Appends one history entry, enforces per-session limit, and refreshes LRU key order. */
function recordChannelHistoryEntryIfEnabled(params) {
const { historyMap, historyKey, entry } = params;
if (!entry || 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;
}
/**
* @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 [];
const recordedEntry = params.entry;
const history = typeof params.media === "function" ? recordChannelHistoryEntryIfEnabled(params) : void 0;
const resolvedMedia = typeof params.media === "function" ? await params.media() : params.media;
if (params.shouldRecord && !params.shouldRecord()) return history ?? [];
const media = normalizeHistoryMediaEntries({
media: resolvedMedia,
limit: params.mediaLimit,
messageId: params.messageId ?? params.entry.messageId
});
if (history) {
const currentHistory = params.historyMap.get(params.historyKey);
const entryIndex = currentHistory?.indexOf(recordedEntry) ?? -1;
if (media.length > 0 && currentHistory && entryIndex >= 0) currentHistory[entryIndex] = {
...recordedEntry,
media
};
return history;
}
return recordChannelHistoryEntryIfEnabled({
...params,
entry: media.length > 0 ? {
...params.entry,
media
} : params.entry
});
}
/**
* @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;
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?.length) 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 ? recordChannelHistoryEntryIfEnabled({
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 clearChannelHistoryIfEnabled(params) {
if (params.limit > 0) params.historyMap.set(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,
historyKind: params.historyKind
});
}
//#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, HISTORY_CONTEXT_MARKER as n, buildHistoryContext as o, clearHistoryEntriesIfEnabled as p, RECENT_HISTORY_CONTEXT_MARKER as r, buildHistoryContextFromEntries as s, CURRENT_MESSAGE_MARKER as t, buildInboundHistoryFromMap as u, recordPendingHistoryEntryIfEnabled as v, recordPendingHistoryEntryWithMedia as y };