Class: Rubino::Tools::Base

Inherits:
Object
  • Object
show all
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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#cancel_tokenObject

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_trackerObject

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_chunkObject

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_rootObject

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

.workspace_rootsObject

Every allowed root (primary + any –add-dir / /add-dir dirs). The sandbox accepts a target under ANY of these.



127
128
129
# File 'lib/rubino/tools/base.rb', line 127

def self.workspace_roots
  Workspace.roots
end

Instance Method Details

#call(arguments) ⇒ Object

Executes the tool with given arguments, returns output string

Raises:

  • (NotImplementedError)


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.

Returns:

  • (Boolean)


37
38
39
# File 'lib/rubino/tools/base.rb', line 37

def cancellation_requested?
  @cancel_token&.cancelled?
end

#config_keyObject

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

#descriptionObject

Returns a description for the LLM

Raises:

  • (NotImplementedError)


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_schemaObject

Returns the JSON schema for input parameters

Raises:

  • (NotImplementedError)


63
64
65
# File 'lib/rubino/tools/base.rb', line 63

def input_schema
  raise NotImplementedError, "#{self.class}#input_schema not implemented"
end

#nameObject

Returns the tool name (used in LLM tool definitions)

Raises:

  • (NotImplementedError)


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

def name
  raise NotImplementedError, "#{self.class}#name not implemented"
end

#risk_levelObject

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

Returns:

  • (Boolean)


78
79
80
# File 'lib/rubino/tools/base.rb', line 78

def risky?
  %i[medium high].include?(risk_level)
end

#to_tool_definitionObject

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