diff --git a/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb b/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb index 0e0aaad6..3f3561ea 100644 --- a/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb +++ b/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb @@ -94,6 +94,30 @@ def format_read details ? "#{label} (#{details})" : label end + # Formats a write tool call. + # + # Input fields: + # :path (String) – file to write [required] + # :content (String) – file contents [required] + # + # Output: 'WRITE "" (+ )' – is + # the first line of :content (stripped, truncated to TRUNCATE_LIMIT + # chars) and is the number of lines written. The count is always + # shown. + # + # Examples: + # WRITE lib/roast.rb "class Roast" (+10 lines) + # WRITE config/app.yml "enabled: true" (+1 line) + # + #: () -> String + def format_write + path, content = arguments.values_at(:path, :content) + lines = content.to_s.lines + preview = truncate(lines.first.to_s.strip) + count = lines.length + "WRITE #{path} \"#{preview}\" (+#{count} #{"line".pluralize(count)})" + end + # Formats a tool call for which Roast has no dedicated formatter. # # Output: " : , ..." – the upcased tool name, then each diff --git a/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb b/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb index adac0e26..ee62324d 100644 --- a/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb +++ b/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb @@ -91,6 +91,22 @@ def format_read ok_line("#{count} #{"line".pluralize(count)}") end + # Formats a write tool result. + # + # Input: :path – the path that was written, from the originating call. + # + # Output: "WRITE OK " – the file path, omitted when the call + # had none. + # + # Examples: + # WRITE OK lib/roast/version.rb + # WRITE OK + # + #: () -> String + def format_write + ok_line(@input[:path]) + end + # Formats a result for which Roast has no dedicated formatter. # # Content: the tool's output text. diff --git a/test/roast/cogs/agent/providers/pi/messages/tool_call_message_test.rb b/test/roast/cogs/agent/providers/pi/messages/tool_call_message_test.rb index 584fe0d0..ac6b1f15 100644 --- a/test/roast/cogs/agent/providers/pi/messages/tool_call_message_test.rb +++ b/test/roast/cogs/agent/providers/pi/messages/tool_call_message_test.rb @@ -56,6 +56,33 @@ class ToolCallMessageTest < ActiveSupport::TestCase assert_equal "READ lib/roast.rb (from line 30)", msg.format end + test "format renders WRITE with the path, preview, and line count" do + msg = ToolCallMessage.new( + id: "1", + name: "write", + arguments: { path: "lib/roast.rb", content: "line one\nline two\nline three" }, + ) + assert_equal 'WRITE lib/roast.rb "line one" (+3 lines)', msg.format + end + + test "format renders a singular line count for single-line write content" do + msg = ToolCallMessage.new( + id: "1", + name: "write", + arguments: { path: "config/app.yml", content: "enabled: true" }, + ) + assert_equal 'WRITE config/app.yml "enabled: true" (+1 line)', msg.format + end + + test "format truncates a long write preview" do + msg = ToolCallMessage.new( + id: "1", + name: "write", + arguments: { path: "lib/roast.rb", content: "x" * 100 }, + ) + assert_equal "WRITE lib/roast.rb \"#{"x" * (ToolCallMessage::TRUNCATE_LIMIT - 3)}...\" (+1 line)", msg.format + end + test "format renders an unhandled tool as NAME key: value, ..." do msg = ToolCallMessage.new( id: "1", diff --git a/test/roast/cogs/agent/providers/pi/messages/tool_result_message_test.rb b/test/roast/cogs/agent/providers/pi/messages/tool_result_message_test.rb index 24c92d0b..df84bf42 100644 --- a/test/roast/cogs/agent/providers/pi/messages/tool_result_message_test.rb +++ b/test/roast/cogs/agent/providers/pi/messages/tool_result_message_test.rb @@ -76,6 +76,31 @@ def setup assert_equal "READ OK 0 lines", msg.format(@context) end + test "format renders WRITE OK with the path from the originating call" do + @context.add_tool_call( + ToolCallMessage.new(id: "1", name: "write", arguments: { path: "lib/roast/version.rb" }), + ) + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "write", + content: "File written successfully", + is_error: false, + ) + + assert_equal "WRITE OK lib/roast/version.rb", msg.format(@context) + end + + test "format renders a bare WRITE OK when the path is unavailable" do + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "write", + content: "File written successfully", + is_error: false, + ) + + assert_equal "WRITE OK", msg.format(@context) + end + test "format renders NAME ERROR with the message for an error result" do msg = ToolResultMessage.new( tool_call_id: "1",