UNPKG

lynkr

Version:

Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.

147 lines (127 loc) 6.47 kB
/** * Markdown → ANSI escape code renderer. * * Activated by MARKDOWN_RENDER_ANSI=true in the environment. * Applied to text blocks in the SSE emission path so clients like claw * receive pre-formatted output without needing their own markdown renderer. * * Deliberately avoids external dependencies — pure regex + string ops. */ // --------------------------------------------------------------------------- // ANSI primitives // --------------------------------------------------------------------------- const R = '\x1b[0m'; // reset all const B = '\x1b[1m'; // bold on const B_ = '\x1b[22m'; // bold off const I = '\x1b[3m'; // italic on const I_ = '\x1b[23m'; // italic off const S = '\x1b[9m'; // strikethrough on const S_ = '\x1b[29m'; // strikethrough off const DIM = '\x1b[2m'; // dim const CYAN = '\x1b[1;96m'; // bold bright-cyan — H1 const BLUE = '\x1b[1;94m'; // bold bright-blue — H2 const MAGENTA = '\x1b[1;95m'; // bold bright-magenta — H3 const WHITE_B = '\x1b[1;97m'; // bold white — H4-H6 const YELLOW = '\x1b[33m'; // yellow — inline code const GREEN = '\x1b[92m'; // bright green — code block body const GRAY = '\x1b[90m'; // dark gray — HR / code fence border const ORANGE = '\x1b[38;5;214m'; // orange — code fence lang tag // --------------------------------------------------------------------------- // Inline formatting (applied to single lines outside code fences) // --------------------------------------------------------------------------- function inlineFmt(line) { // Bold + italic: ***text*** line = line.replace(/\*\*\*(.+?)\*\*\*/g, `${B}${I}$1${I_}${B_}`); // Bold: **text** or __text__ line = line.replace(/\*\*(.+?)\*\*/g, `${B}$1${B_}`); line = line.replace(/__(.+?)__/g, `${B}$1${B_}`); // Italic: *text* or _text_ (single, not preceded/followed by same char) line = line.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${I}$1${I_}`); line = line.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `${I}$1${I_}`); // Strikethrough: ~~text~~ line = line.replace(/~~(.+?)~~/g, `${S}$1${S_}`); // Inline code: `code` (done last so ANSI inside code isn't re-processed) line = line.replace(/`([^`]+)`/g, `${YELLOW}$1${R}`); return line; } // --------------------------------------------------------------------------- // Block-level rendering (processes the whole text at once) // --------------------------------------------------------------------------- function markdownToAnsi(text) { if (!text) return text; const lines = text.split('\n'); const out = []; let inCode = false; let codeLang = ''; for (let i = 0; i < lines.length; i++) { const raw = lines[i]; // ── Code fence open/close ────────────────────────────────────────────── const fenceMatch = raw.match(/^(`{3,})(.*)/); if (fenceMatch) { if (!inCode) { inCode = true; codeLang = fenceMatch[2].trim(); const tag = codeLang ? ` ${codeLang} ` : ''; out.push(`${GRAY}┌─${ORANGE}${tag}${GRAY}${'─'.repeat(Math.max(0, 46 - tag.length))}${R}`); } else { inCode = false; out.push(`${GRAY}${'─'.repeat(48)}${R}`); } continue; } // ── Inside a code block ─────────────────────────────────────────────── if (inCode) { out.push(`${GRAY}${GREEN}${raw}${R}`); continue; } // ── Horizontal rule ─────────────────────────────────────────────────── if (/^[-*_]{3,}\s*$/.test(raw.trim())) { out.push(`${GRAY}${'─'.repeat(50)}${R}`); continue; } // ── Headings ────────────────────────────────────────────────────────── const h6 = raw.match(/^(#{1,6})\s+(.*)/); if (h6) { const level = h6[1].length; const title = inlineFmt(h6[2]); const colors = [CYAN, BLUE, MAGENTA, WHITE_B, WHITE_B, WHITE_B]; const prefix = ['━━ ', '── ', ' ', ' ', ' ', ' '][level - 1]; out.push(`${colors[level - 1]}${prefix}${title}${R}`); continue; } // ── Blockquote ──────────────────────────────────────────────────────── if (raw.startsWith('> ')) { out.push(`${DIM}${inlineFmt(raw.slice(2))}${R}`); continue; } // ── Unordered list ──────────────────────────────────────────────────── const ulMatch = raw.match(/^(\s*)[*\-+] (.*)/); if (ulMatch) { const indent = ulMatch[1]; const depth = Math.floor(indent.length / 2); const bullet = ['•', '◦', '▸'][Math.min(depth, 2)]; out.push(`${indent}${YELLOW}${bullet}${R} ${inlineFmt(ulMatch[2])}`); continue; } // ── Ordered list ────────────────────────────────────────────────────── const olMatch = raw.match(/^(\s*)(\d+)\. (.*)/); if (olMatch) { out.push(`${olMatch[1]}${YELLOW}${olMatch[2]}.${R} ${inlineFmt(olMatch[3])}`); continue; } // ── Normal line (apply inline formatting) ───────────────────────────── out.push(inlineFmt(raw)); } // Close an unclosed code fence gracefully if (inCode) out.push(`${GRAY}${'─'.repeat(48)}${R}`); return out.join('\n'); } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- const enabled = process.env.MARKDOWN_RENDER_ANSI === 'true'; function renderText(text) { if (!enabled || !text) return text; return markdownToAnsi(text); } module.exports = { renderText, markdownToAnsi, enabled };