openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
239 lines (238 loc) • 11.8 kB
JavaScript
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as normalizeOptionalAccountId } from "./account-id-CETVCrTz.js";
import { t as normalizeAnyChannelId } from "./registry-normalize-CY8V_nbx.js";
import "./registry-COqNvmCg.js";
import { r as getLoadedChannelPluginForRead } from "./registry-loaded-Bh7xuMJh.js";
import { a as getReplyPayloadMetadata, i as copyReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js";
import { f as stringifyRouteThreadId, i as channelRouteTargetsMatchExact } from "./channel-route--2in3Ckg.js";
import { t as getChannelPlugin } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { o as hasReplyPayloadContent } from "./payload-DYE7bQ2k.js";
import "./embedded-agent-helpers-D0eiT3IT.js";
import { n as isMessagingToolDuplicate, t as normalizeMediaReferenceForComparison } from "./media-reference-comparison-DpA8NCy-.js";
//#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 normalizeThreadIdForComparison(value) {
return stringifyRouteThreadId(value);
}
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: normalizeThreadIdForComparison(params.targetThreadId)
});
return params.targetKey === params.originTarget;
}
function resolveOriginThreadIdForPayload(params) {
const originThreadId = normalizeThreadIdForComparison(params.originatingThreadId);
const replyToId = normalizeThreadIdForComparison(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 normalizeThreadIdForComparison(transport.threadId) ?? originThreadId;
if (transport?.threadId === null) return normalizeThreadIdForComparison(transport.replyToId);
return originThreadId;
}
/** 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 [];
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
});
return sentTexts.length === 0 ? payloads : payloads.filter((payload) => !isMessagingToolDuplicate(payload.text ?? "", sentTexts) || hasReplyPayloadContent({
...payload,
text: void 0
}, { extraContent: hasEnabledDeliveryOperation(payload) || payload.location != null }));
};
return params.normalizeSentMediaUrls ? params.normalizeSentMediaUrls(sentMediaUrls).then(filterPayload) : filterPayload(sentMediaUrls);
}
/** True when a message-tool send visibly delivered to the source conversation.
* Route matching keeps cross-provider or unrelated-target tool sends from
* counting as the source reply. */
function hasSourceRoutedMessagingToolDelivery(params) {
const decision = resolveMessagingToolPayloadDedupe(params);
if (!decision.matchingRoute) return false;
return decision.routeSentTexts.length > 0 || decision.routeSentMediaUrls.length > 0 || decision.useGlobalSentTextEvidenceFallback && (params.messagingToolSentTexts?.length ?? 0) > 0 || decision.useGlobalSentMediaUrlEvidenceFallback && (params.messagingToolSentMediaUrls?.length ?? 0) > 0;
}
//#endregion
export { resolveMessagingToolPayloadDedupe as a, hasSourceRoutedMessagingToolDelivery as i, filterMessagingToolReplyPayload as n, shouldDedupeMessagingToolRepliesForRoute as o, hasEnabledDeliveryOperation as r, filterMessagingToolMediaDuplicates as t };