import { parseAgentSessionKey, type MemoryCitationsMode, type OpenClawConfig, } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files"; export function resolveMemoryCitationsMode(cfg: OpenClawConfig): MemoryCitationsMode { const mode = cfg.memory?.citations; if (mode === "on" || mode === "off" || mode === "auto") { return mode; } return "auto"; } export function decorateCitations( results: MemorySearchResult[], include: boolean, ): MemorySearchResult[] { if (!include) { return results.map((entry) => ({ ...entry, citation: undefined })); } return results.map((entry) => { const citation = formatCitation(entry); const snippet = `${entry.snippet.trim()}\n\nSource: ${citation}`; return { ...entry, citation, snippet }; }); } function formatCitation(entry: MemorySearchResult): string { const lineRange = entry.startLine === entry.endLine ? `#L${entry.startLine}` : `#L${entry.startLine}-L${entry.endLine}`; return `${entry.path}${lineRange}`; } export function clampResultsByInjectedChars( results: MemorySearchResult[], budget?: number, ): MemorySearchResult[] { if (!budget || budget <= 0) { return results; } let remaining = budget; const clamped: MemorySearchResult[] = []; for (const entry of results) { if (remaining <= 0) { break; } const snippet = entry.snippet ?? ""; if (snippet.length <= remaining) { clamped.push(entry); remaining -= snippet.length; } else { const trimmed = snippet.slice(0, Math.max(0, remaining)); clamped.push({ ...entry, snippet: trimmed }); break; } } return clamped; } export function shouldIncludeCitations(params: { mode: MemoryCitationsMode; sessionKey?: string; }): boolean { if (params.mode === "on") { return true; } if (params.mode === "off") { return false; } return deriveChatTypeFromSessionKey(params.sessionKey) === "direct"; } function deriveChatTypeFromSessionKey(sessionKey?: string): "direct" | "group" | "channel" { const parsed = parseAgentSessionKey(sessionKey); if (!parsed?.rest) { return "direct"; } const tokens = new Set(parsed.rest.toLowerCase().split(":").filter(Boolean)); if (tokens.has("channel")) { return "channel"; } if (tokens.has("group")) { return "group"; } return "direct"; }