56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import type { AgentMentionInfo } from '../types';
|
|||
|
|
import { buildAgentHref, buildSkillHref } from '@/lib/messages/inlineMessageLinks';
|
||
|
|
|
||
|
|
export const SKILL_TOKEN_PATTERN = /(^|\s)\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)/g;
|
||
|
|
|
||
|
|
const FENCED_CODE_SEGMENT_PATTERN = /(```[\s\S]*?```|~~~[\s\S]*?~~~)/g;
|
||
|
|
|
||
|
|
const escapeHtml = (text: string): string => {
|
||
|
|
return text
|
||
|
|
.replace(/&/g, '&')
|
||
|
|
.replace(/</g, '<')
|
||
|
|
.replace(/>/g, '>')
|
||
|
|
.replace(/"/g, '"')
|
||
|
|
.replace(/'/g, ''');
|
||
|
|
};
|
||
|
|
|
||
|
|
const mapNonFencedSegments = (markdown: string, mapSegment: (segment: string) => string): string => {
|
||
|
|
return markdown
|
||
|
|
.split(FENCED_CODE_SEGMENT_PATTERN)
|
||
|
|
.map((segment, index) => (index % 2 === 1 ? segment : mapSegment(segment)))
|
||
|
|
.join('');
|
||
|
|
};
|
||
|
|
|
||
|
|
// In Markdown a single "\n" is a soft break (rendered as a space). Users type plain
|
||
|
|
// text where each newline is meant literally, so convert soft breaks into hard breaks
|
||
|
|
// (two trailing spaces) outside of fenced code blocks, where newlines are already literal.
|
||
|
|
const applyHardLineBreaks = (markdown: string): string => {
|
||
|
|
return mapNonFencedSegments(markdown, (segment) => segment.replace(/ *\n/g, ' \n'));
|
||
|
|
};
|
||
|
|
|
||
|
|
export const prepareUserMarkdownContent = ({
|
||
|
|
textContent,
|
||
|
|
agentMention,
|
||
|
|
skillNames,
|
||
|
|
}: {
|
||
|
|
textContent: string;
|
||
|
|
agentMention?: AgentMentionInfo;
|
||
|
|
skillNames: ReadonlySet<string>;
|
||
|
|
}): string => {
|
||
|
|
let content = mapNonFencedSegments(textContent, escapeHtml);
|
||
|
|
|
||
|
|
// Insert agent mention links with an internal href so markdown renders them as mentions, not external links.
|
||
|
|
if (agentMention?.token && content.includes(agentMention.token)) {
|
||
|
|
const mentionMarkdown = `[${agentMention.token}](${buildAgentHref(agentMention.name)})`;
|
||
|
|
content = content.replace(agentMention.token, mentionMarkdown);
|
||
|
|
}
|
||
|
|
|
||
|
|
content = content.replace(SKILL_TOKEN_PATTERN, (match, prefix: string, skillName: string) => {
|
||
|
|
if (!skillNames.has(skillName)) return match;
|
||
|
|
return `${prefix}[/${skillName}](${buildSkillHref(skillName)})`;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Preserve user newlines (markdown soft breaks would otherwise collapse to spaces)
|
||
|
|
return applyHardLineBreaks(content);
|
||
|
|
};
|