UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

294 lines 9.2 kB
export function autoCorrectJSON(raw, options = {}) { const { structural = true, stripFormatting = true, } = options; let corrected = raw; const corrections = []; try { if (stripFormatting) { const { text, applied } = stripUnwantedFormatting(corrected); corrected = text; corrections.push(...applied); } if (structural) { const { text, applied } = applyStructuralFixes(corrected); corrected = text; corrections.push(...applied); } const { text: fixedQuotes, applied: quoteCorrections } = fixQuotesAndEscapes(corrected); corrected = fixedQuotes; corrections.push(...quoteCorrections); JSON.parse(corrected); return { corrected, success: true, corrections, }; } catch (error) { return { corrected, success: false, corrections, error: error instanceof Error ? error : new Error(String(error)), }; } } function stripUnwantedFormatting(text) { let result = text; const applied = []; const strictFenceRegex = /^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/; if (strictFenceRegex.test(result)) { result = result.replace(strictFenceRegex, "$1"); applied.push("strip_markdown_fence"); } else { const embeddedFenceRegex = /```(?:json)?\s*\n([\s\S]*?)\n[ \t]*```(?:\s*$|\n)/; const match = result.match(embeddedFenceRegex); if (match && match[1]) { result = match[1]; applied.push("strip_markdown_fence"); } } if (result.trim().startsWith("json")) { result = result.trim().replace(/^json\s*/i, ""); applied.push("strip_json_prefix"); } const prefixes = [ /^Here's the JSON:?\s*/i, /^Here is the JSON:?\s*/i, /^The JSON is:?\s*/i, /^Sure,? here's the JSON:?\s*/i, /^Certainly[,!]? here's the JSON:?\s*/i, /^Output:?\s*/i, /^Result:?\s*/i, /^Response:?\s*/i, /^As an AI[^{]*/i, /^I can help[^{]*/i, ]; for (const prefix of prefixes) { if (prefix.test(result)) { result = result.replace(prefix, ""); applied.push("remove_prefix_text"); break; } } const suffixes = [ /[\]}]\s*\n\n.*$/s, /[\]}]\s*I hope this helps.*$/is, /[\]}]\s*Let me know if.*$/is, /[\]}]\s*This JSON.*$/is, ]; for (const suffix of suffixes) { if (suffix.test(result)) { const lastBrace = result.lastIndexOf("}"); const lastBracket = result.lastIndexOf("]"); const lastIndex = Math.max(lastBrace, lastBracket); if (lastIndex !== -1) { result = result.substring(0, lastIndex + 1); applied.push("remove_suffix_text"); break; } } } if (/\/\*[\s\S]*?\*\/|\/\/.*$/gm.test(result)) { result = result.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, ""); applied.push("remove_comments"); } result = result.trim(); return { text: result, applied }; } function applyStructuralFixes(text) { let result = text; const applied = []; const openBraces = (result.match(/{/g) || []).length; const closeBraces = (result.match(/}/g) || []).length; const openBrackets = (result.match(/\[/g) || []).length; const closeBrackets = (result.match(/\]/g) || []).length; if (openBraces > closeBraces) { const missing = openBraces - closeBraces; result += "}".repeat(missing); applied.push("close_brace"); } if (openBrackets > closeBrackets) { const missing = openBrackets - closeBrackets; result += "]".repeat(missing); applied.push("close_bracket"); } const trailingCommaRegex = /,(\s*[}\]])/g; if (trailingCommaRegex.test(result)) { result = result.replace(trailingCommaRegex, "$1"); applied.push("remove_trailing_comma"); } if (result.trim().endsWith(",")) { result = result.trim().slice(0, -1); applied.push("remove_trailing_comma"); } return { text: result, applied }; } function fixQuotesAndEscapes(text) { let result = text; const applied = []; try { JSON.parse(result); } catch (error) { if (error instanceof Error && (error.message.includes("control character") || error.message.includes("Bad control character"))) { result = result.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, (_match, content) => { const escaped = content .replace(/\n/g, "\\n") .replace(/\r/g, "\\r") .replace(/\t/g, "\\t"); return `"${escaped}"`; }); applied.push("escape_control_chars"); } } return { text: result, applied }; } function findFirstJSONDelimiter(text) { let inString = false; let escapeNext = false; for (let i = 0; i < text.length; i++) { const char = text[i]; if (escapeNext) { escapeNext = false; continue; } if (char === "\\") { escapeNext = true; continue; } if (char === '"') { inString = !inString; continue; } if (!inString) { if (char === "{") { return { startIndex: i, openChar: "{", closeChar: "}" }; } if (char === "[") { return { startIndex: i, openChar: "[", closeChar: "]" }; } } } return null; } export function extractJSON(text) { const delimiter = findFirstJSONDelimiter(text); if (!delimiter) { return text; } const { startIndex, openChar, closeChar } = delimiter; let depth = 0; let inString = false; let escapeNext = false; for (let i = startIndex; i < text.length; i++) { const char = text[i]; if (escapeNext) { escapeNext = false; continue; } if (char === "\\") { escapeNext = true; continue; } if (char === '"') { inString = !inString; continue; } if (inString) { continue; } if (char === openChar) { depth++; } else if (char === closeChar) { depth--; if (depth === 0) { return text.substring(startIndex, i + 1); } } } const objectMatch = text.match(/\{[\s\S]*\}/); if (objectMatch) { return objectMatch[0]; } const arrayMatch = text.match(/\[[\s\S]*\]/); if (arrayMatch) { return arrayMatch[0]; } return text; } export function isValidJSON(text) { try { JSON.parse(text); return true; } catch { return false; } } export function describeJSONError(error) { const message = error.message.toLowerCase(); if (message.includes("unexpected end")) { return "Incomplete JSON - missing closing braces or brackets"; } if (message.includes("unexpected token")) { return "Invalid JSON syntax - unexpected character"; } if (message.includes("control character")) { return "Invalid control characters in string values"; } if (message.includes("trailing comma")) { return "Trailing commas not allowed in JSON"; } if (message.includes("expected property name")) { return "Invalid property name - must be quoted"; } return error.message; } export function repairJSON(text) { const autoResult = autoCorrectJSON(text, { structural: true, stripFormatting: true, }); if (autoResult.success) { return autoResult.corrected; } const extracted = extractJSON(text); if (extracted !== text) { const retryResult = autoCorrectJSON(extracted, { structural: true, stripFormatting: true, }); if (retryResult.success) { return retryResult.corrected; } } let result = text.trim(); result = result.replace(/'([^']*?)'/g, '"$1"'); const finalResult = autoCorrectJSON(result, { structural: true, stripFormatting: true, }); if (finalResult.success) { return finalResult.corrected; } throw new Error(`Unable to repair JSON: ${describeJSONError(finalResult.error)}`); } export function safeJSONParse(text, options = {}) { try { const data = JSON.parse(text); return { data, corrected: false, corrections: [] }; } catch { const result = autoCorrectJSON(text, options); if (result.success) { const data = JSON.parse(result.corrected); return { data, corrected: true, corrections: result.corrections }; } throw new Error(`Failed to parse JSON: ${describeJSONError(result.error)}`); } } //# sourceMappingURL=autoCorrect.js.map