Files
openclaw/extensions/memory-core/src/tools.citations.ts
2026-03-27 02:49:33 +00:00

91 lines
2.4 KiB
TypeScript

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";
}