"use strict"; const SKILL_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*)\)/g; const LEADING_SKILL_LINK_RE = /^\s*\[\$([^\]\n]+)\]\(([^)\n]*)\)\s*/; const TRUNCATED_LEADING_SKILL_LINK_RE = /^\s*\[\$([^\]\n]+)\]\([^)\n]*$/; const LEADING_SKILL_TOKEN_RE = /^\s*\$[A-Za-z][\w:.-]*(?:\s+|$)/; function normalizeInlineText(text) { return String(text || "") .replace(/\s+/g, " ") .trim(); } function cleanSkillLinks(text) { return String(text || "").replace(SKILL_LINK_RE, (_m, name) => `$${String(name || "").trim()}`); } function stripLeadingSkillMentions(text) { let out = cleanSkillLinks(text); let changed = true; while (changed) { changed = false; const before = out; out = out.replace(LEADING_SKILL_LINK_RE, ""); out = out.replace(TRUNCATED_LEADING_SKILL_LINK_RE, ""); out = out.replace(LEADING_SKILL_TOKEN_RE, ""); out = out.replace(/^\s*[::,,;;||\-–—]+\s*/, ""); changed = out !== before; } return out; } function cleanUserVisibleText(text) { return cleanSkillLinks(text); } // 某些中转模型会把内部思考误当成普通正文,用 XML 风格标签直接输出。 // 只清理明确的内部块,不改动普通文本中的尖括号内容。 function stripInternalThoughtBlocks(text) { let out = String(text || ""); for (const name of ["thinking", "analysis", "reasoning"]) { const re = new RegExp("<" + name + "\\b[^>]*>[\\s\\S]*?", "gi"); out = out.replace(re, ""); } return out.replace(/<\/?(?:thinking|analysis|reasoning)\b[^>]*>/gi, ""); } // 流式内容可能把标签拆在多个 chunk 中:未闭合的内部块整体暂不输出。 function stripInternalThoughtStreaming(text) { const source = String(text || ""); let out = ""; let cursor = 0; const openRe = /<(thinking|analysis|reasoning)\b[^>]*>/gi; while (true) { openRe.lastIndex = cursor; const open = openRe.exec(source); if (!open) { out += source.slice(cursor); break; } out += source.slice(cursor, open.index); const closeRe = new RegExp("", "i"); const tail = source.slice(openRe.lastIndex); const close = closeRe.exec(tail); if (!close) break; cursor = openRe.lastIndex + close.index + close[0].length; } return stripInternalThoughtBlocks(out); } function cleanThreadTitle(title, fallback = "", maxLen = 80) { const primary = normalizeInlineText(stripLeadingSkillMentions(title)); const secondary = normalizeInlineText(stripLeadingSkillMentions(fallback)); const cleaned = primary || secondary || normalizeInlineText(cleanSkillLinks(title)) || normalizeInlineText(cleanSkillLinks(fallback)); return cleaned ? cleaned.slice(0, maxLen) : ""; } function looksLikeSkillMentionTitle(title) { const text = normalizeInlineText(title); return /^\[\$[^\]]+\]\(/.test(text) || /^\$[A-Za-z][\w:.-]*(?:\s|$)/.test(text); } module.exports = { cleanSkillLinks, cleanThreadTitle, cleanUserVisibleText, stripInternalThoughtBlocks, stripInternalThoughtStreaming, looksLikeSkillMentionTitle, };