From ab5c453590cdd9b0466a224f49a42344fe6bad74 Mon Sep 17 00:00:00 2001 From: Lasmar Khalifa Date: Thu, 2 Jul 2026 12:15:05 -0400 Subject: [PATCH] add find tool formatters --- .../pi/messages/tool_call_message.rb | 26 +++++++++ .../pi/messages/tool_result_message.rb | 26 +++++++++ .../pi/messages/tool_call_message_test.rb | 24 ++++++++ .../pi/messages/tool_result_message_test.rb | 58 +++++++++++++++++++ 4 files changed, 134 insertions(+) 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 16df6b86..29eeb10b 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 @@ -180,6 +180,32 @@ def format_grep details.any? ? "#{base} (#{details.join(", ")})" : base end + # Formats a find tool call. + # + # Input fields: + # :pattern (String) – filename glob to match [required] + # :path (String) – directory to search in [optional] + # :limit (Integer) – max number of results [optional] + # + # Output: "FIND (path: , limit: )" – the "path:" + # and "limit:" details are each included only when present and joined + # with ", " inside a single trailing "(...)". + # + # Examples: + # FIND *.rb (path: lib, limit: 50) + # FIND *.rb (limit: 50) + # FIND *.rb + # + #: () -> String + def format_find + pattern, path, limit = arguments.values_at(:pattern, :path, :limit) + details = [ + ("path: #{path}" if path.present?), + ("limit: #{limit}" if limit.present?), + ].compact + details.any? ? "FIND #{pattern} (#{details.join(", ")})" : "FIND #{pattern}" + 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 6e1bbc3a..e570df45 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 @@ -152,6 +152,32 @@ def format_grep ok_line("#{count} #{"match".pluralize(count)}", note) end + # Formats a find tool result. + # + # Content: matching paths, one per line, plus an optional status line – + # either a bracketed notice ("[2 results limit reached. ...]") or the + # no-results prose ("No files found matching pattern"). + # + # Output: "FIND OK [ · NOTE ]" – counts the + # path lines only, and the notice's brackets are dropped. As in #format_grep, + # the NOTE is shown only alongside results: "0 paths" already says what the + # no-results prose would. + # + # Examples: + # FIND OK 12 paths + # FIND OK 1 path + # FIND OK 2 paths · NOTE 2 results limit reached. Use limit=4 for m... + # FIND OK 0 paths + # + #: () -> String + def format_find + lines = content.to_s.lines.map(&:strip).reject(&:empty?) + notes, paths = lines.partition { |line| line.sub!(/\A\[(.*)\]\z/, '\1') || line.match?(/\ANo files found/) } + count = paths.length + note = "NOTE #{truncate(notes.join(" "))}" if paths.any? && notes.any? + ok_line("#{count} #{"path".pluralize(count)}", note) + 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 83a0def4..33dfb4c9 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 @@ -169,6 +169,30 @@ class ToolCallMessageTest < ActiveSupport::TestCase assert_equal "GREP \"#{"x" * (ToolCallMessage::TRUNCATE_LIMIT - 3)}...\" #{long}", msg.format end + test "format renders FIND with the search path but no limit" do + msg = ToolCallMessage.new( + id: "1", + name: "find", + arguments: { pattern: "*.rb", path: "lib" }, + ) + assert_equal "FIND *.rb (path: lib)", msg.format + end + + test "format renders FIND with the path and limit joined in one parenthetical" do + msg = ToolCallMessage.new(id: "1", name: "find", arguments: { pattern: "*.rb", path: "lib", limit: 50 }) + assert_equal "FIND *.rb (path: lib, limit: 50)", msg.format + end + + test "format renders FIND with the limit but no path" do + msg = ToolCallMessage.new(id: "1", name: "find", arguments: { pattern: "*.rb", limit: 50 }) + assert_equal "FIND *.rb (limit: 50)", msg.format + end + + test "format renders FIND with only the pattern" do + msg = ToolCallMessage.new(id: "1", name: "find", arguments: { pattern: "*.rb" }) + assert_equal "FIND *.rb", 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 92c29a80..280c09d7 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 @@ -170,6 +170,64 @@ def setup assert_equal "GREP OK 1 match · NOTE results truncated", msg.format(@context) end + test "format summarizes find output with a path count" do + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "find", + content: "lib/roast.rb\nlib/roast/version.rb\nlib/roast/cog.rb", + is_error: false, + ) + + assert_equal "FIND OK 3 paths", msg.format(@context) + end + + test "format pluralizes a single find path" do + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "find", + content: "lib/roast.rb", + is_error: false, + ) + + assert_equal "FIND OK 1 path", msg.format(@context) + end + + test "format reports zero find paths when there is no output" do + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "find", + content: nil, + is_error: false, + ) + + assert_equal "FIND OK 0 paths", msg.format(@context) + end + + test "format keeps a find limit notice out of the path count and appends it as a NOTE" do + notice = "[2 results limit reached. Use limit=4 for more, or refine pattern]" + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "find", + content: "file4.txt\nfile5.txt\n\n#{notice}", + is_error: false, + ) + + unbracketed = notice.delete_prefix("[").delete_suffix("]") + truncated_notice = "#{unbracketed[0...ToolResultMessage::TRUNCATE_LIMIT - 3]}..." + assert_equal "FIND OK 2 paths · NOTE #{truncated_notice}", msg.format(@context) + end + + test "format reports zero find paths when the output is the no-results notice" do + msg = ToolResultMessage.new( + tool_call_id: "1", + tool_name: "find", + content: "No files found matching pattern", + is_error: false, + ) + + assert_equal "FIND OK 0 paths", msg.format(@context) + end + test "format renders NAME ERROR with the message for an error result" do msg = ToolResultMessage.new( tool_call_id: "1",