Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 22x 22x 22x 22x 1x 21x 21x 21x 21x 9x 12x 12x 8x 4x 22x 22x 22x 22x 1580x 1580x 238x 238x 238x 1342x 2x 2x 2x 1340x 2x 2x 2x 1338x 973x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 968x 968x 1x 967x 365x 22x | // JSON extraction + sanitization for Claude responses.
//
// Claude often wraps JSON in markdown code fences and occasionally emits
// unescaped control characters inside string values, both of which break a
// naive `JSON.parse(text)`. These helpers normalize the model output before
// parsing so callers don't have to repeat the same string-handling per
// endpoint.
/**
* Extract JSON from Claude API response that may be wrapped in markdown code blocks.
* Handles formats like:
* - ```json\n{...}\n```
* - ```\n{...}\n```
* - Plain JSON: {...}
* - Text before/after JSON: "Here's the response:\n{...}\n"
*/
export function extractJSON(text: string): string {
const trimmed = text.trim();
// Try to extract from markdown code blocks first
const codeBlockPattern = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
const codeBlockMatch = trimmed.match(codeBlockPattern);
if (codeBlockMatch?.[1]) {
return sanitizeJSON(codeBlockMatch[1].trim());
}
// Try to find JSON array or object in the text
const jsonArrayPattern = /(\[[\s\S]*\])/;
const jsonObjectPattern = /(\{[\s\S]*\})/;
const arrayMatch = trimmed.match(jsonArrayPattern);
if (arrayMatch?.[1]) {
return sanitizeJSON(arrayMatch[1].trim());
}
const objectMatch = trimmed.match(jsonObjectPattern);
if (objectMatch?.[1]) {
return sanitizeJSON(objectMatch[1].trim());
}
// Fall back to trimmed original text
return sanitizeJSON(trimmed);
}
/**
* Sanitize JSON string by escaping control characters within string values.
* Fixes cases where Claude returns JSON with unescaped newlines/tabs inside
* string values, which would otherwise break JSON.parse().
*
* Strategy: replace literal control characters with their escaped versions,
* but only within quoted strings (not in structural JSON).
*/
export function sanitizeJSON(jsonText: string): string {
let result = "";
let inString = false;
let escaped = false;
for (let i = 0; i < jsonText.length; i++) {
const char = jsonText[i];
// Track if we're inside a string
if (char === '"' && !escaped) {
inString = !inString;
result += char;
continue;
}
// Track escape sequences
if (char === "\\" && !escaped) {
escaped = true;
result += char;
continue;
}
if (escaped) {
escaped = false;
result += char;
continue;
}
// If we're inside a string, escape control characters
if (inString) {
switch (char) {
case "\n":
result += "\\n";
break;
case "\r":
result += "\\r";
break;
case "\t":
result += "\\t";
break;
case "\b":
result += "\\b";
break;
case "\f":
result += "\\f";
break;
default: {
// Other control characters (ASCII 0-31)
const code = char.charCodeAt(0);
if (code < 32 && code !== 10 && code !== 13 && code !== 9) {
result += `\\u${code.toString(16).padStart(4, "0")}`;
} else {
result += char;
}
}
}
} else {
// Outside strings, preserve as-is
result += char;
}
}
return result;
}
|