openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
52 lines (51 loc) • 3.33 kB
JavaScript
import { t as findCodeRegions } from "./code-regions-BXMpZZLd.js";
import { n as flattenMarkdownDetails, t as stripInternalRuntimeScaffolding } from "./protocol-scaffolding-3qpaR6Vn.js";
//#region src/infra/outbound/sanitize-text.ts
const HTML_TAG_RE = /<\/?[a-z][a-z0-9_.:-]*(?=[\s/>])[^>]*>/gi;
const LABELED_ANGLE_LINK_RE = /<(?:https?:\/\/|mailto:)[^<>\s|]+\|([^<>\r\n|]*[^<>\s|][^<>\r\n|]*)>/gi;
const MAY_CONTAIN_MARKDOWN_CODE_RE = /[`~]|\t| {4}/;
const CODE_ESCAPE = "\0e";
const CODE_PLACEHOLDER = "\0p";
const CONVERTIBLE_HTML_OPEN_TAG_RE = /<(b|strong|i|em|s|strike|del|code|h[1-6]|li|p|div)(?=\s|>)(?:[^"'<>]|"[^"]*"|'[^']*')*>/gi;
const EMPTY_HTML_ELEMENT_RE = /<((?!(?:br|p|div)(?=[\s>]))[a-z][a-z0-9_.:-]*)(?=[\s>])(?:[^"'<>]|"[^"]*"|'[^']*')*>(?:[^\S\r\n\u2028\u2029]|<(?!\/?(?:br|p|div)(?=[\s/>]))\/?[a-z][a-z0-9_.:-]*(?=[\s/>])(?:[^"'<>]|"[^"]*"|'[^']*')*>)*<\/\1\s*>/gi;
function removeMatchesUntilStable(text, pattern) {
let previous;
let current = text;
do {
previous = current;
current = current.replace(pattern, "");
} while (current !== previous);
return current;
}
function convertHtmlOutsideCode(text, options) {
const boldMarker = options.style === "markdown" ? "**" : "*";
const strikeMarker = options.style === "markdown" ? "~~" : "~";
return removeMatchesUntilStable(removeMatchesUntilStable(text.replace(/<((?:https?:\/\/|mailto:)[^<>\s|]+)>/gi, "$1").replace(LABELED_ANGLE_LINK_RE, "$1").replace(CONVERTIBLE_HTML_OPEN_TAG_RE, "<$1>"), EMPTY_HTML_ELEMENT_RE).replace(/<br\s*\/?>/gi, "\n").replace(/<\/?(p|div)>/gi, "\n").replace(/<(b|strong)>(.*?)<\/\1>/gi, `${boldMarker}$2${boldMarker}`).replace(/<(i|em)>(.*?)<\/\1>/gi, "_$2_").replace(/<(s|strike|del)>(.*?)<\/\1>/gi, `${strikeMarker}$2${strikeMarker}`).replace(/<code>(.*?)<\/code>/gi, "`$1`").replace(/<h[1-6]>(.*?)<\/h[1-6]>/gi, `\n${boldMarker}$1${boldMarker}\n`).replace(/<li>(.*?)<\/li>/gi, "• $1\n"), HTML_TAG_RE).replace(/\n{3,}/g, "\n\n");
}
/**
* Convert common HTML tags to their plain-text/lightweight-markup equivalents
* and strip anything that remains.
*
* The function is intentionally conservative — it only targets tags that models
* are known to produce and avoids false positives on angle brackets in normal
* prose (e.g. `a < b`), in fenced blocks, and in inline code spans.
*/
function sanitizeForPlainText(text, options = {}) {
const prepared = flattenMarkdownDetails(stripInternalRuntimeScaffolding(text));
const codeRegions = (prepared.includes("<") || prepared.includes("\n\n\n")) && MAY_CONTAIN_MARKDOWN_CODE_RE.test(prepared) ? findCodeRegions(prepared) : [];
if (codeRegions.length === 0) return convertHtmlOutsideCode(prepared, options);
const preservedText = /* @__PURE__ */ new Map([[CODE_ESCAPE, "\0"]]);
let masked = "";
let cursor = 0;
for (const region of codeRegions) {
masked += prepared.slice(cursor, region.start).replaceAll("\0", CODE_ESCAPE);
const placeholder = `${CODE_PLACEHOLDER}${preservedText.size};`;
masked += placeholder;
preservedText.set(placeholder, prepared.slice(region.start, region.end));
cursor = region.end;
}
masked += prepared.slice(cursor).replaceAll("\0", CODE_ESCAPE);
return convertHtmlOutsideCode(masked, options).replace(/\u0000(?:e|p\d+;)/g, (marker) => preservedText.get(marker) ?? marker);
}
//#endregion
export { sanitizeForPlainText as t };