From 374e811e78cd9389bfa74764b1f1ed7bf10056f1 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 21 Apr 2026 15:13:00 +0200 Subject: [PATCH] feat(ui): render user tool_result blocks as one-line summaries --- .../Helpers/StreamLineFormatter.cs | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs b/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs index 0a58678..eac4930 100644 --- a/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs +++ b/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs @@ -120,7 +120,100 @@ public class StreamLineFormatter return true; } - private static string? FormatUser(JsonElement root) => null; + private static string? FormatUser(JsonElement root) + { + if (!TryGetContentArray(root, out var content)) + return null; + + var sb = new StringBuilder(); + foreach (var block in content.EnumerateArray()) + { + if (block.ValueKind != JsonValueKind.Object) continue; + if (!block.TryGetProperty("type", out var blockTypeProp)) continue; + if (blockTypeProp.GetString() != "tool_result") continue; + + var summary = BuildToolResultSummary(root, block); + if (!string.IsNullOrEmpty(summary)) + { + sb.Append("→ "); + sb.Append(summary); + sb.Append('\n'); + } + } + + return sb.Length == 0 ? null : sb.ToString(); + } + + private static string BuildToolResultSummary(JsonElement root, JsonElement block) + { + var isError = block.TryGetProperty("is_error", out var errProp) + && errProp.ValueKind == JsonValueKind.True; + + var contentText = ResolveContentText(block); + + if (isError) + { + var msg = FirstNonEmptyLine(contentText); + return string.IsNullOrEmpty(msg) ? "error" : $"error: {Truncate(msg, MaxArgChars)}"; + } + + // tool_use_result.file.numLines shortcut for Read-style results + if (root.TryGetProperty("tool_use_result", out var tur) + && tur.ValueKind == JsonValueKind.Object + && tur.TryGetProperty("file", out var file) + && file.ValueKind == JsonValueKind.Object + && file.TryGetProperty("numLines", out var nl) + && nl.ValueKind == JsonValueKind.Number + && nl.TryGetInt32(out var lines)) + { + return $"{lines} lines"; + } + + if (string.IsNullOrWhiteSpace(contentText)) + return "ok"; + + var first = FirstNonEmptyLine(contentText); + return Truncate(first, MaxArgChars); + } + + private static string ResolveContentText(JsonElement block) + { + if (!block.TryGetProperty("content", out var c)) + return ""; + + if (c.ValueKind == JsonValueKind.String) + return c.GetString() ?? ""; + + if (c.ValueKind == JsonValueKind.Array) + { + var sb = new StringBuilder(); + foreach (var part in c.EnumerateArray()) + { + if (part.ValueKind != JsonValueKind.Object) continue; + if (!part.TryGetProperty("type", out var pt)) continue; + if (pt.GetString() != "text") continue; + if (part.TryGetProperty("text", out var t) && t.ValueKind == JsonValueKind.String) + { + if (sb.Length > 0) sb.Append('\n'); + sb.Append(t.GetString()); + } + } + return sb.ToString(); + } + + return ""; + } + + private static string FirstNonEmptyLine(string s) + { + if (string.IsNullOrEmpty(s)) return ""; + foreach (var raw in s.Split('\n')) + { + var line = raw.TrimEnd('\r').Trim(); + if (line.Length > 0) return line; + } + return ""; + } private static string? FormatResult(JsonElement root) {