@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
216 lines • 6.91 kB
JavaScript
export function analyzeJsonStructure(content) {
let openBraces = 0;
let closeBraces = 0;
let openBrackets = 0;
let closeBrackets = 0;
let inString = false;
let escapeNext = false;
const issues = [];
for (let i = 0; i < content.length; i++) {
const char = content[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === "\\") {
escapeNext = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (!inString) {
if (char === "{")
openBraces++;
if (char === "}")
closeBraces++;
if (char === "[")
openBrackets++;
if (char === "]")
closeBrackets++;
}
}
if (inString) {
issues.push("Unclosed string detected");
}
if (openBraces !== closeBraces) {
issues.push(`Unbalanced braces: ${openBraces} open, ${closeBraces} close`);
}
if (openBrackets !== closeBrackets) {
issues.push(`Unbalanced brackets: ${openBrackets} open, ${closeBrackets} close`);
}
const isBalanced = openBraces === closeBraces && openBrackets === closeBrackets && !inString;
return {
openBraces,
closeBraces,
openBrackets,
closeBrackets,
inString,
isBalanced,
issues,
};
}
export function looksLikeJson(content) {
if (!content)
return false;
const trimmed = content.trim();
return trimmed.startsWith("{") || trimmed.startsWith("[");
}
export 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;
}
export 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;
}
export 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;
}
export function jsonRule() {
return {
name: "json-structure",
description: "Validates JSON structure and balance",
streaming: true,
severity: "error",
recoverable: true,
check: (context) => {
const violations = [];
violations.push(...validateJsonStructure(context));
violations.push(...validateJsonChunks(context));
if (context.completed) {
violations.push(...validateJsonParseable(context));
}
return violations;
},
};
}
export 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;
},
};
}
export class JsonGuardrail {
rule;
constructor(strict = false) {
this.rule = strict ? strictJsonRule() : jsonRule();
}
check(context) {
return this.rule.check(context);
}
get name() {
return this.rule.name;
}
}
//# sourceMappingURL=json.js.map