const fs = require('fs'); const content = fs.readFileSync('temp/rich-outline-fail_57_1779015509994.txt', 'utf-8'); const marker = '========== AI εŽŸε§‹ε“εΊ” '; const markerIdx = content.indexOf(marker); const headerEndIdx = content.indexOf(' ==========', markerIdx); const aiStart = content.indexOf('\n', headerEndIdx) + 1; const aiEnd = content.indexOf('\n\n', aiStart); const aiResponse = content.substring(aiStart, aiEnd); console.log('String length (UTF-16 code units):', aiResponse.length); // The issue: JSON.parse uses UTF-16 code unit positions, but the JSON is actually UTF-8 encoded // when transmitted over the network. The LLM returns UTF-8 bytes, but in Node.js when we get // the response as a string, it's decoded to UTF-16. // Wait - actually JSON.parse should work correctly with UTF-16 strings in JavaScript. // Let me check if there's actually a malformed character somewhere. // Try using a simple character-by-character JSON parser approach to find the issue const pos = 1624; console.log('\nAnalyzing around position', pos); // Get the actual bytes via TextEncoder const encoder = new TextEncoder(); const decoder = new TextDecoder('utf-8'); // Let's check the raw bytes of the string const uint8Array = encoder.encode(aiResponse); console.log('Encoded bytes length:', uint8Array.length); // Check bytes around position 1624 (in UTF-16 string) console.log('\nBytes around UTF-16 position', pos, ':'); for (let i = Math.max(0, pos - 5); i < Math.min(uint8Array.length, pos + 10); i++) { console.log(' byte', i, ':', uint8Array[i], '(char:', String.fromCharCode(uint8Array[i]), ')'); } // Now let's try to find where the actual byte-level position differs from UTF-16 position // by counting UTF-8 multi-byte characters before position 1624 let utf8Offset = 0; let utf16Offset = 0; let lastAsciiBefore1624 = -1; let lastAsciiUtf8BytePos = -1; for (let i = 0; i < aiResponse.length && utf16Offset < 1624; i++) { const code = aiResponse.charCodeAt(i); if (code <= 0x7F) { // ASCII - 1 byte in UTF-8, 1 code unit in UTF-16 if (utf16Offset === 1623) { lastAsciiBefore1624 = code; lastAsciiUtf8BytePos = utf8Offset; } utf8Offset += 1; utf16Offset += 1; } else if (code <= 0x7FF) { // 2-byte UTF-8 utf8Offset += 2; utf16Offset += 1; } else if (code <= 0xFFFF) { // 3-byte UTF-8 (most CJK characters) utf8Offset += 3; utf16Offset += 1; } else { // 4-byte UTF-8 (emoji, etc) utf8Offset += 4; utf16Offset += 2; } } console.log('\nAt UTF-16 position 1624:'); console.log(' Corresponding UTF-8 byte position would be around:', utf8Offset); console.log(' Last ASCII char (at UTF-16 1623):', lastAsciiBefore1624, '(code:', lastAsciiBefore1624, ')'); // Now let's check what JSON.parse says the error position actually means // In JSON parse errors, "position" is the character offset in the string // Let's see if position 1624 in the string is actually where the issue is console.log('\n=== Direct parse attempt ==='); try { JSON.parse(aiResponse); } catch(e) { console.log('Error:', e.message); const match = e.message.match(/position (\d+)/); if (match) { const errorPos = parseInt(match[1]); console.log('Error position:', errorPos); console.log('Character at error position:', JSON.stringify(aiResponse[errorPos])); console.log('Context:', JSON.stringify(aiResponse.substring(Math.max(0, errorPos-50), errorPos+50))); // Check if char at errorPos is the start of a multi-byte sequence const code = aiResponse.charCodeAt(errorPos); console.log('Code point at errorPos:', code, 'hex:', code.toString(16)); } }