openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
79 lines (78 loc) • 4.02 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as hasEncodedFileUrlSeparator } from "./local-file-access-B1HKoQAc.js";
import { fileURLToPath } from "node:url";
import path from "node:path";
//#region src/agents/embedded-agent-helpers/messaging-dedupe.ts
/**
* Normalizes outbound message text to suppress duplicate send actions.
*/
const MIN_DUPLICATE_TEXT_LENGTH = 10;
const MIN_SUBSTRING_DUPLICATE_RATIO = .5;
/**
* Normalize text for duplicate comparison.
* - Trims whitespace
* - Lowercases
* - Strips emoji (Emoji_Presentation and Extended_Pictographic)
* - Collapses multiple spaces to single space
*/
function normalizeTextForComparison(text) {
return normalizeLowercaseStringOrEmpty(text).replace(/\p{Emoji_Presentation}|\p{Extended_Pictographic}/gu, "").replace(/\s+/g, " ").trim();
}
/** Compare already-normalized message text against prior sends. */
function isMessagingToolDuplicateNormalized(normalized, normalizedSentTexts) {
if (normalizedSentTexts.length === 0) return false;
if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) return false;
return normalizedSentTexts.some((normalizedSent) => {
if (!normalizedSent || normalizedSent.length < MIN_DUPLICATE_TEXT_LENGTH) return false;
if (normalized.includes(normalizedSent)) return normalizedSent.length >= normalized.length * MIN_SUBSTRING_DUPLICATE_RATIO;
return normalizedSent.includes(normalized) && normalized.length >= normalizedSent.length * MIN_SUBSTRING_DUPLICATE_RATIO;
});
}
/** Return true when raw message text duplicates a prior sent message. */
function isMessagingToolDuplicate(text, sentTexts) {
if (sentTexts.length === 0) return false;
const normalized = normalizeTextForComparison(text);
if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) return false;
return isMessagingToolDuplicateNormalized(normalized, sentTexts.map(normalizeTextForComparison));
}
//#endregion
//#region src/media/media-reference-comparison.ts
const PATH_PARENT_SEGMENT_RE = /(?:^|[\\/])\.\.(?:[\\/]|$)/u;
const FORWARD_NETWORK_PATH_PREFIX_RE = /^\/\//u;
const FILE_URL_PREFIX_RE = /^file:(?:\/\/)?/iu;
const FILE_URL_LOCAL_NETWORK_KEY_PREFIX = "\0file-url-local-network:";
function normalizeAbsoluteLocalPath(value) {
if (!path.isAbsolute(value) || PATH_PARENT_SEGMENT_RE.test(value) || FORWARD_NETWORK_PATH_PREFIX_RE.test(value)) return value;
return path.normalize(value);
}
function normalizeFileUrlLocalPath(value) {
if (!value.startsWith("//")) return normalizeAbsoluteLocalPath(value);
const normalized = PATH_PARENT_SEGMENT_RE.test(value) ? value : `//${path.normalize(value.slice(2))}`;
return `${FILE_URL_LOCAL_NETWORK_KEY_PREFIX}${normalized}`;
}
function normalizeMalformedLocalFileUrl(value) {
const remainder = value.replace(FILE_URL_PREFIX_RE, "");
let localPath;
if (remainder.startsWith("/")) localPath = remainder;
else if (/^localhost(?:\/|$)/iu.test(remainder)) localPath = remainder.slice(9);
else return;
if (process.platform === "win32" && /^\/[a-z]:[\\/]/iu.test(localPath)) localPath = localPath.slice(1);
return normalizeFileUrlLocalPath(localPath);
}
/** Canonicalizes equivalent local media references without resolving the filesystem. */
function normalizeMediaReferenceForComparison(value) {
const trimmed = value.trim();
if (!trimmed) return "";
if (!FILE_URL_PREFIX_RE.test(trimmed)) return normalizeAbsoluteLocalPath(trimmed);
try {
const parsed = new URL(trimmed);
if (parsed.protocol === "file:") {
if (hasEncodedFileUrlSeparator(parsed.pathname)) return trimmed;
const windows = process.platform === "win32" && (parsed.hostname !== "" || /^\/[a-z]:[\\/]/iu.test(parsed.pathname));
return normalizeFileUrlLocalPath(fileURLToPath(parsed, { windows }));
}
} catch {}
return normalizeMalformedLocalFileUrl(trimmed) ?? trimmed;
}
//#endregion
export { normalizeTextForComparison as i, isMessagingToolDuplicate as n, isMessagingToolDuplicateNormalized as r, normalizeMediaReferenceForComparison as t };