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 = typeof ctx.MessageThreadId === "number" ? 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(); }