@@ -351,6 +351,14 @@ export class WorkflowEnricher {
351351 /**
352352 * Analyze page groups using browser-side script
353353 */
354+ // LLMs sometimes embed literal newlines in string fields like "reasoning". JSON.parse rejects
355+ // unescaped control characters in strings, so we escape them before parsing.
356+ private static sanitizeJsonString ( jsonStr : string ) : string {
357+ return jsonStr . replace ( / " (?: [ ^ " \\ ] | \\ .) * " / g, ( match ) =>
358+ match . replace ( / \n / g, '\\n' ) . replace ( / \r / g, '\\r' ) . replace ( / \t / g, '\\t' )
359+ ) ;
360+ }
361+
354362 private static async analyzePageGroups ( validator : SelectorValidator ) : Promise < any [ ] > {
355363 try {
356364 const page = ( validator as any ) . page ;
@@ -586,7 +594,7 @@ Note: selectedGroupIndex must be between 0 and ${elementGroups.length - 1}`;
586594 jsonStr = objectMatch [ 0 ] ;
587595 }
588596
589- const decision = JSON . parse ( jsonStr ) ;
597+ const decision = JSON . parse ( this . sanitizeJsonString ( jsonStr ) ) ;
590598
591599 if ( ! decision . actionType || decision . actionType !== 'captureList' ) {
592600 throw new Error ( 'LLM response must have actionType: "captureList"' ) ;
@@ -901,7 +909,7 @@ Return a JSON object with this exact structure:
901909 jsonStr = objectMatch [ 0 ] ;
902910 }
903911
904- const parsedResponse = JSON . parse ( jsonStr ) ;
912+ const parsedResponse = JSON . parse ( this . sanitizeJsonString ( jsonStr ) ) ;
905913
906914 let labelMapping : Record < string , string > ;
907915 if ( parsedResponse . fieldLabels ) {
@@ -1116,7 +1124,7 @@ Rules:
11161124 jsonStr = objectMatch [ 0 ] ;
11171125 }
11181126
1119- const filterResult = JSON . parse ( jsonStr ) ;
1127+ const filterResult = JSON . parse ( this . sanitizeJsonString ( jsonStr ) ) ;
11201128
11211129 if ( ! Array . isArray ( filterResult . selectedFields ) ) {
11221130 throw new Error ( 'Invalid response: selectedFields must be an array' ) ;
@@ -1752,7 +1760,7 @@ Extract the search query, extraction goal, and limit. Return JSON only.`;
17521760 jsonStr = objectMatch [ 0 ] ;
17531761 }
17541762
1755- const intent = JSON . parse ( jsonStr ) ;
1763+ const intent = JSON . parse ( this . sanitizeJsonString ( jsonStr ) ) ;
17561764
17571765 if ( ! intent . searchQuery || ! intent . extractionGoal ) {
17581766 throw new Error ( 'Invalid intent parsing response - missing required fields' ) ;
@@ -2023,7 +2031,7 @@ Select the BEST result index (0-${searchResults.length - 1}). Return JSON only.`
20232031 jsonStr = objectMatch [ 0 ] ;
20242032 }
20252033
2026- const decision = JSON . parse ( jsonStr ) ;
2034+ const decision = JSON . parse ( this . sanitizeJsonString ( jsonStr ) ) ;
20272035
20282036 if ( decision . selectedIndex === undefined || decision . selectedIndex < 0 || decision . selectedIndex >= searchResults . length ) {
20292037 throw new Error ( `Invalid selectedIndex: ${ decision . selectedIndex } ` ) ;
0 commit comments