Class: Rubino::Tools::Result

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/tools/result.rb

Overview

Encapsulates the result of a tool execution.

Constant Summary collapse

EMPTY_OUTPUT_PLACEHOLDER =

Substituted when a tool legitimately produces no output (e.g. ‘touch`). The string survives persistence and load_history, where nil/“” would be dropped and leave a tool_call orphaned — the provider then 400s the next turn for a tool_call with no matching tool_result.

"(no output)"
DENIED_OUTPUTS =

Model-facing text per denial reason (#143). Only a real human decision may read “denied by user” — an automatic denial must name the policy that fired, otherwise a child agent reports (and propagates upward) that “the user denied my tools” when no human ever decided anything.

{
  user: "Tool execution denied by user.",
  policy: "Tool execution denied by policy (not by the user).",
  hardline: "Tool execution blocked by policy (hardline safety floor, not by the user): " \
            "this command is never allowed.",
  permission_rule: "Tool execution blocked by policy (a configured permissions deny rule, " \
                   "not by the user).",
  doom_loop: "Tool execution blocked by the doom-loop guard (policy, not by the user): " \
             "this exact call was already made repeatedly. Change strategy instead of " \
             "retrying it — e.g. wait for the background-task completion notice instead " \
             "of polling."
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, call_id:, output:, status:, error: nil, metrics: nil, error_code: nil, artifact: nil) ⇒ Result

‘error_code` is an optional Symbol surface for callers (UI badges, automation, future contract tests) that want to branch on the failure mode without parsing the human-facing error string. Today the canonical signal is still the output text — the symbol is a belt-and-suspenders next to it, not a replacement.

‘artifact` is an optional Hash carrying { path:, filename:, content_type:, byte_size: } when a tool produced a downloadable user-facing file. The agent loop reads this and emits an ARTIFACT_CREATED bus event so SSE consumers (the web UI, the CLI) can offer a download.



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/rubino/tools/result.rb', line 21

def initialize(name:, call_id:, output:, status:, error: nil,
               metrics: nil, error_code: nil, artifact: nil)
  @name = name
  @call_id = call_id
  @output = output
  @status = status
  @error = error
  @metrics = metrics
  @error_code = error_code
  @artifact = artifact
  @session_id = nil
end

Instance Attribute Details

#artifactObject (readonly)

Returns the value of attribute artifact.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def artifact
  @artifact
end

#call_idObject (readonly)

Returns the value of attribute call_id.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def call_id
  @call_id
end

#errorObject (readonly)

Returns the value of attribute error.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def error
  @error
end

#error_codeObject (readonly)

Returns the value of attribute error_code.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def error_code
  @error_code
end

#metricsObject (readonly)

Returns the value of attribute metrics.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def metrics
  @metrics
end

#nameObject (readonly)

Returns the value of attribute name.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def name
  @name
end

#outputObject (readonly)

Returns the value of attribute output.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def output
  @output
end

#session_idObject (readonly)

Returns the value of attribute session_id.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def session_id
  @session_id
end

#statusObject (readonly)

Returns the value of attribute status.



7
8
9
# File 'lib/rubino/tools/result.rb', line 7

def status
  @status
end

Class Method Details

.denied(name:, call_id:, reason: :user) ⇒ Object



102
103
104
105
# File 'lib/rubino/tools/result.rb', line 102

def self.denied(name:, call_id:, reason: :user)
  key = DENIED_OUTPUTS.key?(reason) ? reason : :policy
  new(name: name, call_id: call_id, output: DENIED_OUTPUTS[key], status: :denied)
end

.error(name:, call_id:, error:, error_code: nil) ⇒ Object



78
79
80
81
82
83
# File 'lib/rubino/tools/result.rb', line 78

def self.error(name:, call_id:, error:, error_code: nil)
  msg = error.to_s
  msg = "unknown error" if msg.empty?
  new(name: name, call_id: call_id, output: "Error: #{msg}", status: :error,
      error: error, error_code: error_code)
end

.normalize_output(output) ⇒ Object



107
108
109
110
# File 'lib/rubino/tools/result.rb', line 107

def self.normalize_output(output)
  text = output.to_s
  text.empty? ? EMPTY_OUTPUT_PLACEHOLDER : text
end

.success(name:, call_id:, output:, metrics: nil, error_code: nil, artifact: nil) ⇒ Object

Factory methods



73
74
75
76
# File 'lib/rubino/tools/result.rb', line 73

def self.success(name:, call_id:, output:, metrics: nil, error_code: nil, artifact: nil)
  new(name: name, call_id: call_id, output: normalize_output(output),
      status: :success, metrics: metrics, error_code: error_code, artifact: artifact)
end

Instance Method Details

#denied?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/rubino/tools/result.rb', line 42

def denied?
  @status == :denied
end

#errorish?Boolean

True when this result represents a failure for DISPLAY purposes, even when the tool didn’t raise. Many tools (read, edit, …) report a soft failure by RETURNING an “Error: …” string (status stays :success) or by setting an error_code, instead of raising. The CLI used to render those as a green “✓ done” because it only checked #success?. This is the single predicate the UI uses so an errored tool shows “✗” regardless of which failure convention the tool used.

Returns:

  • (Boolean)


53
54
55
56
57
58
# File 'lib/rubino/tools/result.rb', line 53

def errorish?
  return true unless success?
  return true unless @error_code.nil?

  @output.to_s.start_with?("Error:")
end

#failed?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'lib/rubino/tools/result.rb', line 38

def failed?
  @status == :error
end

#success?Boolean

Returns:

  • (Boolean)


34
35
36
# File 'lib/rubino/tools/result.rb', line 34

def success?
  @status == :success
end

#truncated_preview(max_length: 80) ⇒ Object

Returns a truncated preview for display



61
62
63
64
# File 'lib/rubino/tools/result.rb', line 61

def truncated_preview(max_length: 80)
  text = @output.to_s
  text.length > max_length ? "#{text[0...max_length]}..." : text
end