UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

248 lines 7.94 kB
const BEGIN_PATTERN = /\\begin\{(\w+)\}/g; const END_PATTERN = /\\end\{(\w+)\}/g; const DISPLAY_MATH_OPEN = /\\\[/g; const DISPLAY_MATH_CLOSE = /\\\]/g; const DOUBLE_DOLLAR = /\$\$/g; const LATEX_PATTERNS = [ /\\begin\{/, /\\end\{/, /\\\[/, /\\\]/, /\$\$/, /\\[a-zA-Z]+\{/, /\\section/, /\\subsection/, /\\textbf/, /\\textit/, /\\frac/, /\\sum/, /\\int/, ]; export function analyzeLatexStructure(content) { const issues = []; const openEnvironments = []; const envStack = []; BEGIN_PATTERN.lastIndex = 0; END_PATTERN.lastIndex = 0; const begins = []; const ends = []; let match; while ((match = BEGIN_PATTERN.exec(content)) !== null) { begins.push({ env: match[1], pos: match.index }); } while ((match = END_PATTERN.exec(content)) !== null) { ends.push({ env: match[1], pos: match.index }); } const events = [ ...begins.map((b) => ({ ...b, type: "begin" })), ...ends.map((e) => ({ ...e, type: "end" })), ].sort((a, b) => a.pos - b.pos); for (const event of events) { if (event.type === "begin") { envStack.push(event.env); } else { if (envStack.length === 0) { issues.push(`\\end{${event.env}} without matching \\begin{${event.env}}`); } else { const last = envStack[envStack.length - 1]; if (last === event.env) { envStack.pop(); } else { issues.push(`Environment mismatch: \\begin{${last}} closed with \\end{${event.env}}`); envStack.pop(); } } } } for (const env of envStack) { openEnvironments.push(env); issues.push(`Unclosed environment: \\begin{${env}}`); } const isBalanced = envStack.length === 0 && issues.length === 0; return { openEnvironments, isBalanced, issues, }; } export function looksLikeLatex(content) { if (!content) return false; return LATEX_PATTERNS.some((pattern) => pattern.test(content)); } export function validateLatexEnvironments(context) { const { content, completed } = context; const violations = []; if (!looksLikeLatex(content)) { return violations; } const structure = analyzeLatexStructure(content); if (!completed) { const mismatchIssues = structure.issues.filter((issue) => issue.includes("mismatch")); for (const issue of mismatchIssues) { violations.push({ rule: "latex-environments", message: issue, severity: "error", recoverable: true, }); } if (structure.openEnvironments.length > 5) { violations.push({ rule: "latex-environments", message: `Excessive unclosed environments: ${structure.openEnvironments.length}`, severity: "warning", recoverable: true, }); } } else { if (!structure.isBalanced) { for (const issue of structure.issues) { violations.push({ rule: "latex-environments", message: issue, severity: "error", recoverable: true, suggestion: "Retry generation to properly balance LaTeX environments", }); } } } return violations; } export function validateLatexMath(context) { const { content, completed } = context; const violations = []; if (!looksLikeLatex(content)) { return violations; } DISPLAY_MATH_OPEN.lastIndex = 0; DISPLAY_MATH_CLOSE.lastIndex = 0; DOUBLE_DOLLAR.lastIndex = 0; const displayMathOpen = (content.match(DISPLAY_MATH_OPEN) || []).length; const displayMathClose = (content.match(DISPLAY_MATH_CLOSE) || []).length; const doubleDollar = (content.match(DOUBLE_DOLLAR) || []).length; let singleDollarCount = 0; let escapeNext = false; for (let i = 0; i < content.length; i++) { if (escapeNext) { escapeNext = false; continue; } if (content[i] === "\\") { escapeNext = true; continue; } if (content[i] === "$" && (i + 1 >= content.length || content[i + 1] !== "$")) { singleDollarCount++; } } if (completed) { if (displayMathOpen !== displayMathClose) { violations.push({ rule: "latex-math", message: `Unbalanced display math: ${displayMathOpen} \\[ and ${displayMathClose} \\]`, severity: "error", recoverable: true, suggestion: "Ensure all \\[ have matching \\]", }); } if (doubleDollar % 2 !== 0) { violations.push({ rule: "latex-math", message: `Unbalanced $$ delimiters: ${doubleDollar} found (should be even)`, severity: "error", recoverable: true, suggestion: "Ensure all $$ are paired", }); } if (singleDollarCount % 2 !== 0) { violations.push({ rule: "latex-math", message: `Unbalanced inline math: ${singleDollarCount} $ found (should be even)`, severity: "warning", recoverable: true, suggestion: "Check inline math delimiters", }); } } return violations; } export function validateLatexCommon(context) { const { content, completed } = context; const violations = []; if (!looksLikeLatex(content)) { return violations; } if (!completed) { return violations; } const commandPattern = /\\[a-zA-Z]+/g; let match; while ((match = commandPattern.exec(content)) !== null) { const afterCommand = content.slice(match.index + match[0].length); if (afterCommand.startsWith("{")) { let depth = 0; let found = false; for (let i = 0; i < afterCommand.length; i++) { if (afterCommand[i] === "{") depth++; if (afterCommand[i] === "}") { depth--; if (depth === 0) { found = true; break; } } } if (!found && depth > 0) { violations.push({ rule: "latex-common", message: `Unclosed braces after command ${match[0]}`, severity: "warning", recoverable: true, }); } } } return violations; } export function latexRule() { return { name: "latex-environments", description: "Validates LaTeX environment balance and structure", streaming: true, severity: "error", recoverable: true, check: (context) => { const violations = []; if (!looksLikeLatex(context.content) && context.content.length > 50) { return violations; } violations.push(...validateLatexEnvironments(context)); violations.push(...validateLatexMath(context)); if (context.completed) { violations.push(...validateLatexCommon(context)); } return violations; }, }; } export class LatexGuardrail { rule; constructor() { this.rule = latexRule(); } check(context) { return this.rule.check(context); } get name() { return this.rule.name; } } //# sourceMappingURL=latex.js.map