UNPKG

@earendil-works/pi-coding-agent

Version:

Coding agent CLI with read, bash, edit, write tools and session management

80 lines • 4.1 kB
import { getCapabilities, Marked } from "@earendil-works/pi-tui"; import { render, toAnsi } from "lovely-mermaid"; const markdownParser = new Marked(); function isMermaid(token) { return token.type === "code" && token.lang?.trim().split(/\s+/, 1)[0]?.toLowerCase() === "mermaid"; } function codeSpan(line) { // Encode each diagram row as inline code (` ... `) so Markdown preserves its spacing and // box-drawing characters. Use a non-breaking space for blank rows because an // empty code span has no visible height. const content = line || "\u00a0"; // CommonMark code spans use matching backtick delimiters, so choose one // longer than any backtick run in the content (``hel`lo`` -> <code>hel`lo</code>). // If the content starts or ends with a backtick, separating it from the // delimiter with a space keeps that backtick as content; CommonMark removes // the padding when rendering (`` `edge` `` -> <code>`edge`</code>). // Mermaid labels can preserve backticks, for example: // `┌──────────────┐ ┌──────────────┐` // ```│ plain ` tick ├───▶│ two `` ticks │``` // `└──────────────┘ └──────────────┘` const longestBacktickRun = Math.max(0, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length)); const fence = "`".repeat(longestBacktickRun + 1); const padding = content.startsWith("`") || content.endsWith("`") ? " " : ""; return `${fence}${padding}${content}${padding}${fence}`; } function themedLines(art, theme) { const foreground = (color) => theme.getFgAnsi(color).slice(2, -1); const ansiTheme = { border: foreground("borderMuted"), text: foreground("text"), edge: foreground("accent"), edgeLabel: foreground("text"), title: `1;${foreground("accent")}`, }; const linkLabels = getCapabilities().hyperlinks; const displayArt = { ...art, // Only osc 8 the text, not the whole box. styled: art.styled.map((row) => row.map((span) => { if (span.href === undefined || (linkLabels && span.role === "text")) return span; const unlinked = { ...span }; delete unlinked.href; return unlinked; })), }; return toAnsi(displayArt, ansiTheme); } /** Create a transformer that replaces top-level Mermaid code blocks with Unicode terminal diagrams. */ export function createMermaidMarkdownTransformer(options) { return (markdown, context) => { const mode = options.getMode(); // Diagrams render in user and assistant text and in model-written summaries, not in thinking or other surfaces. if (mode === "off" || (context.messageType !== "user" && context.messageType !== "assistant" && context.messageType !== "summary") || (context.isStreaming && mode !== "streaming")) { return markdown; } return markdownParser .lexer(markdown) .map((token) => { if (!isMermaid(token)) return token.raw; const art = render(token.text); if (!art || art.width > context.availableWidth) return token.raw; if (!context.isStreaming && art.warnings.length > 0) { const suffix = art.warnings.length > 1 ? ` (+${art.warnings.length - 1} more)` : ""; const warning = `Mermaid diagram not rendered: ${art.warnings[0]}${suffix}`; const styledWarning = options.theme ? options.theme.fg("warning", warning) : warning; return `${token.raw}\n${codeSpan(styledWarning)} \n`; } const lines = options.theme ? themedLines(art, options.theme) : art.plain; // Markdown hard breaks keep every diagram row on its own line. return `${lines.map(codeSpan).join(" \n")}\n`; }) .join(""); }; } //# sourceMappingURL=mermaid.js.map