@@ -2,6 +2,7 @@ package chat
22
33import (
44 "encoding/json"
5+ "fmt"
56 "strings"
67
78 tea "charm.land/bubbletea/v2"
@@ -24,6 +25,18 @@ type NestedToolContainer interface {
2425 AddNestedTool(tool ToolMessageItem)
2526}
2627
28+// collapsedNestedHint is the affordance shown under a collapsed sub-agent
29+// card, indicating its nested tool calls can be revealed with space.
30+const collapsedNestedHint = " ▸ space to expand"
31+
32+// toolCountLabel renders a "N tools" summary for a sub-agent header line.
33+func toolCountLabel(n int) string {
34+ if n == 1 {
35+ return "1 tool"
36+ }
37+ return fmt.Sprintf("%d tools", n)
38+}
39+
2740// AgentToolMessageItem is a message item that represents an agent tool call.
2841type AgentToolMessageItem struct {
2942 *baseToolMessageItem
@@ -139,6 +152,10 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
139152 }
140153
141154 header := toolHeader(sty, opts.Status, "Agent", cappedWidth, opts)
155+ nestedCount := len(r.agent.nestedTools)
156+ if nestedCount > 0 {
157+ header += " " + sty.Tool.ParamMain.Render(toolCountLabel(nestedCount))
158+ }
142159 if opts.Compact {
143160 return header
144161 }
@@ -164,17 +181,22 @@ func (r *AgentToolRenderContext) RenderTool(sty *styles.Styles, width int, opts
164181 ),
165182 )
166183
167− // Build tree with nested tool calls.
168− childTools := tree.Root(header)
169−
170− for _, nestedTool := range r.agent.nestedTools {
171− childView := nestedTool.Render(remainingWidth)
172− childTools.Child(childView)
173− }
174−
175− // Build parts.
184+ // Nested tool calls are collapsed by default to keep the conversation
185+ // readable; press space to expand the full call tree.
176186 var parts []string
177− parts = append(parts, childTools.Enumerator(roundedEnumerator(2, taskTagWidth-5)).String())
187+ if opts.ExpandedContent {
188+ childTools := tree.Root(header)
189+ for _, nestedTool := range r.agent.nestedTools {
190+ childView := nestedTool.Render(remainingWidth)
191+ childTools.Child(childView)
192+ }
193+ parts = append(parts, childTools.Enumerator(roundedEnumerator(2, taskTagWidth-5)).String())
194+ } else {
195+ parts = append(parts, header)
196+ if nestedCount > 0 {
197+ parts = append(parts, sty.Tool.AgentPrompt.Render(collapsedNestedHint))
198+ }
199+ }
178200
179201 // Show animation if still running.
180202 if !opts.HasResult() && !opts.IsCanceled() {
@@ -308,6 +330,10 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
308330 }
309331
310332 header := toolHeader(sty, opts.Status, "Agentic Fetch", cappedWidth, opts, toolParams...)
333+ nestedCount := len(r.fetch.nestedTools)
334+ if nestedCount > 0 {
335+ header += " " + sty.Tool.ParamMain.Render(toolCountLabel(nestedCount))
336+ }
311337 if opts.Compact {
312338 return header
313339 }
@@ -333,17 +359,22 @@ func (r *AgenticFetchToolRenderContext) RenderTool(sty *styles.Styles, width int
333359 ),
334360 )
335361
336− // Build tree with nested tool calls.
337− childTools := tree.Root(header)
338−
339− for _, nestedTool := range r.fetch.nestedTools {
340− childView := nestedTool.Render(remainingWidth)
341− childTools.Child(childView)
342− }
343−
344− // Build parts.
362+ // Nested tool calls are collapsed by default to keep the conversation
363+ // readable; press space to expand the full call tree.
345364 var parts []string
346− parts = append(parts, childTools.Enumerator(roundedEnumerator(2, promptTagWidth-5)).String())
365+ if opts.ExpandedContent {
366+ childTools := tree.Root(header)
367+ for _, nestedTool := range r.fetch.nestedTools {
368+ childView := nestedTool.Render(remainingWidth)
369+ childTools.Child(childView)
370+ }
371+ parts = append(parts, childTools.Enumerator(roundedEnumerator(2, promptTagWidth-5)).String())
372+ } else {
373+ parts = append(parts, header)
374+ if nestedCount > 0 {
375+ parts = append(parts, sty.Tool.AgentPrompt.Render(collapsedNestedHint))
376+ }
377+ }
347378
348379 // Show animation if still running.
349380 if !opts.HasResult() && !opts.IsCanceled() {