@@ -16,61 +16,39 @@ export type ParseResponseMessageContentResult = {
1616 messageContent : string ;
1717} ;
1818
19- const readQuotedAttr = ( tag : string , attr : string ) : string | null => {
20- const needle = `${ attr } ="` ;
21- const lower = tag . toLowerCase ( ) ;
22- const idx = lower . indexOf ( needle . toLowerCase ( ) ) ;
23-
24- if ( idx === - 1 ) {
25- return null ;
26- }
27-
28- let i = idx + needle . length ;
29- let out = '' ;
30-
31- while ( i < tag . length ) {
32- const c = tag [ i ] ;
33-
34- if ( c === '\\' && i + 1 < tag . length ) {
35- const esc = tag [ i + 1 ] ;
36-
37- if ( esc === 'n' ) {
38- out += '\n' ;
39- } else if ( esc === 't' ) {
40- out += '\t' ;
41- } else if ( esc === 'r' ) {
42- out += '\r' ;
43- } else if ( esc === '"' ) {
44- out += '"' ;
45- } else if ( esc === '\\' ) {
46- out += '\\' ;
47- } else if ( esc === 'u' && / ^ [ 0 - 9 a - f A - F ] { 4 } / . test ( tag . slice ( i + 2 , i + 6 ) ) ) {
48- out += String . fromCharCode ( parseInt ( tag . slice ( i + 2 , i + 6 ) , 16 ) ) ;
49- i += 6 ;
50-
51- continue ;
52- } else {
53- // e.g. `\"` in attributes: keep `\`, let `"` pass through for html decode
54- out += c ;
55- i += 1 ;
56-
57- continue ;
58- }
59-
60- i += 2 ;
61-
62- continue ;
19+ const unescapeAttributeValue = ( value : string ) : string =>
20+ value . replace ( / \\ ( u [ 0 - 9 a - f A - F ] { 4 } | [ " ' \\ n t r ] ) / g, ( _ , esc : string ) => {
21+ switch ( esc ) {
22+ case 'n' :
23+ return '\n' ;
24+ case 't' :
25+ return '\t' ;
26+ case 'r' :
27+ return '\r' ;
28+ case '"' :
29+ return '"' ;
30+ case '\'' :
31+ return '\'' ;
32+ case '\\' :
33+ return '\\' ;
34+ default :
35+ return esc . startsWith ( 'u' ) ? String . fromCharCode ( parseInt ( esc . slice ( 1 ) , 16 ) ) : esc ;
6336 }
37+ } ) ;
6438
65- if ( c === '"' ) {
66- break ;
67- }
39+ const parseTagAttributes = ( tag : string ) : Record < string , string > => {
40+ const attrs : Record < string , string > = { } ;
41+ const attrRe = / ( [ ^ \s = / > ] + ) \s * = \s * (?: " ( (?: \\ . | [ ^ " ] ) * ) " | ' ( (?: \\ . | [ ^ ' ] ) * ) ' ) / g ;
6842
69- out += c ;
70- i ++ ;
43+ let match : RegExpExecArray | null ;
44+
45+ while ( ( match = attrRe . exec ( tag ) ) !== null ) {
46+ const [ , rawName , doubleQuoted , singleQuoted ] = match ;
47+ const rawValue = doubleQuoted ?? singleQuoted ?? '' ;
48+ attrs [ rawName . toLowerCase ( ) ] = unescapeAttributeValue ( rawValue ) ;
7149 }
7250
73- return out ;
51+ return attrs ;
7452} ;
7553
7654const indexAfterOpenDetailsTag = ( s : string ) : number => {
@@ -79,7 +57,6 @@ const indexAfterOpenDetailsTag = (s: string): number => {
7957 if ( ! open ) {
8058 return - 1 ;
8159 }
82-
8360 let i = open [ 0 ] . length ;
8461 let inDouble = false ;
8562 let escape = false ;
@@ -90,28 +67,24 @@ const indexAfterOpenDetailsTag = (s: string): number => {
9067 if ( escape ) {
9168 escape = false ;
9269 i ++ ;
93-
9470 continue ;
9571 }
9672
9773 if ( c === '\\' ) {
9874 escape = true ;
9975 i ++ ;
100-
10176 continue ;
10277 }
10378
10479 if ( c === '"' ) {
10580 inDouble = ! inDouble ;
10681 i ++ ;
107-
10882 continue ;
10983 }
11084
11185 if ( c === '>' && ! inDouble ) {
11286 return i + 1 ;
11387 }
114-
11588 i ++ ;
11689 }
11790
@@ -147,30 +120,6 @@ const classifyAndNormalizePayload = (raw: string): { contentType: PayloadContent
147120 return { contentType : 'text' , normalized : String ( parsed ) } ;
148121} ;
149122
150- const repairUtf8MisreadAsLatin1 = ( s : string ) : string => {
151- for ( let j = 0 ; j < s . length ; j ++ ) {
152- if ( s . charCodeAt ( j ) > 255 ) {
153- return s ;
154- }
155- }
156-
157- const bytes = new Uint8Array ( s . length ) ;
158-
159- for ( let j = 0 ; j < s . length ; j ++ ) {
160- bytes [ j ] = s . charCodeAt ( j ) ;
161- }
162-
163- return new TextDecoder ( 'utf-8' , { fatal : false } ) . decode ( bytes ) ;
164- } ;
165-
166- const normalizeToolResultText = ( s : string ) : string => {
167- let t = String ( s ) ;
168-
169- t = repairUtf8MisreadAsLatin1 ( t ) ;
170-
171- return t ;
172- } ;
173-
174123const tryParseLeadingToolCallsDetails = ( content : string ) : { tool : ToolData ; rest : string } | null => {
175124 const leadingWs = content . match ( / ^ \s * / ) ?. [ 0 ] ?? '' ;
176125 const fromDetails = content . slice ( leadingWs . length ) ;
@@ -186,8 +135,9 @@ const tryParseLeadingToolCallsDetails = (content: string): { tool: ToolData; res
186135 }
187136
188137 const openTag = fromDetails . slice ( 0 , openEnd ) ;
138+ const attrs = parseTagAttributes ( openTag ) ;
189139
190- if ( ! / t y p e \s * = \s * [ " ' ] ? t o o l _ c a l l s [ " ' ] ? / i . test ( openTag ) ) {
140+ if ( ( attrs . type ?? '' ) . toLowerCase ( ) !== 'tool_calls' ) {
191141 return null ;
192142 }
193143
@@ -197,14 +147,13 @@ const tryParseLeadingToolCallsDetails = (content: string): { tool: ToolData; res
197147 return null ;
198148 }
199149
200- const id = readQuotedAttr ( openTag , 'id' ) ?? undefined ;
201- const toolName = readQuotedAttr ( openTag , ' name' ) ?? '' ;
202- const argsRaw = readQuotedAttr ( openTag , ' arguments' ) ?? '' ;
203- const resultRaw = readQuotedAttr ( openTag , ' result' ) ?? '' ;
150+ const id = attrs . id ?? undefined ;
151+ const toolName = attrs . name ?? '' ;
152+ const argsRaw = attrs . arguments ?? '' ;
153+ const resultRaw = attrs . result ?? '' ;
204154
205155 const outputPayload = classifyAndNormalizePayload ( resultRaw ) ;
206156 const input = parseObjectToString ( parseJsonRecursive ( decode ( argsRaw ) . trim ( ) ) ) ;
207-
208157 const blockEnd = leadingWs . length + openEnd + closeMatch . index + closeMatch [ 0 ] . length ;
209158 const rest = content . slice ( blockEnd ) . trimStart ( ) ;
210159
@@ -213,7 +162,7 @@ const tryParseLeadingToolCallsDetails = (content: string): { tool: ToolData; res
213162 id,
214163 toolName,
215164 input,
216- output : normalizeToolResultText ( outputPayload . normalized ) ,
165+ output : outputPayload . normalized ,
217166 outputContentType : outputPayload . contentType ,
218167 } ,
219168 rest,
@@ -230,7 +179,6 @@ export const parseResponseMessageContent = (content: string): ParseResponseMessa
230179 if ( ! next ) {
231180 break ;
232181 }
233-
234182 toolsData . push ( next . tool ) ;
235183 rest = next . rest ;
236184 }
0 commit comments