Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,39 @@ def format_bash
command.empty? ? "BASH" : "BASH #{command}"
end

# Formats a read tool call.
#
# Input fields:
# :path (String) – file to read [required]
# :offset (Integer) – 1-indexed first line [optional]
# :limit (Integer) – maximum number of lines [optional]
#
# Output: "READ <path>" (:path truncated to TRUNCATE_LIMIT chars), with a
# line range appended when :offset and/or :limit is given:
# :limit set → " (lines <start>–<end>)", start = :offset (default 1),
# end = start + :limit - 1
# :offset only → " (from line <offset>)" (reads to end of file)
# With neither, the bare "READ <path>". A missing path renders "READ".
#
# Examples:
# READ lib/roast.rb (lines 30–80)
# READ lib/roast.rb (from line 30)
# READ lib/roast.rb
#
#: () -> String
def format_read
path, offset, limit = arguments.values_at(:path, :offset, :limit)
path = truncate(path)
details = if limit
offset ||= 1
"lines #{offset}–#{offset + limit - 1}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge case: with limit: 0 this renders (lines 1–0) — a backwards range (offset + limit - 1 = 1 + 0 - 1 = 0). limit: 0 isn't obviously invalid input from Pi. Minor, but a limit.positive? guard (or documenting that limit is assumed ≥1) avoids the inverted range.

elsif offset
"from line #{offset}"
end
label = path.empty? ? "READ" : "READ #{path}"
details ? "#{label} (#{details})" : label
end

# Formats a tool call for which Roast has no dedicated formatter.
#
# Output: "<NAME> <key>: <value>, ..." – the upcased tool name, then each
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ def format_bash
ok_line("#{count} #{"line".pluralize(count)}", preview)
end

# Formats a read tool result.
#
# Content: the file's text.
#
# Output: "READ OK <n> <line|lines>" – <n> is the line count (pluralized).
#
# Examples:
# READ OK 42 lines
# READ OK 1 line
# READ OK 0 lines
#
#: () -> String
def format_read

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Counting inconsistency with the later sibling formatters. This counts content.to_s.lines.length (includes blank lines). The find/ls result formatters landing later in the stack use .map(&:strip).reject(&:empty?).length (drop blanks), so content "a\n\nb" reports 3 here vs 2 there. Same "count the lines" job, two rules. Worth making deliberate now — either align, or add a comment that read reports file line count (blanks included) while entry-listing tools don't.

count = content.to_s.lines.length
ok_line("#{count} #{"line".pluralize(count)}")
end

# Formats a result for which Roast has no dedicated formatter.
#
# Content: the tool's output text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,36 @@ class ToolCallMessageTest < ActiveSupport::TestCase
assert_equal "BASH #{"x" * (ToolCallMessage::TRUNCATE_LIMIT - 3)}...", msg.format
end

test "format renders READ with the path" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: { path: "lib/roast.rb" })
assert_equal "READ lib/roast.rb", msg.format
end

test "format renders a bare READ when the path is missing" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: {})
assert_equal "READ", msg.format
end

test "format truncates a long read path" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: { path: "x" * 100 })
assert_equal "READ #{"x" * (ToolCallMessage::TRUNCATE_LIMIT - 3)}...", msg.format
end

test "format renders READ with a closed line range when offset and limit are set" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: { path: "lib/roast.rb", offset: 30, limit: 51 })
assert_equal "READ lib/roast.rb (lines 30–80)", msg.format
end

test "format defaults the read offset to 1 when only limit is given" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: { path: "lib/roast.rb", limit: 50 })
assert_equal "READ lib/roast.rb (lines 1–50)", msg.format
end

test "format renders an open-ended read range from offset when limit is absent" do
msg = ToolCallMessage.new(id: "1", name: "read", arguments: { path: "lib/roast.rb", offset: 30 })
assert_equal "READ lib/roast.rb (from line 30)", msg.format
end

test "format renders an unhandled tool as NAME key: value, ..." do
msg = ToolCallMessage.new(
id: "1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,39 @@ def setup
assert_equal "BASH OK 0 lines", msg.format(@context)
end

test "format summarizes read output with a line count" do
msg = ToolResultMessage.new(
tool_call_id: "1",
tool_name: "read",
content: "line one\nline two\nline three",
is_error: false,
)

assert_equal "READ OK 3 lines", msg.format(@context)
end

test "format pluralizes a single line of read output" do
msg = ToolResultMessage.new(
tool_call_id: "1",
tool_name: "read",
content: "just one line",
is_error: false,
)

assert_equal "READ OK 1 line", msg.format(@context)
end

test "format reports zero lines when the file is empty" do
msg = ToolResultMessage.new(
tool_call_id: "1",
tool_name: "read",
content: nil,
is_error: false,
)

assert_equal "READ OK 0 lines", msg.format(@context)
end

test "format renders NAME ERROR with the message for an error result" do
msg = ToolResultMessage.new(
tool_call_id: "1",
Expand Down
Loading