How does tool call grouping work?
how's does the tool call grouping work
Now I have a complete picture. Here's how tool call grouping works:
##Tool Call Grouping
The system lives entirely in apps/web/app/[owner]/[repo]/[postNumber]/comment-content.tsx and operates at two levels: first grouping the raw message parts, then rendering each group.
###1. groupParts() — Collecting consecutive tool calls (line 614)
The groupParts function iterates over all AgentUIMessage parts and produces a flat array of GroupedPart items, which can be one of three types: "text", "reasoning", or "tool-group".
The grouping logic is straightforward:
- Tool parts (anything where
part.type.startsWith("tool-")and has a"state"property) are accumulated into a runningcurrentToolGrouparray. Consecutive tool parts keep getting pushed into the same group. - Text parts (non-empty) and reasoning parts (non-empty) break the current tool group. When encountered, the accumulated tool group is flushed to
resultas a single"tool-group"entry, and the text/reasoning is added as its own entry. - Other part types like
step-start,step-finish, etc. are silently ignored — they do not break the tool grouping. This is noted explicitly in the code comment on line 664. - After the loop, any remaining unflushed tool group is pushed.
So if the AI makes 5 tool calls in a row, they all end up in one "tool-group". If it makes 2 calls, writes some text, then makes 3 more calls, you get two separate tool groups with a text part between them.
###2. ToolGroup component — Rendering a group (line 491)
The ToolGroup component renders a group of tool call parts. It has special handling depending on the group's state:
| Scenario | Rendering |
|---|---|
| Single completed tool (no in-progress) | Rendered directly as a single <Tool> — no collapsible wrapper |
| Multiple completed tools | Rendered inside a <Collapsible> that's collapsed by default. The trigger shows a summary like "3 Grep - 2 Read" |
| Streaming (last group + last tool in-progress) | The in-progress tool is split out and rendered separately below the collapsible, so it's always visible |
###3. countToolsByName() — Summary labels (line 412)
The countToolsByName helper counts tools by type name (e.g., "GREP", "READ") to generate the collapsible trigger text. It strips the "tool-" prefix, uppercases it, counts occurrences, and joins them with " - ".
###4. "Thinking" collapse — Outer-level grouping (line 690-710)
There's a second layer of collapsing in the CommentContent component. When not streaming, everything before the last text part is considered "thinking" and can be collapsed into a single collapsible section (if it contains non-tool parts like text or reasoning). This aggregates tool counts from all thinking-phase tool groups into a single summary trigger.
###Visual Summary
Message Stream:
[Grep] [Read] [Read] [text: "Found it"] [Bash] [text: "Here's the answer"]
groupParts() produces:
1. tool-group: [Grep, Read, Read] → collapsed: "1 GREP - 2 READ"
2. text: "Found it"
3. tool-group: [Bash] → single tool, no collapsible
4. text: "Here's the answer"
CommentContent then collapses #1-#3 as "thinking" (since #4 is the last text).
END OF POST