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 a9414aa5..fbc228b2 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 @@ -97,6 +97,36 @@ 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 + # the number of lines written. The content summary is appended only + # when :content is present; a missing path renders the bare "WRITE". + # + # Examples: + # WRITE lib/roast.rb "class Roast" (+10 lines) + # WRITE config/app.yml "enabled: true" (+1 line) + # WRITE lib/roast.rb + # WRITE + # + #: () -> String + def format_write + path, content = arguments.values_at(:path, :content) + path = path.to_s + label = path.empty? ? "WRITE" : "WRITE #{path}" + return label if content.nil? + + lines = content.to_s.lines + preview = truncate(lines.first.to_s.strip) + count = lines.length + "#{label} \"#{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 3af2ac8d..003843f2 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 @@ -96,6 +96,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 2cd7a4c5..00405f06 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 @@ -61,6 +61,43 @@ class ToolCallMessageTest < ActiveSupport::TestCase assert_equal "READ lib/roast.rb", 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 WRITE with the path alone when content is absent" do + msg = ToolCallMessage.new(id: "1", name: "write", arguments: { path: "lib/roast.rb" }) + assert_equal "WRITE lib/roast.rb", msg.format + end + + test "format renders a bare WRITE when the path is missing" do + msg = ToolCallMessage.new(id: "1", name: "write", arguments: {}) + assert_equal "WRITE", 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 ec1715ce..d7663e23 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",