export class AIResStreamParser { private buffer: string = ''; private inCodeBlock: boolean = false; public parse(chunk: string): unknown { this.buffer += chunk; const clean = this.cleanBuffer(this.buffer); try { return JSON.parse(clean); } catch { return this.tryParsePartial(clean); } } private cleanBuffer(text: string): string { if (!this.inCodeBlock && text.includes('```json')) { this.inCodeBlock = true; } let content = text; if (this.inCodeBlock) { const startMatch = text.match(/```json\s*/); if (startMatch && startMatch.index !== undefined) { content = text.substring(startMatch.index + startMatch[0].length); } } content = content.replace(/```\s*$/, ''); return content.trim(); } private tryParsePartial(text: string): unknown { if (!text) return null; let braces = 0; let brackets = 0; let inString = false; let escaped = false; for (let i = 0; i < text.length; i++) { const char = text[i]; if (escaped) { escaped = false; continue; } if (char === '\\') { escaped = true; continue; } if (char === '"') { inString = !inString; continue; } if (!inString) { if (char === '{') braces++; if (char === '}') braces--; if (char === '[') brackets++; if (char === ']') brackets--; } } if (braces < 0 || brackets < 0) return null; let fix = text; if (inString) fix += '"'; const suffix = '}'.repeat(braces) + ']'.repeat(brackets); try { return JSON.parse(fix + suffix); } catch { /* incomplete JSON */ } try { if (!fix.endsWith('"') && inString) fix += '"'; return JSON.parse(fix + suffix); } catch { /* incomplete JSON */ } return null; } }