Files
openclaw/src/auto-reply/reply/inbound-dedupe.ts
2026-02-17 13:36:48 +09:00

56 lines
1.8 KiB
TypeScript

import { logVerbose, shouldLogVerbose } from "../../globals.js";
import { createDedupeCache, type DedupeCache } from "../../infra/dedupe.js";
import type { MsgContext } from "../templating.js";
const DEFAULT_INBOUND_DEDUPE_TTL_MS = 20 * 60_000;
const DEFAULT_INBOUND_DEDUPE_MAX = 5000;
const inboundDedupeCache = createDedupeCache({
ttlMs: DEFAULT_INBOUND_DEDUPE_TTL_MS,
maxSize: DEFAULT_INBOUND_DEDUPE_MAX,
});
const normalizeProvider = (value?: string | null) => value?.trim().toLowerCase() || "";
const resolveInboundPeerId = (ctx: MsgContext) =>
ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? ctx.SessionKey;
export function buildInboundDedupeKey(ctx: MsgContext): string | null {
const provider = normalizeProvider(ctx.OriginatingChannel ?? ctx.Provider ?? ctx.Surface);
const messageId = ctx.MessageSid?.trim();
if (!provider || !messageId) {
return null;
}
const peerId = resolveInboundPeerId(ctx);
if (!peerId) {
return null;
}
const sessionKey = ctx.SessionKey?.trim() ?? "";
const accountId = ctx.AccountId?.trim() ?? "";
const threadId =
ctx.MessageThreadId !== undefined && ctx.MessageThreadId !== null
? String(ctx.MessageThreadId)
: "";
return [provider, accountId, sessionKey, peerId, threadId, messageId].filter(Boolean).join("|");
}
export function shouldSkipDuplicateInbound(
ctx: MsgContext,
opts?: { cache?: DedupeCache; now?: number },
): boolean {
const key = buildInboundDedupeKey(ctx);
if (!key) {
return false;
}
const cache = opts?.cache ?? inboundDedupeCache;
const skipped = cache.check(key, opts?.now);
if (skipped && shouldLogVerbose()) {
logVerbose(`inbound dedupe: skipped ${key}`);
}
return skipped;
}
export function resetInboundDedupe(): void {
inboundDedupeCache.clear();
}