Module: RubyLLM::Toolbox::ProcessRunner

Defined in:
lib/ruby_llm/toolbox/process_runner.rb

Overview

Shared subprocess runner used by BashTool and the Docker sandbox. Always array-form (no shell), streams both pipes so a chatty child can’t deadlock on a full pipe buffer, and hard-kills the process if it blows the wall-clock budget.

Returns [stdout, stderr, status] where status is a Process::Status or the symbol :timeout.

Class Method Summary collapse

Class Method Details

.capture(argv, env: {}, stdin: nil, timeout: 30, unsetenv_others: true, chdir: nil, rlimits: {}) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/ruby_llm/toolbox/process_runner.rb', line 18

def capture(argv, env: {}, stdin: nil, timeout: 30, unsetenv_others: true, chdir: nil, rlimits: {})
  opts = { unsetenv_others: unsetenv_others }
  opts[:chdir] = chdir if chdir
  opts.merge!(rlimits) if rlimits && !rlimits.empty?
  Open3.popen3(env, *argv, **opts) do |i, o, e, thr|
    write_stdin(i, stdin)
    pump(o, e, thr, timeout)
  end
end

.kill(pid) ⇒ Object



65
66
67
68
69
# File 'lib/ruby_llm/toolbox/process_runner.rb', line 65

def kill(pid)
  Process.kill("KILL", pid)
rescue StandardError
  nil
end

.pump(stdout, stderr, wait_thr, timeout) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ruby_llm/toolbox/process_runner.rb', line 41

def pump(stdout, stderr, wait_thr, timeout)
  out = +""
  err = +""
  readers = [stdout, stderr]

  Timeout.timeout(timeout) do
    until readers.empty?
      ready, = IO.select(readers)
      ready.each do |io|
        chunk = io.read_nonblock(4096)
        (io.equal?(stdout) ? out : err) << chunk
      rescue IO::WaitReadable
        next
      rescue EOFError
        readers.delete(io)
      end
    end
    [out, err, wait_thr.value]
  end
rescue Timeout::Error
  kill(wait_thr.pid)
  [out, err, :timeout]
end

.write_stdin(io, stdin) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ruby_llm/toolbox/process_runner.rb', line 28

def write_stdin(io, stdin)
  io.write(stdin) if stdin && !stdin.empty?
rescue StandardError
  # Child may have exited/closed the pipe before we finished writing.
  nil
ensure
  begin
    io.close
  rescue StandardError
    nil
  end
end