Skip to content
Merged
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/claude/tool_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,25 @@ def format_task
ok_line(preview)
end

# Formats a TaskOutput tool-result line.
#
# Content: the polled task's payload – an XML-ish string carrying a
# <status> (or <retrieval_status>) tag and an <output> body.
#
# Output: "TASKOUTPUT OK <status>" – the text inside <status>, or
# inside <retrieval_status> when <status> is absent. The status is
# omitted when neither tag is present, leaving a bare "TASKOUTPUT OK".
#
# Examples:
# TASKOUTPUT OK completed
# TASKOUTPUT OK pending
# TASKOUTPUT OK
#
#: () -> String
def format_taskoutput
ok_line(tag_text("status") || tag_text("retrieval_status"))
end

#: () -> String
def format_unknown
"UNKNOWN [#{tool_name}] OK #{tool_use_description}\n#{content}"
Expand Down Expand Up @@ -324,6 +343,20 @@ def error_line
"#{tool_name.to_s.upcase} ERROR #{message}".strip
end

# Returns the stripped text inside the first <tag>…</tag> pair in the
# result content, or nil when the tag is absent. The body is captured
# verbatim by a non-greedy match, so — unlike an XML parser — a body
# that itself contains bare angle brackets (such as an error message)
# is extracted intact.
#
# Matches a single, flat element: the lazy capture stops at the first
# closing </tag>, and nested same-name tags are not handled.
#
#: (String) -> String?
def tag_text(tag)
content.to_s[%r{<#{Regexp.escape(tag)}>(.*?)</#{Regexp.escape(tag)}>}m, 1]&.strip
end

# Truncates to TRUNCATE_LIMIT chars, appending "..." when cut. nil -> "".
#
#: (String?) -> String
Expand Down
48 changes: 48 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 @@ -928,6 +928,54 @@ class Claude::ToolResultTest < ActiveSupport::TestCase
assert_equal "TASK OK #{"x" * (Claude::ToolResult::TRUNCATE_LIMIT - 3)}...", output
end

test "format_taskoutput surfaces the status tag" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "taskoutput", input: {} },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "<status>completed</status><output>done</output>",
is_error: false,
)

output = tool_result.format

assert_equal "TASKOUTPUT OK completed", output
end

test "format_taskoutput falls back to retrieval_status when status is absent" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "taskoutput", input: {} },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "<retrieval_status>pending</retrieval_status>",
is_error: false,
)

output = tool_result.format

assert_equal "TASKOUTPUT OK pending", output
end

test "format_taskoutput reports a bare OK when no status tag is present" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
hash: { name: "taskoutput", input: {} },
)
tool_result = Claude::ToolResult.new(
tool_use: tool_use_message,
content: "<output>just output</output>",
is_error: false,
)

output = tool_result.format

assert_equal "TASKOUTPUT OK", output
end

test "ok_line renders a bare OK line when given no parts" do
tool_use_message = Claude::Messages::ToolUseMessage.new(
type: :tool_use,
Expand Down
Loading