export type EchoTracker = { rememberText: ( text: string | undefined, opts: { combinedBody?: string; combinedBodySessionKey?: string; logVerboseMessage?: boolean; }, ) => void; has: (key: string) => boolean; forget: (key: string) => void; buildCombinedKey: (params: { sessionKey: string; combinedBody: string }) => string; }; export function createEchoTracker(params: { maxItems?: number; logVerbose?: (msg: string) => void; }): EchoTracker { const recentlySent = new Set(); const maxItems = Math.max(1, params.maxItems ?? 100); const buildCombinedKey = (p: { sessionKey: string; combinedBody: string }) => `combined:${p.sessionKey}:${p.combinedBody}`; const trim = () => { while (recentlySent.size > maxItems) { const firstKey = recentlySent.values().next().value as string | undefined; if (!firstKey) break; recentlySent.delete(firstKey); } }; const rememberText: EchoTracker["rememberText"] = (text, opts) => { if (!text) return; recentlySent.add(text); if (opts.combinedBody && opts.combinedBodySessionKey) { recentlySent.add( buildCombinedKey({ sessionKey: opts.combinedBodySessionKey, combinedBody: opts.combinedBody, }), ); } if (opts.logVerboseMessage) { params.logVerbose?.( `Added to echo detection set (size now: ${recentlySent.size}): ${text.substring(0, 50)}...`, ); } trim(); }; return { rememberText, has: (key) => recentlySent.has(key), forget: (key) => { recentlySent.delete(key); }, buildCombinedKey, }; }