Created minimal greet tool returning 'hi'.
BuildExplained extension loading and tool availability.
UnderstandConfirmed greet tool availability after restart.
VerifyAttempted to modify tool for custom rendering and emojis.
EditFixed message format and tool call details.
Edityo, read the extension docs, then write me a minimal custom tool called greet that just says "hi" when called. put it in .pi/extensions/greet.ts
The user wants me to read the extension docs and create a minimal custom tool. Let me first read the extension documentation.
I've read the extension docs. The key part for creating a minimal greet tool is in the Quick Start section. Here's what I need:
.pi/extensions/greet.tsExtensionAPIpi.registerTool()Let me create a minimal version:
Done. I created a minimal greet tool that:
.pi/extensions/greet.tsCreated .pi/extensions/greet.ts with a minimal tool that returns "hi" when called. It'll be auto-discovered from the project-local extensions folder. No parameters, just returns the text.
ok, is the tool available to you?
The user is asking if I have access to the greet tool. Let me check what tools I have available. I can see from the system prompt that I have: Read, Bash, Edit, Write. I don't see a "greet" tool listed.
The extension was written to .pi/extensions/greet.ts which is a project-local extension path. But for me (the current session) to have access to it, the pi instance would need to be restarted or the extension loaded. Since I'm already running, the tool isn't available to me in this session.
No. I only have the built-in tools: Read, Bash, Edit, Write.
The extension is in place, but it won't be loaded until you restart pi or start a new session. Extensions are discovered at startup, not hot-reloaded.
how about now?
Let me check what tools I have available now. Looking at the system prompt, I can see the available functions listed at the top. Let me check... I see: Read, Bash, Edit, Write, and... greet!
Yes, there's a "greet" function that "Says hi" with no required parameters.
Yes! I now have the greet tool available. Let me try it:
The tool worked perfectly. It just returned "hi" as expected.
Works perfectly.
cool, modify it to do custom result rendering, add emojis or whatever
The user wants me to modify the greet tool to have custom result rendering. Looking at the extension docs, I can add renderResult to the tool definition. I should use the TUI components like Text from @mariozechner/pi-tui.
Let me read the current file first (as per the rules), then modify it to add custom rendering with emojis.
Now I'll add custom rendering with emojis. I need to import Text from pi-tui and add renderResult function.
Done. Added a custom renderResult that shows a fancy greeting with emojis, themed accent color, and bold text. The user will need to restart pi to see the changes since extensions aren't hot-reloaded.
Done. Added renderResult with sparkles and themed bold text. Restart pi to see the fancy output.
ok invoke it
The user wants me to invoke the greet tool to see the new rendering. Let me call it.
The tool returned "hi" but the user should see the custom rendering in their TUI with the emojis and styled text. The function result I see is just the raw content, but on their end the renderResult function would display the fancy version.
There you go. On your end you should see the sparkly styled version. I just see the raw "hi" content, but the TUI renders it with your custom renderResult.
can you add a slash command that shows me the last files read via the read tool?
The user wants me to add a slash command that tracks files read via the read tool and shows them. I need to:
tool_result eventLet me read the current file first, then modify it.
Now I need to add:
tool_call event for the "read" tool (or tool_result)/reads or /lastfiles that shows the tracked filesLet me add this functionality.
Done. Added:1. An array to track recent reads (up to 20)2. A tool_call event handler that captures Read tool calls3. A /reads command that shows the list with timestamps
The user will need to restart pi to load the updated extension.
Done. Added:
Read tool/reads command shows them with timestamps (seconds ago)Restart pi, then use /reads after I've read some files.
oh, wtf? no files read yet? oh, you can just go through the entries in the active branch and fetch the tool calls from there.
The user is right. The current implementation only tracks reads during the current session after the extension loads. But the session already has history of read tool calls in the session entries. I should reconstruct the list from the session entries on session_start, similar to how the docs show state reconstruction.
Let me read the current file and fix it.
I need to change the approach. Instead of tracking reads in real-time, I should just read from the session entries directly in the command handler. This way it always has the full history. I can look at the branch entries for tool call messages with toolName "Read".
Good. Now the command reads directly from the session branch entries, looking for assistant messages with tool_use parts where name is "Read". This way it has the full history from the session, not just what happened since the extension loaded.
Fixed. Now it reads directly from the session branch entries instead of tracking in memory. Shows all Read tool calls from the current conversation. Restart and try /reads.
still says no files read
Hmm, the tool name might be different. Let me check what the actual tool name is. In the system, the tool is called "Read" with capital R based on what I see in my available tools. But maybe in the session entries it's stored differently?
Let me check the session entries to see what the actual structure looks like. I could use bash to look at a session file, or I could add some debugging to the extension.
Actually, let me just read the extension docs again to see how tool calls are stored in entries. Or I could look at some examples.
Wait, looking at the extension docs more carefully:
if (entry.type === "message" && entry.message.role === "toolResult") { if (entry.message.toolName === "my_tool") {So for tool results, it's entry.message.toolName. But I'm looking at assistant messages with tool_use content parts.
The issue might be that the tool name in the content part is lowercase "read" not "Read". Let me check by looking at a session file or by making the extension case-insensitive.
Actually, I should check what the actual structure is. The user said it's not finding any reads, so either:
Let me update the extension to be case-insensitive and also check for lowercase "read".
Let me check the actual structure of the entries:
>cd /Users/badlogic/workspaces/pi-mono && find ~/.pi -name "*.json" -path "*sessions*" -newer /tmp 2>/dev/null | head -1 | xargs cat 2>/dev/null | head -100 || echo "No recent session found"Let me try a different approach - look at the types or examples to see how tool calls are stored.
>cd /Users/badlogic/workspaces/pi-mono && rg -l "tool_use" packages/coding-agent/src --type ts | head -5Let me look at the types to see how entries are structured.
>cd /Users/badlogic/workspaces/pi-mono && rg "toolName|tool_use" packages/ai/src/types.ts -A2 -B2 | head -40I see - tool calls are in assistant messages as tool_use content parts, but the name field might be different. Let me check the actual types for tool_use content.
>cd /Users/badlogic/workspaces/pi-mono && rg "type.*tool_use" packages/ai/src/types.ts -A5 -B2Let me search more broadly.
>cd /Users/badlogic/workspaces/pi-mono && rg "ToolUse|tool-use" packages/ai/src/types.ts -A5 -B2Maybe it's in a different file. Let me just search for the content types.
>cd /Users/badlogic/workspaces/pi-mono && rg "ToolCallContent|ToolUseContent" packages/ai/src/types.ts -A10Let me just look at the types file directly for content types.
>cd /Users/badlogic/workspaces/pi-mono && rg "Content\s*=" packages/ai/src/types.ts | head -20Different approach. Let me look at one of the example extensions that accesses tool calls.
>cd /Users/badlogic/workspaces/pi-mono && cat packages/coding-agent/examples/extensions/chalk-logger.tsSo event.toolName in the tool_call event is the tool name. But I was checking the session entries directly, not the events. Let me look at how the session stores tool calls - maybe the tool call entries store it differently.
Looking at the docs again:
for (const entry of ctx.sessionManager.getEntries()) { if (entry.type === "message" && entry.message.role === "toolResult") { if (entry.message.toolName === "my_tool") {So for toolResult messages, the tool name is on entry.message.toolName. But I'm looking at assistant messages which have the tool_use in the content array. That structure might be different.
Let me look at the agent-session or the types more closely.
>cd /Users/badlogic/workspaces/pi-mono && rg "interface.*Content" packages/ai/src/types.ts -A8 | head -60It's ToolCall with type: "toolCall", not type: "tool_use". Let me see the full interface.
>cd /Users/badlogic/workspaces/pi-mono && rg "interface ToolCall" packages/ai/src/types.ts -A10So the structure is:
type: "toolCall" (not "tool_use")- name: string (the tool name)- arguments (the input)And the tool name is probably lowercase "read" not "Read". Let me update the extension to check for type === "toolCall" and part.name (case-insensitive or lowercase).
Found it. The type is "toolCall" not "tool_use", and the tool name is probably lowercase. Let me fix:
I need to change:- part.type === "tool_use" to part.type === "toolCall"- part.name === "Read" to part.name.toLowerCase() === "read" or just part.name === "read"- part.input?.path to part.arguments?.path
Fixed:- type: "toolCall" instead of "tool_use"- name === "read" (lowercase)- arguments.path instead of input.path
Fixed. The type is "toolCall", tool name is lowercase "read", and params are in arguments. Restart and try /reads.
if (entry.type === "message" && entry.message.role === "toolResult") { if (entry.message.toolName === "my_tool") {for (const entry of ctx.sessionManager.getEntries()) { if (entry.type === "message" && entry.message.role === "toolResult") { if (entry.message.toolName === "my_tool") {