From 1ba899f6a6823e507e2540e58aaf59ec983b8e9b Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 27 Mar 2026 03:10:13 +0100 Subject: [PATCH] fix(coding-agent): honor custom renderers for built-in tool overrides closes #2595 --- packages/coding-agent/CHANGELOG.md | 1 + .../interactive/components/tool-execution.ts | 12 +---- .../test/tool-execution-component.test.ts | 44 ++++++++++++++++++- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 73c1e732..2f799074 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed auto-compaction overflow recovery for Ollama models when the backend returns explicit `prompt too long; exceeded max context length ...` errors instead of silently truncating input ([#2626](https://github.com/badlogic/pi-mono/issues/2626)) +- Fixed built-in tool overrides that reuse built-in parameter schemas to still honor custom `renderCall` and `renderResult` renderers in the interactive TUI, restoring the `minimal-mode` example ([#2595](https://github.com/badlogic/pi-mono/issues/2595)) ## [0.63.0] - 2026-03-27 diff --git a/packages/coding-agent/src/modes/interactive/components/tool-execution.ts b/packages/coding-agent/src/modes/interactive/components/tool-execution.ts index 765a8437..0e87287e 100644 --- a/packages/coding-agent/src/modes/interactive/components/tool-execution.ts +++ b/packages/coding-agent/src/modes/interactive/components/tool-execution.ts @@ -72,19 +72,11 @@ export class ToolExecutionComponent extends Container { this.updateDisplay(); } - private isBuiltInDefinition(definition: ToolDefinition | undefined): boolean { - return ( - definition !== undefined && - this.builtInToolDefinition !== undefined && - definition.parameters === this.builtInToolDefinition.parameters - ); - } - private getCallRenderer(): ToolDefinition["renderCall"] | undefined { if (!this.builtInToolDefinition) { return this.toolDefinition?.renderCall; } - if (!this.toolDefinition || this.isBuiltInDefinition(this.toolDefinition)) { + if (!this.toolDefinition) { return this.builtInToolDefinition.renderCall; } return this.toolDefinition.renderCall ?? this.builtInToolDefinition.renderCall; @@ -94,7 +86,7 @@ export class ToolExecutionComponent extends Container { if (!this.builtInToolDefinition) { return this.toolDefinition?.renderResult; } - if (!this.toolDefinition || this.isBuiltInDefinition(this.toolDefinition)) { + if (!this.toolDefinition) { return this.builtInToolDefinition.renderResult; } return this.toolDefinition.renderResult ?? this.builtInToolDefinition.renderResult; diff --git a/packages/coding-agent/test/tool-execution-component.test.ts b/packages/coding-agent/test/tool-execution-component.test.ts index 658b447f..1bfb0ec2 100644 --- a/packages/coding-agent/test/tool-execution-component.test.ts +++ b/packages/coding-agent/test/tool-execution-component.test.ts @@ -4,7 +4,7 @@ import stripAnsi from "strip-ansi"; import { beforeAll, describe, expect, test } from "vitest"; import type { ToolDefinition } from "../src/core/extensions/types.js"; import { type BashOperations, createBashToolDefinition } from "../src/core/tools/bash.js"; -import { createReadToolDefinition } from "../src/core/tools/read.js"; +import { createReadTool, createReadToolDefinition } from "../src/core/tools/read.js"; import { createWriteToolDefinition } from "../src/core/tools/write.js"; import { ToolExecutionComponent } from "../src/modes/interactive/components/tool-execution.js"; import { initTheme } from "../src/modes/interactive/theme/theme.js"; @@ -166,6 +166,48 @@ describe("ToolExecutionComponent parity", () => { expect(rendered).toContain("override result"); }); + test("uses custom renderers for built-in overrides that reuse built-in definition parameters", () => { + const builtInDefinition = createReadToolDefinition(process.cwd()); + const component = new ToolExecutionComponent( + "read", + "tool-4d", + { path: "README.md" }, + {}, + { + ...builtInDefinition, + renderCall: () => new Text("override call", 0, 0), + renderResult: () => new Text("override result", 0, 0), + }, + createFakeTui(), + ); + component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false); + const rendered = stripAnsi(component.render(120).join("\n")); + expect(rendered).toContain("override call"); + expect(rendered).toContain("override result"); + expect(rendered).not.toContain("read README.md"); + }); + + test("uses custom renderers for built-in overrides that reuse wrapped built-in tool parameters", () => { + const builtInTool = createReadTool(process.cwd()); + const component = new ToolExecutionComponent( + "read", + "tool-4e", + { path: "README.md" }, + {}, + { + ...createBaseToolDefinition("read"), + parameters: builtInTool.parameters, + renderCall: () => new Text("wrapped override call", 0, 0), + renderResult: () => new Text("wrapped override result", 0, 0), + }, + createFakeTui(), + ); + component.updateResult({ content: [{ type: "text", text: "hello" }], details: undefined, isError: false }, false); + const rendered = stripAnsi(component.render(120).join("\n")); + expect(rendered).toContain("wrapped override call"); + expect(rendered).toContain("wrapped override result"); + }); + test("shares renderer state across custom call and result slots", () => { type RenderState = { token?: string }; const toolDefinition: ToolDefinition = {