@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
294 lines • 9.2 kB
JavaScript
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