Class: Rubino::Tools::Base
- Inherits:
-
Object
- Object
- Rubino::Tools::Base
- Defined in:
- lib/rubino/tools/base.rb
Overview
Abstract base class for all tools. Each tool must implement: name, description, input_schema, risk_level, call.
Direct Known Subclasses
MCP::MCPToolWrapper, Skills::SkillTool, AnswerChildTool, AskParentTool, AttachFileTool, EditTool, GitHubTool, GitTool, GlobTool, GrepTool, MemoryTool, MultiEditTool, PatchTool, ProbeTool, QuestionTool, ReadAttachmentTool, ReadTool, RubyTool, SessionSearchTool, ShellInputTool, ShellKillTool, ShellOutputTool, ShellTailTool, ShellTool, SteerTool, SummarizeFileTool, TaskResultTool, TaskStopTool, TaskTool, TestTool, TodoTool, VisionTool, WebFetchTool, WebSearchTool, WriteTool
Instance Attribute Summary collapse
-
#cancel_token ⇒ Object
Set by ToolExecutor before each call so long-running tools (shell, http, watchers) can poll for user cancellation.
-
#read_tracker ⇒ Object
Session-scoped ReadTracker injected by ToolExecutor.
-
#stream_chunk ⇒ Object
Optional Proc, injected by ToolExecutor, that the tool can call with incremental output chunks during a long-running call.
Class Method Summary collapse
-
.workspace_root ⇒ Object
Filesystem sandbox for write/edit/delete operations.
-
.workspace_roots ⇒ Object
Every allowed root (primary + any –add-dir / /add-dir dirs).
Instance Method Summary collapse
-
#call(arguments) ⇒ Object
Executes the tool with given arguments, returns output string.
-
#cancellation_requested? ⇒ Boolean
True when the user has requested cancellation.
-
#config_key ⇒ Object
The ‘tools.<key>` config gate that enables/disables this tool.
-
#description ⇒ Object
Returns a description for the LLM.
-
#emit_chunk(text) ⇒ Object
Convenience guard so tools don’t sprinkle nil-checks at every emit.
-
#input_schema ⇒ Object
Returns the JSON schema for input parameters.
-
#name ⇒ Object
Returns the tool name (used in LLM tool definitions).
-
#risk_level ⇒ Object
Returns the risk level: :low, :medium, :high.
-
#risky? ⇒ Boolean
Returns true if this tool requires user confirmation.
-
#to_tool_definition ⇒ Object
Returns the tool definition for LLM registration.
Instance Attribute Details
#cancel_token ⇒ Object
Set by ToolExecutor before each call so long-running tools (shell, http, watchers) can poll for user cancellation. Default is nil — the tool should treat that as “no cancellation possible” and not crash.
11 12 13 |
# File 'lib/rubino/tools/base.rb', line 11 def cancel_token @cancel_token end |
#read_tracker ⇒ Object
Session-scoped ReadTracker injected by ToolExecutor. ReadTool registers successful reads; EditTool / MultiEditTool consult it before writing so they can refuse to edit a file the model never opened in this session. Nil-tolerant: tools that don’t care just ignore it.
18 19 20 |
# File 'lib/rubino/tools/base.rb', line 18 def read_tracker @read_tracker end |
#stream_chunk ⇒ Object
Optional Proc, injected by ToolExecutor, that the tool can call with incremental output chunks during a long-running call. ShellTool uses this to stream stdout/stderr lines as the subprocess writes them instead of dumping everything at end-of-command. Nil-tolerant: a tool with no streamable output (read, edit, glob) just ignores it.
25 26 27 |
# File 'lib/rubino/tools/base.rb', line 25 def stream_chunk @stream_chunk end |
Class Method Details
.workspace_root ⇒ Object
Filesystem sandbox for write/edit/delete operations.
Defaults to Dir.pwd, overridable via terminal.cwd in config. Mutating tools must call within_workspace? before touching the disk so a prompt injection that asks for ‘file_path: “/etc/passwd”` is refused at the tool boundary, before the approval prompt even sees the path.
The check resolves every symlink with File.realpath before comparing against the workspace root: dropping a ‘link → /etc` inside the workspace and writing through it used to bypass the boundary because expand_path alone never crosses the symlink. realpath walks the filesystem and gives us the canonical destination, so an in-workspace path that ultimately points outside is rejected like any other escape. For non-existent targets (write-creates-new-file) we resolve the deepest existing ancestor and re-attach the remainder — the new file will land at that ancestor, so the ancestor is what we sandbox.
Set tools.workspace_strict=false in config.yml to disable globally (the agent then trusts the model + the approval flow alone). The directory tools sandbox to. Exposed as a class method so the File API operations can root their Workspace at the SAME place (otherwise produced artifacts under this root look like traversal escapes relative to paths_home and the download 422s). The PRIMARY root — terminal.cwd or the launch cwd. Kept as the single source of truth for “the” directory: the @-picker, shell/test cwd, the File API workspace and the attachment downloader all root here so they agree. The write/edit SANDBOX, however, spans every root (see #within_workspace?) so an added dir is also writable.
121 122 123 |
# File 'lib/rubino/tools/base.rb', line 121 def self.workspace_root Workspace.primary_root end |
Instance Method Details
#call(arguments) ⇒ Object
Executes the tool with given arguments, returns output string
73 74 75 |
# File 'lib/rubino/tools/base.rb', line 73 def call(arguments) raise NotImplementedError, "#{self.class}#call not implemented" end |
#cancellation_requested? ⇒ Boolean
True when the user has requested cancellation. Cheap, lock-protected. Use in tight loops; on true, terminate gracefully and either return an “interrupted” string or raise Rubino::Interrupted.
37 38 39 |
# File 'lib/rubino/tools/base.rb', line 37 def cancellation_requested? @cancel_token&.cancelled? end |
#config_key ⇒ Object
The ‘tools.<key>` config gate that enables/disables this tool. Single source of truth shared with Registry#tool_enabled_in_config? and the `tools` CLI command, so the displayed state always matches the state the registry actually enforces. Defaults to the tool’s own name; tools whose config key differs (webfetch/websearch both gate on ‘tools.web`) override this. Returning a key absent from config means the tool is enabled (opt-out model).
53 54 55 |
# File 'lib/rubino/tools/base.rb', line 53 def config_key name end |
#description ⇒ Object
Returns a description for the LLM
58 59 60 |
# File 'lib/rubino/tools/base.rb', line 58 def description raise NotImplementedError, "#{self.class}#description not implemented" end |
#emit_chunk(text) ⇒ Object
Convenience guard so tools don’t sprinkle nil-checks at every emit.
28 29 30 31 32 |
# File 'lib/rubino/tools/base.rb', line 28 def emit_chunk(text) return if text.nil? || text.to_s.empty? @stream_chunk&.call(text.to_s) end |
#input_schema ⇒ Object
Returns the JSON schema for input parameters
63 64 65 |
# File 'lib/rubino/tools/base.rb', line 63 def input_schema raise NotImplementedError, "#{self.class}#input_schema not implemented" end |
#name ⇒ Object
Returns the tool name (used in LLM tool definitions)
42 43 44 |
# File 'lib/rubino/tools/base.rb', line 42 def name raise NotImplementedError, "#{self.class}#name not implemented" end |
#risk_level ⇒ Object
Returns the risk level: :low, :medium, :high
68 69 70 |
# File 'lib/rubino/tools/base.rb', line 68 def risk_level :low end |
#risky? ⇒ Boolean
Returns true if this tool requires user confirmation
78 79 80 |
# File 'lib/rubino/tools/base.rb', line 78 def risky? %i[medium high].include?(risk_level) end |
#to_tool_definition ⇒ Object
Returns the tool definition for LLM registration
83 84 85 86 87 88 89 |
# File 'lib/rubino/tools/base.rb', line 83 def to_tool_definition { name: name, description: description, parameters: input_schema } end |