id: NVIM-RESEARCH-T3BL4TK4
type: research
title: Pi tool result formats for presentation
Pi tool result formats for presentation
Read from real Pi session logs on disk ($PI_CODING_AGENT_SESSION_DIR), not from prose.
Each tool result is a toolResult message: { role, toolCallId, toolName, content, isError, timestamp, details? }.
content is always a text block list [{ type:"text", text }]; the rich, per-tool structure lives in details.
This closes the evidence gap that blocked grep file-grouping and the bash full-output surface.
Per-tool shapes
Tool
details keys
content text shape
edit
diff, firstChangedLine
a short sentence, e.g. Successfully replaced 4 block(s) in <path>.
grep
none
ripgrep context output, or No matches found
find
none
newline-separated names, no line numbers
ls
none
newline-separated entries, directories suffixed /
bash
truncation, fullOutputPath
the captured output, possibly truncated
read
truncation
file content
write
none
a short sentence
edit
details.diff is a display diff with a line-number gutter and change markers, for example:
9 * ...
+ 11 * The tools use ...
+ 12 * a system prompt section ...
A removed line is marked -, an added line +, and a context line has no marker.
details.firstChangedLine is the first changed line number.
This matches what pivi already consumes; no change needed for the edit diff.
grep
No details. The match structure is only in content.text, in ripgrep's context format:
a match line is PATH:LINE:TEXT
a context line is PATH-LINE-TEXT
distinct match groups may be separated by a -- line
an empty search is exactly No matches found
The path repeats on every line, so grouping by file means reading the PATH prefix and collecting consecutive lines that share it; the match lines are the ones joined by : rather than -.
There is no column offset in this format, so an exact in-line match span is not available; only the whole match line can be distinguished without re-searching the text client-side.
find and ls
content.text is a newline-separated list.
find returns names or paths with no line numbers, so a find result groups trivially as a flat list.
ls suffixes directories with /.
bash
details.truncation carries { truncated: bool, ... }; details.fullOutputPath is a filesystem path to the complete output when the kept result was trimmed.
A full-output detail surface opens that path.
read
details.truncation only; the subject path is the file itself, so a preview leads back to the file that was read.
Conclusions
grep and find file-grouping is implementable now: parse the ripgrep context format, grouping consecutive lines by their PATH prefix and marking the : match lines; find is a flat list.
Exact in-line match highlighting is not supported by the data; distinguish the match line, or re-find the term client-side, rather than relying on an offset.
The bash full-output surface opens details.fullOutputPath; truncation is details.truncation.truncated.
Edit diff and first-changed-line are already correct against the real shape; no rework there.
Unresolved
Whether ripgrep emits -- separators and heading lines under every Pi configuration, or only with certain flags; the parser must tolerate both.
Whether a stable match column is ever present in any grep mode; none seen in the sampled logs.
Whether details.truncation ever includes a kept/total count useful to show beyond the boolean.
---
id: NVIM-RESEARCH-T3BL4TK4
type: research
title: Pi tool result formats for presentation
---
# Pi tool result formats for presentation
Read from real Pi session logs on disk (`$PI_CODING_AGENT_SESSION_DIR`), not from prose.
Each tool result is a `toolResult` message: `{ role, toolCallId, toolName, content, isError, timestamp, details? }`.
`content` is always a text block list `[{ type:"text", text }]`; the rich, per-tool structure lives in `details`.
This closes the evidence gap that blocked grep file-grouping and the bash full-output surface.
## Per-tool shapes
| Tool | `details` keys | `content` text shape |
| --- | --- | --- |
| edit | `diff`, `firstChangedLine` | a short sentence, e.g. `Successfully replaced 4 block(s) in <path>.` |
| grep | none | ripgrep context output, or `No matches found` |
| find | none | newline-separated names, no line numbers |
| ls | none | newline-separated entries, directories suffixed `/` |
| bash | `truncation`, `fullOutputPath` | the captured output, possibly truncated |
| read | `truncation` | file content |
| write | none | a short sentence |
### edit
`details.diff` is a display diff with a line-number gutter and change markers, for example:
```
9 * ...
+ 11 * The tools use ...
+ 12 * a system prompt section ...
```
A removed line is marked `-`, an added line `+`, and a context line has no marker.
`details.firstChangedLine` is the first changed line number.
This matches what pivi already consumes; no change needed for the edit diff.
### grep
No `details`. The match structure is only in `content.text`, in ripgrep's context format:
- a match line is `PATH:LINE:TEXT`
- a context line is `PATH-LINE-TEXT`
- distinct match groups may be separated by a `--` line
- an empty search is exactly `No matches found`
The path repeats on every line, so grouping by file means reading the `PATH` prefix and collecting consecutive lines that share it; the match lines are the ones joined by `:` rather than `-`.
There is no column offset in this format, so an exact in-line match span is not available; only the whole match line can be distinguished without re-searching the text client-side.
### find and ls
`content.text` is a newline-separated list.
`find` returns names or paths with no line numbers, so a find result groups trivially as a flat list.
`ls` suffixes directories with `/`.
### bash
`details.truncation` carries `{ truncated: bool, ... }`; `details.fullOutputPath` is a filesystem path to the complete output when the kept result was trimmed.
A full-output detail surface opens that path.
### read
`details.truncation` only; the subject path is the file itself, so a preview leads back to the file that was read.
## Conclusions
- grep and find file-grouping is implementable now: parse the ripgrep context format, grouping consecutive lines by their `PATH` prefix and marking the `:` match lines; `find` is a flat list.
- Exact in-line match highlighting is not supported by the data; distinguish the match line, or re-find the term client-side, rather than relying on an offset.
- The bash full-output surface opens `details.fullOutputPath`; truncation is `details.truncation.truncated`.
- Edit diff and first-changed-line are already correct against the real shape; no rework there.
## Unresolved
- Whether ripgrep emits `--` separators and heading lines under every Pi configuration, or only with certain flags; the parser must tolerate both.
- Whether a stable match column is ever present in any grep mode; none seen in the sampled logs.
- Whether `details.truncation` ever includes a kept/total count useful to show beyond the boolean.