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
24 changes: 24 additions & 0 deletions lib/roast/cogs/agent/providers/claude/tool_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,30 @@ def format_read
ok_line("#{count} #{"line".pluralize(count)}")
end

# Formats a WebFetch tool-result line.
#
# Input: :url – the URL that was fetched.
# Content: the answer the model produced from the fetched page. A page
# that could not be retrieved (e.g. an HTTP 404) still comes back as
# a non-error result whose content describes what happened.
#
# Output: "WEBFETCH OK <url> · <preview>" – the fetched :url followed
# by the first line of that answer, stripped and truncated to
# TRUNCATE_LIMIT chars. Either part is omitted when absent (no url, or
# no content), collapsing to just the other; :url is not truncated, as
# it is the identity of the fetch.
#
# Examples:
# WEBFETCH OK https://example.com · The main heading is "Example Domain".
# WEBFETCH OK https://example.com/missing · The server returned HTTP 404...
# WEBFETCH OK https://example.com
#
#: () -> String
def format_webfetch
preview = truncate(content.to_s.lines.first.to_s.strip)
ok_line(tool_use_input[:url], preview)
end

# Formats a Glob tool-result line.
#
# Content: newline-separated matches. Lines starting with "/" are
Expand Down
21 changes: 21 additions & 0 deletions lib/roast/cogs/agent/providers/claude/tool_use.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ def format_read
details ? "#{label} (#{details})" : label
end

# Formats a WebFetch tool-use line.
#
# Input fields:
# :url (String) – the URL to fetch [required]
# :prompt (String) – what to extract/answer from the page [required]
#
# Output: "WEBFETCH <url>", with " (<prompt>)" appended when :prompt is
# present. :prompt is truncated to TRUNCATE_LIMIT chars; :url is not, as
# it is the identity of the fetch (like a file path in a Read).
#
# Examples:
# WEBFETCH https://example.com (What is the main heading?)
# WEBFETCH https://example.com
#
#: () -> String
def format_webfetch
url, prompt = input.values_at(:url, :prompt)
label = url.to_s.empty? ? "WEBFETCH" : "WEBFETCH #{url}"
prompt ? "#{label} (#{truncate(prompt)})" : label
end

# Formats a Glob tool-use line.
#
# Input fields:
Expand Down
81 changes: 81 additions & 0 deletions test/roast/cogs/agent/providers/claude/tool_result_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,87 @@ class Claude::ToolResultTest < ActiveSupport::TestCase
assert_equal "READ OK 0 lines", output
end

test "format_webfetch shows the fetched url and previews the first line of the answer" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "webfetch", input: { url: "https://example.com", prompt: "What is the main heading?" } },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "The main heading is \"Example Domain\".\n\nIt appears twice on the page.",
is_error: false,
)

output = tool_result.format

assert_equal "WEBFETCH OK https://example.com · The main heading is \"Example Domain\".", output
end

test "format_webfetch previews a failed fetch that came back as a non-error result" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "webfetch", input: { url: "https://example.com/missing" } },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "The server returned HTTP 404 Not Found.\n\nThe response body was not retrieved.",
is_error: false,
)

output = tool_result.format

assert_equal "WEBFETCH OK https://example.com/missing · The server returned HTTP 404 Not Found.", output
end

test "format_webfetch truncates the preview but not the url" do
long_url = "https://example.com/#{"a" * (Claude::ToolResult::TRUNCATE_LIMIT + 10)}"
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "webfetch", input: { url: long_url } },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "x" * 60,
is_error: false,
)

output = tool_result.format

assert_equal "WEBFETCH OK #{long_url} · #{"x" * (Claude::ToolResult::TRUNCATE_LIMIT - 3)}...", output
end

test "format_webfetch shows the url alone when the content is nil" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "webfetch", input: { url: "https://example.com" } },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: nil,
is_error: false,
)

output = tool_result.format

assert_equal "WEBFETCH OK https://example.com", output
end

test "format_webfetch collapses to a bare line when both url and content are absent" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "webfetch", input: {} },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "",
is_error: false,
)

output = tool_result.format

assert_equal "WEBFETCH OK", output
end

test "format_glob reports the number of matched files" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
Expand Down
37 changes: 37 additions & 0 deletions test/roast/cogs/agent/providers/claude/tool_use_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,43 @@ class Claude::ToolUseTest < ActiveSupport::TestCase
assert_equal "READ", output
end

# format_webfetch

test "format_webfetch appends the prompt in parentheses" do
tool_use = Claude::ToolUse.new(name: :webfetch, input: { url: "https://example.com", prompt: "What is the main heading?" })

output = tool_use.format

assert_equal "WEBFETCH https://example.com (What is the main heading?)", output
end

test "format_webfetch renders the url only when no prompt given" do
tool_use = Claude::ToolUse.new(name: :webfetch, input: { url: "https://example.com" })

output = tool_use.format

assert_equal "WEBFETCH https://example.com", output
end

test "format_webfetch truncates the prompt but not the url" do
long_url = "https://example.com/#{"a" * (Claude::ToolUse::TRUNCATE_LIMIT + 10)}"
long_prompt = "b" * (Claude::ToolUse::TRUNCATE_LIMIT + 10)
truncated = "#{"b" * (Claude::ToolUse::TRUNCATE_LIMIT - 3)}..."
tool_use = Claude::ToolUse.new(name: :webfetch, input: { url: long_url, prompt: long_prompt })

output = tool_use.format

assert_equal "WEBFETCH #{long_url} (#{truncated})", output
end

test "format_webfetch has no trailing space when the url is absent" do
tool_use = Claude::ToolUse.new(name: :webfetch, input: {})

output = tool_use.format

assert_equal "WEBFETCH", output
end

# format_glob

test "format_glob renders the pattern only when no path given" do
Expand Down
Loading