UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

183 lines (182 loc) 8.19 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { r as normalizeOptionalAccountId } from "./account-id-Df9e41E6.js"; import { a as normalizeAnyChannelId } from "./registry-9YoS2BJP.js"; import { h as stringifyRouteThreadId, o as channelRouteTargetsMatchExact } from "./channel-route-D7X5briz.js"; import { t as getChannelPlugin } from "./registry-MkxNn1Ue.js"; import { t as getLoadedChannelPluginForRead } from "./registry-loaded-read-7phT5GIn.js"; import "./plugins-t2ejWcVy.js"; import { a as isMessagingToolDuplicate } from "./embedded-agent-helpers-7sfpu1OT.js"; //#region src/auto-reply/reply/reply-payloads-dedupe.ts /** De-duplicates assistant reply payloads against message-tool sends on the same route. */ /** Removes text payloads already sent by message tools. */ function filterMessagingToolDuplicates(params) { const { payloads, sentTexts } = params; if (sentTexts.length === 0) return payloads; return payloads.filter((payload) => { if (payload.mediaUrl || payload.mediaUrls?.length) return true; return !isMessagingToolDuplicate(payload.text ?? "", sentTexts); }); } /** Removes media payload URLs already sent by message tools. */ function filterMessagingToolMediaDuplicates(params) { const { payloads, sentMediaUrls } = params; if (sentMediaUrls.length === 0) return payloads; const sentSet = /* @__PURE__ */ new Set(); for (const sentMediaUrl of sentMediaUrls) { const normalized = normalizeMediaForDedupe(sentMediaUrl); if (normalized) sentSet.add(normalized); } if (sentSet.size === 0) return payloads; let nextPayloads; for (let index = 0; index < payloads.length; index++) { const payload = payloads[index]; const mediaUrl = payload.mediaUrl; const mediaUrls = payload.mediaUrls; const stripSingle = mediaUrl && sentSet.has(normalizeMediaForDedupe(mediaUrl)); let filteredUrls; let strippedMediaUrls = false; if (mediaUrls?.length) for (let mediaIndex = 0; mediaIndex < mediaUrls.length; mediaIndex++) { const url = mediaUrls[mediaIndex]; if (sentSet.has(normalizeMediaForDedupe(url))) { strippedMediaUrls = true; if (!filteredUrls) filteredUrls = mediaUrls.slice(0, mediaIndex); continue; } if (filteredUrls) filteredUrls.push(url); } if (!stripSingle && !strippedMediaUrls) { if (nextPayloads) nextPayloads.push(payload); continue; } const nextPayload = Object.assign({}, payload, { mediaUrl: stripSingle ? void 0 : mediaUrl, mediaUrls: filteredUrls?.length ? filteredUrls : void 0 }); if (!nextPayloads) nextPayloads = payloads.slice(0, index); nextPayloads.push(nextPayload); } return nextPayloads ?? payloads; } function normalizeMediaForDedupe(value) { const trimmed = value.trim(); if (!trimmed) return ""; if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("file://")) return trimmed; try { const parsed = new URL(trimmed); if (parsed.protocol === "file:") return decodeURIComponent(parsed.pathname || ""); } catch {} return trimmed.replace(/^file:\/\//i, ""); } function normalizeProviderForComparison(value) { const trimmed = normalizeOptionalString(value); if (!trimmed) return; const lowered = normalizeLowercaseStringOrEmpty(trimmed); const normalizedChannel = normalizeAnyChannelId(trimmed); if (normalizedChannel) return normalizedChannel; return lowered; } function normalizeThreadIdForComparison(value) { return stringifyRouteThreadId(value); } function normalizeTargetForDedupe(provider, rawTarget) { const fallback = normalizeOptionalString(rawTarget); if (!fallback) return; const providerId = normalizeProviderForComparison(provider); return normalizeOptionalString((providerId ? getLoadedChannelPluginForRead(providerId)?.messaging?.normalizeTarget : void 0)?.(rawTarget ?? "") ?? fallback); } function resolveTargetProviderForComparison(params) { const targetProvider = normalizeProviderForComparison(params.targetProvider); if (!targetProvider || targetProvider === "message") return params.currentProvider; return targetProvider; } function normalizeRouteTargetForDedupe(params) { const to = normalizeTargetForDedupe(params.provider, params.rawTarget); if (!to) return null; return { channel: params.provider, to, ...params.accountId ? { accountId: params.accountId } : {}, ...params.threadId != null ? { threadId: params.threadId } : {} }; } function targetsMatchForDedupe(params) { const pluginMatch = getChannelPlugin(params.provider)?.outbound?.targetsMatchForReplySuppression; if (pluginMatch) return pluginMatch({ originTarget: params.originTarget, targetKey: params.targetKey, targetThreadId: normalizeThreadIdForComparison(params.targetThreadId) }); return params.targetKey === params.originTarget; } /** Returns true when message-tool route evidence says source replies should be deduped. */ function shouldDedupeMessagingToolRepliesForRoute(params) { return getMatchingMessagingToolReplyTargets(params).length > 0; } /** Finds message-tool sends that target the same channel/account/thread as the source reply. */ function getMatchingMessagingToolReplyTargets(params) { const provider = normalizeProviderForComparison(params.messageProvider); if (!provider) return []; const originRawTarget = normalizeOptionalString(params.originatingTo); const originAccount = normalizeOptionalAccountId(params.accountId); const sentTargets = params.messagingToolSentTargets ?? []; if (sentTargets.length === 0) return []; return sentTargets.filter((target) => { const targetProvider = resolveTargetProviderForComparison({ currentProvider: provider, targetProvider: target?.provider }); if (targetProvider !== provider) return false; const targetAccount = normalizeOptionalAccountId(target.accountId); if (originAccount && targetAccount && originAccount !== targetAccount) return false; const targetRaw = normalizeOptionalString(target.to); const routeAccount = originAccount ?? targetAccount; const originRoute = normalizeRouteTargetForDedupe({ provider, rawTarget: originRawTarget, accountId: routeAccount }); if (!originRoute) return false; const targetRoute = normalizeRouteTargetForDedupe({ provider: targetProvider, rawTarget: targetRaw, accountId: routeAccount, threadId: target.threadId }); if (!targetRoute) return false; if (channelRouteTargetsMatchExact({ left: originRoute, right: targetRoute })) return true; return targetsMatchForDedupe({ provider, originTarget: originRoute.to, targetKey: targetRoute.to, targetThreadId: target.threadId }); }); } /** Resolves whether and how to dedupe final payloads against message-tool sends. */ function resolveMessagingToolPayloadDedupe(params) { const sentTargets = params.messagingToolSentTargets ?? []; const matchingTargets = getMatchingMessagingToolReplyTargets({ messageProvider: params.messageProvider, messagingToolSentTargets: sentTargets, originatingTo: params.originatingTo, accountId: params.accountId }); const matchingRoute = matchingTargets.length > 0; const routeSentTexts = matchingTargets.flatMap((target) => typeof target.text === "string" && target.text.trim() ? [target.text] : []); const routeSentMediaUrls = matchingTargets.flatMap((target) => Array.isArray(target.mediaUrls) ? target.mediaUrls.filter((url) => typeof url === "string" && Boolean(url.trim())) : []); const hasTargetTextEvidence = sentTargets.some((target) => typeof target.text === "string" && Boolean(target.text.trim())); const hasTargetMediaUrlEvidence = sentTargets.some((target) => Array.isArray(target.mediaUrls) && target.mediaUrls.some((url) => typeof url === "string" && Boolean(url.trim()))); return { shouldDedupePayloads: matchingRoute || sentTargets.length === 0, matchingRoute, routeSentTexts, routeSentMediaUrls, useGlobalSentTextEvidenceFallback: matchingRoute && !hasTargetTextEvidence, useGlobalSentMediaUrlEvidenceFallback: matchingRoute && !hasTargetMediaUrlEvidence }; } //#endregion export { shouldDedupeMessagingToolRepliesForRoute as i, filterMessagingToolMediaDuplicates as n, resolveMessagingToolPayloadDedupe as r, filterMessagingToolDuplicates as t };