Files
LexiChain/lib/services/ai/analysis.parser.ts

111 lines
2.4 KiB
TypeScript
Raw Normal View History

2026-03-28 23:46:45 +01:00
function stripMarkdownFences(value: string): string {
return value
.replace(/^```json\s*/i, "")
.replace(/^```\s*/i, "")
.replace(/\s*```$/, "")
.trim();
}
function extractBalancedJson(text: string): string | null {
let start = -1;
let inString = false;
let escaped = false;
const stack: string[] = [];
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (start === -1) {
if (char === "{" || char === "[") {
start = i;
stack.push(char);
}
continue;
}
if (inString) {
if (!escaped && char === "\\") {
escaped = true;
continue;
}
if (!escaped && char === '"') {
inString = false;
}
escaped = false;
continue;
}
if (char === '"') {
inString = true;
continue;
}
if (char === "{" || char === "[") {
stack.push(char);
continue;
}
if (char === "}" || char === "]") {
const last = stack[stack.length - 1];
const isMatch =
(last === "{" && char === "}") || (last === "[" && char === "]");
if (!isMatch) {
return null;
}
stack.pop();
if (stack.length === 0 && start !== -1) {
return text.slice(start, i + 1);
}
}
}
return null;
}
function sanitizeLooseJson(value: string): string {
return value
.replace(/[\u201C\u201D]/g, '"')
.replace(/[\u2018\u2019]/g, "'")
.replace(/,\s*([}\]])/g, "$1")
.trim();
}
export function parseJsonResponse(text: string): unknown {
if (!text || typeof text !== "string" || text.trim().length === 0) {
throw new Error("AI response is empty.");
}
const cleaned = stripMarkdownFences(text);
try {
return JSON.parse(cleaned);
} catch {
// continue to robust fallback
}
const extracted = extractBalancedJson(cleaned);
if (!extracted) {
throw new Error(
`No complete JSON object found in AI response. Preview: ${cleaned.slice(0, 220)}`,
);
}
try {
return JSON.parse(extracted);
} catch {
const sanitized = sanitizeLooseJson(extracted);
try {
return JSON.parse(sanitized);
} catch (error) {
const message =
error instanceof Error ? error.message : "unknown parse error";
throw new Error(
`JSON parse failed after recovery attempts: ${message}. Preview: ${sanitized.slice(0, 220)}`,
);
}
}
}