@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
292 lines (291 loc) • 8.61 kB
JavaScript
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