openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
228 lines (227 loc) • 11.2 kB
JavaScript
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-6kR-86XC.mjs";
import { r as normalizeOptionalAccountId } from "./account-id-CW6dlabA.mjs";
import "./registry-Cu3BYvNI.mjs";
import { t as normalizeAnyChannelId } from "./registry-normalize-BnQO61Jh.mjs";
import { i as getLoadedChannelPluginForRead } from "./registry-loaded-CWeoGJWS.mjs";
import { o as hasReplyPayloadContent } from "./payload-B9AxxNvV.mjs";
import { _ as isReplyPayloadTerminalContent, a as copyReplyPayloadMetadata, s as getReplyPayloadMetadata } from "./reply-payload-DBtQZ6PK.mjs";
import { t as normalizeMediaReferenceForComparison } from "./media-reference-comparison-D61D3Ct7.mjs";
import { f as stringifyRouteThreadId, i as channelRouteTargetsMatchExact } from "./channel-route-BIXHeyAA.mjs";
import { t as getChannelPlugin } from "./registry-B56V4gqI.mjs";
import "./plugins-CBMHLsl7.mjs";
import { n as normalizeReplyPayload } from "./normalize-reply-XuQItfL2.mjs";
import "./embedded-agent-helpers-DKuJhdd0.mjs";
import { t as isMessagingToolDuplicate } from "./messaging-dedupe-BmhhMVBf.mjs";
//#region src/auto-reply/reply/reply-payloads-dedupe.ts
/** De-duplicates assistant reply payloads against message-tool sends on the same route. */
/** 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 = normalizeMediaReferenceForComparison(sentMediaUrl);
if (normalized) sentSet.add(normalized);
}
if (sentSet.size === 0) return payloads;
let nextPayloads;
for (const [index, payload] of payloads.entries()) {
if (hasEnabledDeliveryOperation(payload)) {
if (nextPayloads) nextPayloads.push(payload);
continue;
}
const mediaUrl = payload.mediaUrl;
const mediaUrls = payload.mediaUrls;
const stripSingle = mediaUrl && sentSet.has(normalizeMediaReferenceForComparison(mediaUrl));
let filteredUrls;
let strippedMediaUrls = false;
if (mediaUrls?.length) for (const [mediaIndex, url] of mediaUrls.entries()) {
if (sentSet.has(normalizeMediaReferenceForComparison(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 nextMediaUrl = stripSingle ? void 0 : mediaUrl;
const nextMediaUrls = strippedMediaUrls ? filteredUrls : mediaUrls;
const nextPayload = copyReplyPayloadMetadata(payload, {
...payload,
mediaUrl: nextMediaUrl,
mediaUrls: nextMediaUrls?.length ? nextMediaUrls : void 0,
...payload.audioAsVoice === true && !nextMediaUrl && !nextMediaUrls?.length ? { audioAsVoice: void 0 } : {}
});
if (!nextPayloads) nextPayloads = payloads.slice(0, index);
nextPayloads.push(nextPayload);
}
return nextPayloads ?? payloads;
}
function hasEnabledDeliveryOperation(payload) {
const pin = payload.delivery?.pin;
return pin === true || typeof pin === "object" && pin.enabled;
}
function normalizeProviderForComparison(value) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return;
return normalizeAnyChannelId(trimmed) || normalizeLowercaseStringOrEmpty(trimmed);
}
function normalizeTargetForDedupe(provider, rawTarget) {
const fallback = normalizeOptionalString(rawTarget);
if (!fallback) return;
const providerId = normalizeProviderForComparison(provider);
const normalizer = providerId ? getLoadedChannelPluginForRead(providerId)?.messaging?.normalizeTarget : void 0;
return normalizeOptionalString(normalizer?.(rawTarget ?? "") ?? fallback);
}
function resolveTargetProviderForComparison(params) {
const targetProvider = normalizeProviderForComparison(params.targetProvider);
return targetProvider && targetProvider !== "message" ? targetProvider : params.currentProvider;
}
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: stringifyRouteThreadId(params.targetThreadId)
});
return params.targetKey === params.originTarget;
}
function resolveOriginThreadIdForPayload(params) {
const originThreadId = stringifyRouteThreadId(params.originatingThreadId);
const replyToId = stringifyRouteThreadId(params.replyToId);
const resolveReplyTransport = getChannelPlugin(params.provider)?.threading?.resolveReplyTransport;
if (!params.config || !resolveReplyTransport) return originThreadId;
const transport = resolveReplyTransport({
cfg: params.config,
accountId: params.accountId,
threadId: originThreadId,
replyToId,
replyToIsExplicit: params.replyToIsExplicit,
replyToCurrent: params.replyToCurrent,
replyDelivery: params.replyDelivery
});
if (transport?.threadId != null) return stringifyRouteThreadId(transport.threadId) ?? originThreadId;
if (transport?.threadId === null) return stringifyRouteThreadId(transport.replyToId);
return originThreadId;
}
/** 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 [];
const originThreadId = resolveOriginThreadIdForPayload({
provider,
config: params.config,
accountId: originAccount,
originatingThreadId: params.originatingThreadId,
replyToId: params.replyToId,
replyToIsExplicit: params.replyToIsExplicit,
replyToCurrent: params.replyToCurrent,
replyDelivery: params.replyDelivery
});
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,
threadId: originThreadId
});
if (!originRoute) return false;
const targetRoute = normalizeRouteTargetForDedupe({
provider: targetProvider,
rawTarget: targetRaw,
accountId: routeAccount,
threadId: target.threadId ?? (target.threadImplicit ? originThreadId : void 0)
});
if (!targetRoute) return false;
if (channelRouteTargetsMatchExact({
left: originRoute,
right: targetRoute
})) return true;
if (!Boolean(getChannelPlugin(provider)?.outbound?.targetsMatchForReplySuppression) && (originRoute.threadId != null || targetRoute.threadId != null)) return false;
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({
...params,
messagingToolSentTargets: sentTargets
});
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())));
const allTargetsMatchRoute = matchingRoute && matchingTargets.length === sentTargets.length;
return {
shouldDedupePayloads: matchingRoute || sentTargets.length === 0,
matchingRoute,
routeSentTexts,
routeSentMediaUrls,
useGlobalSentTextEvidenceFallback: allTargetsMatchRoute && !hasTargetTextEvidence,
useGlobalSentMediaUrlEvidenceFallback: allTargetsMatchRoute && !hasTargetMediaUrlEvidence
};
}
function filterMessagingToolReplyPayload(params) {
const metadata = getReplyPayloadMetadata(params.payload);
const decision = resolveMessagingToolPayloadDedupe({
...params,
replyToId: params.payload.replyToId,
replyToIsExplicit: Boolean(metadata?.replyToIdExplicit || params.payload.replyToTag || params.payload.replyToCurrent),
replyToCurrent: params.payload.replyToCurrent,
replyDelivery: metadata?.replyDelivery
});
if (!decision.shouldDedupePayloads) {
const payloads = [params.payload];
return params.normalizeSentMediaUrls ? Promise.resolve(payloads) : payloads;
}
const sentMediaUrls = decision.matchingRoute && !decision.useGlobalSentMediaUrlEvidenceFallback ? decision.routeSentMediaUrls : params.sentMediaUrls ?? [];
const sentTexts = decision.matchingRoute && !decision.useGlobalSentTextEvidenceFallback ? decision.routeSentTexts : params.sentTexts ?? [];
const filterPayload = (normalizedSentMediaUrls) => {
const payloads = filterMessagingToolMediaDuplicates({
payloads: [params.payload],
sentMediaUrls: normalizedSentMediaUrls
});
const remaining = sentTexts.length === 0 ? payloads : payloads.filter((payload) => !isMessagingToolDuplicate(payload.text ?? "", sentTexts) || hasReplyPayloadContent({
...payload,
text: void 0
}, { extraContent: hasEnabledDeliveryOperation(payload) || payload.location != null }));
if (params.onDeliveredTerminalDuplicate && decision.matchingRoute && remaining.length === 0 && isReplyPayloadTerminalContent(params.payload) && normalizeReplyPayload(params.payload, { applyChannelTransforms: false }) !== null) params.onDeliveredTerminalDuplicate();
return remaining;
};
return params.normalizeSentMediaUrls ? params.normalizeSentMediaUrls(sentMediaUrls).then(filterPayload) : filterPayload(sentMediaUrls);
}
//#endregion
export { resolveMessagingToolPayloadDedupe as i, filterMessagingToolReplyPayload as n, hasEnabledDeliveryOperation as r, filterMessagingToolMediaDuplicates as t };