From 3a67fe81b49832651665afd83db6b1e1ffb3b10e Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 21 Apr 2026 15:08:35 +0200 Subject: [PATCH] feat(ui): render assistant tool_use blocks with per-tool args --- .../Helpers/StreamLineFormatter.cs | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs b/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs index 1990bed..3e86556 100644 --- a/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs +++ b/src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs @@ -95,7 +95,8 @@ public class StreamLineFormatter break; case "tool_use": - // Filled in by a later task. + sb.Append(FormatToolUse(block)); + sb.Append('\n'); break; case "thinking": @@ -128,6 +129,96 @@ public class StreamLineFormatter return null; } + private static string FormatToolUse(JsonElement block) + { + var name = block.TryGetProperty("name", out var nameProp) + ? nameProp.GetString() ?? "?" + : "?"; + + JsonElement input = default; + var hasInput = block.TryGetProperty("input", out input) + && input.ValueKind == JsonValueKind.Object; + + var label = name; + if (hasInput && (name == "Task" || name == "Agent")) + { + var sub = GetStr(input, "subagent_type"); + if (!string.IsNullOrEmpty(sub)) + label = $"{name}: {sub}"; + } + + string? arg = hasInput ? BuildToolArg(name, input) : null; + + return string.IsNullOrEmpty(arg) + ? $"[{label}]" + : $"[{label}] {arg}"; + } + + private static string? BuildToolArg(string toolName, JsonElement input) + { + switch (toolName) + { + case "Read": + case "Write": + case "Edit": + case "NotebookEdit": + return Basename(GetStr(input, "file_path")); + + case "Bash": + case "PowerShell": + { + var cmd = GetStr(input, "command"); + return string.IsNullOrEmpty(cmd) ? null : "$ " + Truncate(cmd, MaxArgChars); + } + + case "Grep": + { + var p = GetStr(input, "pattern"); + return string.IsNullOrEmpty(p) ? null : $"\"{Truncate(p, MaxArgChars)}\""; + } + + case "Glob": + return Truncate(GetStr(input, "pattern"), MaxArgChars); + + case "Task": + case "Agent": + return Truncate(GetStr(input, "description"), MaxArgChars); + + case "WebFetch": + return GetStr(input, "url"); + + case "WebSearch": + { + var q = GetStr(input, "query"); + return string.IsNullOrEmpty(q) ? null : $"\"{Truncate(q, MaxArgChars)}\""; + } + + case "TodoWrite": + return null; + + default: + return null; + } + } + + private static string? GetStr(JsonElement obj, string name) + => obj.TryGetProperty(name, out var p) && p.ValueKind == JsonValueKind.String + ? p.GetString() + : null; + + private static string Basename(string? path) + { + if (string.IsNullOrEmpty(path)) return ""; + var i = path.LastIndexOfAny(new[] { '/', '\\' }); + return i < 0 ? path : path[(i + 1)..]; + } + + private static string Truncate(string? s, int max) + { + if (string.IsNullOrEmpty(s)) return ""; + return s.Length <= max ? s : s[..max] + "…"; + } + public string FormatFile(string filePath) { var sb = new StringBuilder();