Class: RubyLLM::CodeMode

Inherits:
Tool
  • Object
show all
Defined in:
lib/ruby_llm/code_mode.rb

Defined Under Namespace

Classes: Mount

Constant Summary collapse

VERSION =
"0.1.1"
DEFAULT_TIMEOUT_MS =
30_000
DEFAULT_FUEL_MS =
10_000
FIXED_DESCRIPTION =
"Executes Ruby code inside a secure sandbox and returns what it produced.\n\n## How to use\n\nPass a complete, self-contained Ruby script as the `code` argument. It runs\nas a fresh Ruby 4.0 process with:\n- no network, no threads, no subprocesses, and no access to the host\n  filesystem outside the mounted folders listed below;\n- a private writable scratch directory at `/work`, wiped between executions;\n- no state carried over between executions, except the contents of\n  read-write folders.\n\nCapture progress with `puts` (returned as `stdout`) and end with a value or\na JSON-serializable data structure (returned as `value`; non-serializable\nobjects come back as their inspect string). If the code raises, the error\nclass, message and backtrace are returned so you can fix and retry.\n".freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build_description ⇒ Object



66
67
68
69
70
71
72
73
# File 'lib/ruby_llm/code_mode.rb', line 66

def build_description
  read_only, read_write = mounts.partition { |m| m.mode == :read_only }
  [
    FIXED_DESCRIPTION.rstrip,
    folder_section("## Read-only folders (readable, never writable)", read_only),
    folder_section("## Read-write folders (readable and writable)", read_write)
  ].compact.join("\n\n")
end

.configuration ⇒ Object



58
59
60
61
62
63
64
# File 'lib/ruby_llm/code_mode.rb', line 58

def configuration
  @configuration ||= SecurityBox::Configuration.build(
    timeout_ms: DEFAULT_TIMEOUT_MS,
    fuel_ms: DEFAULT_FUEL_MS,
    mounts: mounts.map(&:payload)
  )
end

.description(text = nil) ⇒ Object

Raises:

  • (ArgumentError)


75
76
77
78
79
80
81
# File 'lib/ruby_llm/code_mode.rb', line 75

def description(text = nil)
  raise ArgumentError,
        "RubyLLM::CodeMode builds the tool description from the declared mounts; " \
        "the description is fixed and cannot be set by hand" if text

  build_description
end

.inherited(subclass) ⇒ Object



87
88
89
90
91
# File 'lib/ruby_llm/code_mode.rb', line 87

def inherited(subclass)
  super
  subclass.instance_variable_set(:@mounts, mounts.dup)
  subclass.instance_variable_set(:@configuration, nil)
end

.mount(source:, dest:, description: nil) ⇒ Object



46
47
48
# File 'lib/ruby_llm/code_mode.rb', line 46

def mount(source:, dest:, description: nil)
  add_mount(source, dest, :read_only, description)
end

.mount_rw(source:, dest:, description: nil) ⇒ Object



50
51
52
# File 'lib/ruby_llm/code_mode.rb', line 50

def mount_rw(source:, dest:, description: nil)
  add_mount(source, dest, :read_write, description)
end

.mounts ⇒ Object



54
55
56
# File 'lib/ruby_llm/code_mode.rb', line 54

def mounts
  @mounts ||= []
end

.tool_name ⇒ Object



39
40
41
42
43
44
# File 'lib/ruby_llm/code_mode.rb', line 39

def tool_name
  leaf = name.to_s.split('::').last
  return '' unless leaf

  RubyLLM::Support::Utils.underscore(leaf).delete_suffix('_tool')
end

.warmup ⇒ Object



83
84
85
# File 'lib/ruby_llm/code_mode.rb', line 83

def warmup
  SecurityBox.warmup(timeout_ms: DEFAULT_TIMEOUT_MS, fuel_ms: DEFAULT_FUEL_MS)
end

Instance Method Details

#execute(code:) ⇒ Object



123
124
125
126
127
# File 'lib/ruby_llm/code_mode.rb', line 123

def execute(code:)
  format_result(sandbox.eval(code))
rescue SecurityBox::Error => e
  { status: "sandbox_error", error: { class: e.class.name, message: e.message } }
end