UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

292 lines (291 loc) 8.61 kB
function createIncrementalJsonState() { return { openBraces: 0, closeBraces: 0, openBrackets: 0, closeBrackets: 0, inString: false, escapeNext: false, processedLength: 0 }; } function updateJsonStateIncremental(state, delta) { for (let i = 0; i < delta.length; i++) { const char = delta[i]; if (state.escapeNext) { state.escapeNext = false; continue; } if (char === "\\") { state.escapeNext = true; continue; } if (char === '"') { state.inString = !state.inString; continue; } if (!state.inString) { if (char === "{") state.openBraces++; if (char === "}") state.closeBraces++; if (char === "[") state.openBrackets++; if (char === "]") state.closeBrackets++; } } state.processedLength += delta.length; return state; } function incrementalStateToStructure(state) { const issues = []; if (state.inString) { issues.push("Unclosed string detected"); } if (state.openBraces !== state.closeBraces) { issues.push( `Unbalanced braces: ${state.openBraces} open, ${state.closeBraces} close` ); } if (state.openBrackets !== state.closeBrackets) { issues.push( `Unbalanced brackets: ${state.openBrackets} open, ${state.closeBrackets} close` ); } const isBalanced = state.openBraces === state.closeBraces && state.openBrackets === state.closeBrackets && !state.inString; return { openBraces: state.openBraces, closeBraces: state.closeBraces, openBrackets: state.openBrackets, closeBrackets: state.closeBrackets, inString: state.inString, isBalanced, issues }; } function analyzeJsonStructure(content) { const state = createIncrementalJsonState(); updateJsonStateIncremental(state, content); return incrementalStateToStructure(state); } function looksLikeJson(content) { if (!content) return false; const trimmed = content.trim(); return trimmed.startsWith("{") || trimmed.startsWith("["); } function validateJsonStructure(context) { const { content, completed } = context; const violations = []; if (!looksLikeJson(content)) { return violations; } const structure = analyzeJsonStructure(content); if (!completed) { if (structure.closeBraces > structure.openBraces) { violations.push({ rule: "json-structure", message: `Too many closing braces: ${structure.closeBraces} close, ${structure.openBraces} open`, severity: "error", recoverable: true }); } if (structure.closeBrackets > structure.openBrackets) { violations.push({ rule: "json-structure", message: `Too many closing brackets: ${structure.closeBrackets} close, ${structure.openBrackets} open`, severity: "error", recoverable: true }); } } else { if (!structure.isBalanced) { for (const issue of structure.issues) { violations.push({ rule: "json-structure", message: issue, severity: "error", recoverable: true, suggestion: "Retry generation to get properly balanced JSON" }); } } } return violations; } function validateJsonChunks(context) { const { content, delta } = context; const violations = []; if (!delta || !looksLikeJson(content)) { return violations; } const malformedPatterns = [ { pattern: /,,+/, message: "Multiple consecutive commas" }, { pattern: /\{\s*,/, message: "Comma immediately after opening brace" }, { pattern: /\[\s*,/, message: "Comma immediately after opening bracket" }, { pattern: /:\s*,/, message: "Comma immediately after colon" } ]; for (const { pattern, message } of malformedPatterns) { if (pattern.test(content)) { violations.push({ rule: "json-chunks", message: `Malformed JSON: ${message}`, severity: "error", recoverable: true }); } } return violations; } function validateJsonParseable(context) { const { content, completed } = context; const violations = []; if (!completed || !looksLikeJson(content)) { return violations; } try { JSON.parse(content.trim()); } catch (error) { violations.push({ rule: "json-parseable", message: `JSON is not parseable: ${error instanceof Error ? error.message : "Unknown error"}`, severity: "error", recoverable: true, suggestion: "Retry generation to get valid JSON" }); } return violations; } function jsonRule() { let incrementalState = null; let lastProcessedLength = 0; return { name: "json-structure", description: "Validates JSON structure and balance", streaming: true, severity: "error", recoverable: true, check: (context) => { const violations = []; const { content, delta, completed } = context; if (!looksLikeJson(content)) { incrementalState = null; lastProcessedLength = 0; return violations; } if (content.length < lastProcessedLength) { incrementalState = null; lastProcessedLength = 0; } if (completed) { incrementalState = null; lastProcessedLength = 0; violations.push(...validateJsonStructure(context)); violations.push(...validateJsonChunks(context)); violations.push(...validateJsonParseable(context)); } else { if (!incrementalState) { incrementalState = createIncrementalJsonState(); lastProcessedLength = 0; } if (delta) { updateJsonStateIncremental(incrementalState, delta); } else if (content.length > lastProcessedLength) { const newContent = content.slice(lastProcessedLength); updateJsonStateIncremental(incrementalState, newContent); } lastProcessedLength = content.length; if (incrementalState.closeBraces > incrementalState.openBraces) { violations.push({ rule: "json-structure", message: `Too many closing braces: ${incrementalState.closeBraces} close, ${incrementalState.openBraces} open`, severity: "error", recoverable: true }); } if (incrementalState.closeBrackets > incrementalState.openBrackets) { violations.push({ rule: "json-structure", message: `Too many closing brackets: ${incrementalState.closeBrackets} close, ${incrementalState.openBrackets} open`, severity: "error", recoverable: true }); } if (delta) { if (delta.includes(",,")) { violations.push({ rule: "json-chunks", message: "Malformed JSON: Multiple consecutive commas", severity: "error", recoverable: true }); } } } return violations; } }; } function strictJsonRule() { return { name: "json-strict", description: "Strict JSON validation including structure and parseability", streaming: false, severity: "error", recoverable: true, check: (context) => { const violations = []; if (!context.completed) { return violations; } const { content } = context; if (!looksLikeJson(content)) { violations.push({ rule: "json-strict", message: "Content does not appear to be JSON (must start with { or [)", severity: "error", recoverable: true }); return violations; } violations.push(...validateJsonParseable(context)); if (violations.length === 0) { try { const parsed = JSON.parse(content.trim()); if (typeof parsed !== "object" || parsed === null) { violations.push({ rule: "json-strict", message: "JSON root must be an object or array", severity: "error", recoverable: true }); } } catch { } } return violations; } }; } class JsonGuardrail { rule; constructor(strict = false) { this.rule = strict ? strictJsonRule() : jsonRule(); } check(context) { return this.rule.check(context); } get name() { return this.rule.name; } } export { JsonGuardrail, analyzeJsonStructure, createIncrementalJsonState, incrementalStateToStructure, jsonRule, looksLikeJson, strictJsonRule, updateJsonStateIncremental, validateJsonChunks, validateJsonParseable, validateJsonStructure }; //# sourceMappingURL=json.js.map