1+ using System . Text ;
12using System . Text . Json ;
23using System . Text . Json . Nodes ;
34using System . Text . RegularExpressions ;
@@ -105,31 +106,42 @@ internal static bool TryGetKnownToolIdentity(string? value, out string identity)
105106 return false ;
106107 }
107108
108- internal static string ExtractParentToolCallId ( JsonElement data )
109+ internal static string ? ExtractToolCorrelationId ( JsonElement data )
109110 {
110- var parent = GetStringProperty ( data , "parentItemId" , "parentId" , "toolItemId" , "parentCallId" ) ;
111- if ( ! string . IsNullOrWhiteSpace ( parent ) )
112- return parent ;
111+ if ( data . TryGetProperty ( "toolCallId" , out var toolCallIdValue )
112+ && toolCallIdValue . ValueKind == JsonValueKind . String )
113+ {
114+ var toolCallId = toolCallIdValue . GetString ( ) ;
115+ if ( ! string . IsNullOrWhiteSpace ( toolCallId ) )
116+ return toolCallId ;
117+ }
113118
114- foreach ( var containerName in new [ ] { "details" , "parent" } )
119+ if ( ! data . TryGetProperty ( "itemId" , out var itemIdValue )
120+ || itemIdValue . ValueKind != JsonValueKind . String )
115121 {
116- if ( data . TryGetProperty ( containerName , out var container )
117- && container . ValueKind == JsonValueKind . Object )
118- {
119- parent = GetStringProperty (
120- container ,
121- "parentItemId" ,
122- "parentId" ,
123- "toolItemId" ,
124- "itemId" ,
125- "callId" ) ;
126- if ( ! string . IsNullOrWhiteSpace ( parent ) )
127- return parent ;
128- }
122+ return null ;
129123 }
130- return string . Empty ;
124+
125+ var itemId = itemIdValue . GetString ( ) ;
126+ if ( string . IsNullOrWhiteSpace ( itemId ) )
127+ return null ;
128+
129+ foreach ( var prefix in new [ ] { "tool:" , "command:" , "patch:" } )
130+ {
131+ if ( ! itemId . StartsWith ( prefix , StringComparison . Ordinal ) )
132+ continue ;
133+
134+ itemId = itemId [ prefix . Length .. ] ;
135+ break ;
136+ }
137+
138+ return string . IsNullOrWhiteSpace ( itemId ) ? null : itemId ;
131139 }
132140
141+ internal static bool IsToolResultError ( JsonElement data ) =>
142+ data . TryGetProperty ( "isError" , out var isError )
143+ && isError . ValueKind == JsonValueKind . True ;
144+
133145 internal static string GetStringProperty ( JsonElement data , params string [ ] names )
134146 {
135147 foreach ( var name in names )
@@ -215,14 +227,6 @@ internal static string ExtractCommandOutputText(JsonElement data)
215227 }
216228 }
217229
218- if ( data . TryGetProperty ( "title" , out var title )
219- && title . ValueKind == JsonValueKind . String )
220- {
221- var text = title . GetString ( ) ;
222- if ( ! string . IsNullOrEmpty ( text ) )
223- return TruncateToolOutput ( text ) ;
224- }
225-
226230 return string . Empty ;
227231 }
228232
@@ -240,11 +244,38 @@ internal static string ExtractToolResultText(JsonElement data, string fallback)
240244 {
241245 if ( result . ValueKind == JsonValueKind . String )
242246 return TruncateToolOutput ( result . GetString ( ) ?? "" ) ;
243- if ( result . ValueKind == JsonValueKind . Object
244- && result . TryGetProperty ( "content" , out var resultContent )
245- && resultContent . ValueKind == JsonValueKind . String )
247+ if ( result . ValueKind == JsonValueKind . Array )
246248 {
247- return TruncateToolOutput ( resultContent . GetString ( ) ?? "" ) ;
249+ var arrayText = ExtractTypedTextBlocks ( result ) ;
250+ if ( ! string . IsNullOrEmpty ( arrayText ) )
251+ return arrayText ;
252+ }
253+ if ( result . ValueKind == JsonValueKind . Object )
254+ {
255+ if ( result . TryGetProperty ( "details" , out var details )
256+ && details . ValueKind == JsonValueKind . Object )
257+ {
258+ var aggregated = GetStringProperty ( details , "aggregated" ) ;
259+ if ( ! string . IsNullOrEmpty ( aggregated ) )
260+ return TruncateToolOutput ( aggregated ) ;
261+ }
262+
263+ if ( result . TryGetProperty ( "content" , out var resultContent )
264+ && resultContent . ValueKind == JsonValueKind . String )
265+ {
266+ return TruncateToolOutput ( resultContent . GetString ( ) ?? "" ) ;
267+ }
268+
269+ if ( result . TryGetProperty ( "content" , out resultContent ) )
270+ {
271+ var contentText = ExtractTypedTextBlocks ( resultContent ) ;
272+ if ( ! string . IsNullOrEmpty ( contentText ) )
273+ return contentText ;
274+ }
275+
276+ var directText = ExtractTypedTextBlocks ( result ) ;
277+ if ( ! string . IsNullOrEmpty ( directText ) )
278+ return directText ;
248279 }
249280 }
250281
@@ -263,7 +294,23 @@ internal static string ExtractToolResultText(JsonElement data, string fallback)
263294
264295 internal static string ExtractToolErrorText ( JsonElement data , string fallback )
265296 {
266- foreach ( var key in new [ ] { "error" , "message" , "stderr" , "content" } )
297+ if ( data . TryGetProperty ( "result" , out var result )
298+ && result . ValueKind == JsonValueKind . Object )
299+ {
300+ if ( result . TryGetProperty ( "details" , out var details )
301+ && details . ValueKind == JsonValueKind . Object )
302+ {
303+ var detailsError = GetStringProperty ( details , "error" , "message" , "reason" ) ;
304+ if ( ! string . IsNullOrEmpty ( detailsError ) )
305+ return TruncateToolOutput ( detailsError ) ;
306+ }
307+
308+ var resultError = GetStringProperty ( result , "error" , "message" , "reason" ) ;
309+ if ( ! string . IsNullOrEmpty ( resultError ) )
310+ return TruncateToolOutput ( resultError ) ;
311+ }
312+
313+ foreach ( var key in new [ ] { "error" , "message" , "stderr" , "summary" } )
267314 {
268315 if ( data . TryGetProperty ( key , out var value ) )
269316 {
@@ -283,9 +330,71 @@ internal static string ExtractToolErrorText(JsonElement data, string fallback)
283330 }
284331 }
285332 }
333+
334+ var resultText = ExtractToolResultText ( data , string . Empty ) ;
335+ if ( ! string . IsNullOrEmpty ( resultText ) )
336+ return resultText ;
337+
286338 return fallback ;
287339 }
288340
341+ internal static string ExtractToolResultErrorText ( JsonElement data )
342+ {
343+ var safeSummary = SanitizeToolDisplayValue (
344+ GetStringProperty ( data , "toolErrorSummary" ) ) ;
345+ return string . IsNullOrEmpty ( safeSummary )
346+ ? ExtractToolErrorText ( data , string . Empty )
347+ : safeSummary ;
348+ }
349+
350+ internal static bool HasSafeToolErrorSummary ( JsonElement data ) =>
351+ ! string . IsNullOrEmpty ( SanitizeToolDisplayValue (
352+ GetStringProperty ( data , "toolErrorSummary" ) ) ) ;
353+
354+ internal static string ExtractPatchSummaryText ( JsonElement data )
355+ {
356+ var summary = GetStringProperty ( data , "summary" ) ;
357+ return string . IsNullOrEmpty ( summary ) ? string . Empty : TruncateToolOutput ( summary ) ;
358+ }
359+
360+ private static string ExtractTypedTextBlocks ( JsonElement content )
361+ {
362+ var builder = new StringBuilder ( ) ;
363+ void AppendBlock ( JsonElement block )
364+ {
365+ if ( builder . Length > ToolOutputMaxChars
366+ || block . ValueKind != JsonValueKind . Object
367+ || ! string . Equals ( GetStringProperty ( block , "type" ) , "text" , StringComparison . Ordinal )
368+ || ! block . TryGetProperty ( "text" , out var textValue )
369+ || textValue . ValueKind != JsonValueKind . String )
370+ {
371+ return ;
372+ }
373+
374+ var text = textValue . GetString ( ) ? . Trim ( ) ;
375+ if ( string . IsNullOrEmpty ( text ) )
376+ return ;
377+
378+ if ( builder . Length > 0 )
379+ builder . Append ( '\n ' ) ;
380+ var remaining = ToolOutputMaxChars + 1 - builder . Length ;
381+ if ( remaining > 0 )
382+ builder . Append ( text . AsSpan ( 0 , Math . Min ( text . Length , remaining ) ) ) ;
383+ }
384+
385+ if ( content . ValueKind == JsonValueKind . Array )
386+ {
387+ foreach ( var block in content . EnumerateArray ( ) )
388+ AppendBlock ( block ) ;
389+ }
390+ else
391+ {
392+ AppendBlock ( content ) ;
393+ }
394+
395+ return TruncateToolOutput ( builder . ToString ( ) ) ;
396+ }
397+
289398 internal static bool LooksLikeSystemControlNote ( string text )
290399 {
291400 if ( string . IsNullOrEmpty ( text ) )
0 commit comments