/** * Safely parses JSON from a string, handling markdown code blocks and leading/trailing text. */ export function safeParseJson(text: string): T | null { if (!text) return null; let jsonStr = text.trim(); // 1. Try to extract JSON from markdown code blocks if they exist // Matches ```json ... ``` or just ``` ... ``` const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/i; const match = jsonStr.match(codeBlockRegex); if (match && match[1]) { jsonStr = match[1].trim(); } else { // 2. If no markdown block, try to find the start and end of JSON characters // This handles cases where the AI adds introductory or concluding text outside the block const firstOpenBrace = jsonStr.indexOf('{'); const firstOpenBracket = jsonStr.indexOf('['); let startIndex = -1; if (firstOpenBrace !== -1 && (firstOpenBracket === -1 || firstOpenBrace < firstOpenBracket)) { startIndex = firstOpenBrace; } else if (firstOpenBracket !== -1) { startIndex = firstOpenBracket; } if (startIndex !== -1) { const lastCloseBrace = jsonStr.lastIndexOf('}'); const lastCloseBracket = jsonStr.lastIndexOf(']'); const endIndex = Math.max(lastCloseBrace, lastCloseBracket); if (endIndex !== -1 && endIndex > startIndex) { jsonStr = jsonStr.substring(startIndex, endIndex + 1); } } } try { return JSON.parse(jsonStr) as T; } catch (error) { console.error('[safeParseJson] Failed to parse JSON:', error); console.error('[safeParseJson] Original text:', text); console.error('[safeParseJson] Extracted string:', jsonStr); return null; } }