shared_tools 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +594 -42
  4. data/lib/shared_tools/{ruby_llm/mcp → mcp}/github_mcp_server.rb +31 -24
  5. data/lib/shared_tools/mcp/imcp.rb +28 -0
  6. data/lib/shared_tools/mcp/tavily_mcp_server.rb +44 -0
  7. data/lib/shared_tools/mcp.rb +24 -0
  8. data/lib/shared_tools/tools/browser/base_driver.rb +64 -0
  9. data/lib/shared_tools/tools/browser/base_tool.rb +50 -0
  10. data/lib/shared_tools/tools/browser/click_tool.rb +54 -0
  11. data/lib/shared_tools/tools/browser/elements/element_grouper.rb +73 -0
  12. data/lib/shared_tools/tools/browser/elements/nearby_element_detector.rb +109 -0
  13. data/lib/shared_tools/tools/browser/formatters/action_formatter.rb +37 -0
  14. data/lib/shared_tools/tools/browser/formatters/data_entry_formatter.rb +135 -0
  15. data/lib/shared_tools/tools/browser/formatters/element_formatter.rb +52 -0
  16. data/lib/shared_tools/tools/browser/formatters/input_formatter.rb +59 -0
  17. data/lib/shared_tools/tools/browser/inspect_tool.rb +87 -0
  18. data/lib/shared_tools/tools/browser/inspect_utils.rb +51 -0
  19. data/lib/shared_tools/tools/browser/page_inspect/button_summarizer.rb +140 -0
  20. data/lib/shared_tools/tools/browser/page_inspect/form_summarizer.rb +98 -0
  21. data/lib/shared_tools/tools/browser/page_inspect/html_summarizer.rb +37 -0
  22. data/lib/shared_tools/tools/browser/page_inspect/link_summarizer.rb +103 -0
  23. data/lib/shared_tools/tools/browser/page_inspect_tool.rb +55 -0
  24. data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +39 -0
  25. data/lib/shared_tools/tools/browser/selector_generator/base_selectors.rb +28 -0
  26. data/lib/shared_tools/tools/browser/selector_generator/contextual_selectors.rb +140 -0
  27. data/lib/shared_tools/tools/browser/selector_generator.rb +73 -0
  28. data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +67 -0
  29. data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +45 -0
  30. data/lib/shared_tools/tools/browser/visit_tool.rb +43 -0
  31. data/lib/shared_tools/tools/browser/watir_driver.rb +132 -0
  32. data/lib/shared_tools/tools/browser.rb +27 -0
  33. data/lib/shared_tools/tools/browser_tool.rb +255 -0
  34. data/lib/shared_tools/tools/calculator_tool.rb +169 -0
  35. data/lib/shared_tools/tools/composite_analysis_tool.rb +520 -0
  36. data/lib/shared_tools/tools/computer/base_driver.rb +177 -0
  37. data/lib/shared_tools/tools/computer/mac_driver.rb +103 -0
  38. data/lib/shared_tools/tools/computer.rb +21 -0
  39. data/lib/shared_tools/tools/computer_tool.rb +207 -0
  40. data/lib/shared_tools/tools/data_science_kit.rb +707 -0
  41. data/lib/shared_tools/tools/database/base_driver.rb +17 -0
  42. data/lib/shared_tools/tools/database/postgres_driver.rb +30 -0
  43. data/lib/shared_tools/tools/database/sqlite_driver.rb +29 -0
  44. data/lib/shared_tools/tools/database.rb +9 -0
  45. data/lib/shared_tools/tools/database_query_tool.rb +313 -0
  46. data/lib/shared_tools/tools/database_tool.rb +99 -0
  47. data/lib/shared_tools/tools/devops_toolkit.rb +420 -0
  48. data/lib/shared_tools/tools/disk/base_driver.rb +91 -0
  49. data/lib/shared_tools/tools/disk/base_tool.rb +20 -0
  50. data/lib/shared_tools/tools/disk/directory_create_tool.rb +39 -0
  51. data/lib/shared_tools/tools/disk/directory_delete_tool.rb +39 -0
  52. data/lib/shared_tools/tools/disk/directory_list_tool.rb +37 -0
  53. data/lib/shared_tools/tools/disk/directory_move_tool.rb +40 -0
  54. data/lib/shared_tools/tools/disk/file_create_tool.rb +38 -0
  55. data/lib/shared_tools/tools/disk/file_delete_tool.rb +40 -0
  56. data/lib/shared_tools/tools/disk/file_move_tool.rb +43 -0
  57. data/lib/shared_tools/tools/disk/file_read_tool.rb +40 -0
  58. data/lib/shared_tools/tools/disk/file_replace_tool.rb +44 -0
  59. data/lib/shared_tools/tools/disk/file_write_tool.rb +40 -0
  60. data/lib/shared_tools/tools/disk/local_driver.rb +91 -0
  61. data/lib/shared_tools/tools/disk.rb +17 -0
  62. data/lib/shared_tools/tools/disk_tool.rb +132 -0
  63. data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +79 -0
  64. data/lib/shared_tools/tools/doc.rb +8 -0
  65. data/lib/shared_tools/tools/doc_tool.rb +109 -0
  66. data/lib/shared_tools/tools/docker/base_tool.rb +56 -0
  67. data/lib/shared_tools/tools/docker/compose_run_tool.rb +77 -0
  68. data/lib/shared_tools/tools/docker.rb +8 -0
  69. data/lib/shared_tools/tools/error_handling_tool.rb +403 -0
  70. data/lib/shared_tools/tools/eval/python_eval_tool.rb +209 -0
  71. data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +93 -0
  72. data/lib/shared_tools/tools/eval/shell_eval_tool.rb +64 -0
  73. data/lib/shared_tools/tools/eval.rb +10 -0
  74. data/lib/shared_tools/tools/eval_tool.rb +139 -0
  75. data/lib/shared_tools/tools/secure_tool_template.rb +353 -0
  76. data/lib/shared_tools/tools/version.rb +7 -0
  77. data/lib/shared_tools/tools/weather_tool.rb +197 -0
  78. data/lib/shared_tools/tools/workflow_manager_tool.rb +312 -0
  79. data/lib/shared_tools/tools.rb +16 -0
  80. data/lib/shared_tools/version.rb +1 -1
  81. data/lib/shared_tools.rb +9 -33
  82. metadata +189 -68
  83. data/lib/shared_tools/llm_rb/run_shell_command.rb +0 -23
  84. data/lib/shared_tools/llm_rb.rb +0 -9
  85. data/lib/shared_tools/omniai.rb +0 -9
  86. data/lib/shared_tools/raix/what_is_the_weather.rb +0 -18
  87. data/lib/shared_tools/raix.rb +0 -9
  88. data/lib/shared_tools/ruby_llm/edit_file.rb +0 -71
  89. data/lib/shared_tools/ruby_llm/incomplete/calculator_tool.rb +0 -70
  90. data/lib/shared_tools/ruby_llm/incomplete/composite_analysis_tool.rb +0 -89
  91. data/lib/shared_tools/ruby_llm/incomplete/data_science_kit.rb +0 -128
  92. data/lib/shared_tools/ruby_llm/incomplete/database_query_tool.rb +0 -100
  93. data/lib/shared_tools/ruby_llm/incomplete/devops_toolkit.rb +0 -112
  94. data/lib/shared_tools/ruby_llm/incomplete/error_handling_tool.rb +0 -109
  95. data/lib/shared_tools/ruby_llm/incomplete/secure_tool_template.rb +0 -117
  96. data/lib/shared_tools/ruby_llm/incomplete/weather_tool.rb +0 -110
  97. data/lib/shared_tools/ruby_llm/incomplete/workflow_manager_tool.rb +0 -145
  98. data/lib/shared_tools/ruby_llm/list_files.rb +0 -49
  99. data/lib/shared_tools/ruby_llm/mcp/imcp.rb +0 -33
  100. data/lib/shared_tools/ruby_llm/mcp.rb +0 -10
  101. data/lib/shared_tools/ruby_llm/pdf_page_reader.rb +0 -59
  102. data/lib/shared_tools/ruby_llm/python_eval.rb +0 -194
  103. data/lib/shared_tools/ruby_llm/read_file.rb +0 -40
  104. data/lib/shared_tools/ruby_llm/ruby_eval.rb +0 -77
  105. data/lib/shared_tools/ruby_llm/run_shell_command.rb +0 -49
  106. data/lib/shared_tools/ruby_llm.rb +0 -12
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module SharedTools
5
+ module Tools
6
+ module Eval
7
+ # @example
8
+ # tool = SharedTools::Tools::Eval::PythonEvalTool.new
9
+ # tool.execute(code: "print('Hello'); result = 2 + 2")
10
+ class PythonEvalTool < ::RubyLLM::Tool
11
+ def self.name = 'eval_python'
12
+
13
+ description <<~DESCRIPTION
14
+ Execute Python source code safely and return the result.
15
+
16
+ This tool evaluates Python code by writing it to a temporary file
17
+ and executing it with the python3 command, capturing both stdout
18
+ and the final expression result.
19
+
20
+ WARNING: This tool executes arbitrary Python code. Use with caution.
21
+ NOTE: Requires python3 to be available in the system PATH.
22
+ DESCRIPTION
23
+
24
+ params do
25
+ string :code, description: "The Python code to execute"
26
+ end
27
+
28
+ # @param logger [Logger] optional logger
29
+ def initialize(logger: nil)
30
+ @logger = logger || RubyLLM.logger
31
+ end
32
+
33
+ # @param code [String] Python code to execute
34
+ #
35
+ # @return [Hash] execution result
36
+ def execute(code:)
37
+ @logger.info("Requesting permission to execute Python code")
38
+
39
+ if code.strip.empty?
40
+ error_msg = "Python code cannot be empty"
41
+ @logger.error(error_msg)
42
+ return { error: error_msg }
43
+ end
44
+
45
+ # Show user the code and ask for confirmation
46
+ allowed = SharedTools.execute?(tool: self.class.to_s, stuff: code)
47
+
48
+ unless allowed
49
+ @logger.warn("User declined to execute the Python code")
50
+ return { error: "User declined to execute the Python code" }
51
+ end
52
+
53
+ @logger.info("Executing Python code")
54
+
55
+ begin
56
+ require 'tempfile'
57
+ require 'open3'
58
+ require 'json'
59
+
60
+ # Create a Python script that captures both output and result
61
+ python_script = create_python_wrapper(code)
62
+
63
+ # Write to temporary file
64
+ temp_file = Tempfile.new(['python_eval', '.py'])
65
+ temp_file.write(python_script)
66
+ temp_file.flush
67
+
68
+ # Execute the Python script
69
+ stdout, stderr, status = Open3.capture3("python3", temp_file.path)
70
+
71
+ temp_file.close
72
+ temp_file.unlink
73
+
74
+ if status.success?
75
+ @logger.debug("Python code execution completed successfully")
76
+ parse_python_output(stdout)
77
+ else
78
+ @logger.error("Python code execution failed: #{stderr}")
79
+ {
80
+ error: stderr.strip,
81
+ success: false
82
+ }
83
+ end
84
+ rescue => e
85
+ @logger.error("Failed to execute Python code: #{e.message}")
86
+ {
87
+ error: e.message,
88
+ backtrace: e.backtrace&.first(5),
89
+ success: false
90
+ }
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def create_python_wrapper(user_code)
97
+ require 'base64'
98
+ encoded_code = Base64.strict_encode64(user_code)
99
+
100
+ <<~PYTHON
101
+ import sys
102
+ import json
103
+ import io
104
+ import base64
105
+ from contextlib import redirect_stdout
106
+
107
+ # Decode the user code
108
+ user_code = base64.b64decode('#{encoded_code}').decode('utf-8')
109
+
110
+ # Capture stdout
111
+ captured_output = io.StringIO()
112
+
113
+ try:
114
+ with redirect_stdout(captured_output):
115
+ # Handle compound statements (semicolon-separated)
116
+ if ';' in user_code and not user_code.strip().startswith('for ') and not user_code.strip().startswith('if '):
117
+ # Split by semicolon, execute all but last, eval the last
118
+ parts = [part.strip() for part in user_code.split(';') if part.strip()]
119
+ if len(parts) > 1:
120
+ for part in parts[:-1]:
121
+ exec(part)
122
+ # Try to eval the last part
123
+ try:
124
+ result = eval(parts[-1])
125
+ except SyntaxError:
126
+ exec(parts[-1])
127
+ result = None
128
+ else:
129
+ # Single part, try eval then exec
130
+ try:
131
+ result = eval(parts[0])
132
+ except SyntaxError:
133
+ exec(parts[0])
134
+ result = None
135
+ else:
136
+ # Try to evaluate as expression first
137
+ try:
138
+ result = eval(user_code)
139
+ except SyntaxError:
140
+ # If not an expression, execute as statement
141
+ exec(user_code)
142
+ result = None
143
+
144
+ output = captured_output.getvalue()
145
+
146
+ # Prepare result for JSON serialization
147
+ try:
148
+ json.dumps(result) # Test if result is JSON serializable
149
+ serializable_result = result
150
+ except (TypeError, ValueError):
151
+ serializable_result = str(result)
152
+
153
+ result_data = {
154
+ "success": True,
155
+ "result": serializable_result,
156
+ "output": output if output else None,
157
+ "python_type": type(result).__name__
158
+ }
159
+
160
+ print("PYTHON_EVAL_RESULT:", json.dumps(result_data))
161
+
162
+ except Exception as e:
163
+ error_data = {
164
+ "success": False,
165
+ "error": str(e),
166
+ "error_type": type(e).__name__
167
+ }
168
+ print("PYTHON_EVAL_RESULT:", json.dumps(error_data))
169
+ PYTHON
170
+ end
171
+
172
+ def parse_python_output(stdout)
173
+ lines = stdout.split("\n")
174
+ result_line = lines.find { |line| line.start_with?("PYTHON_EVAL_RESULT:") }
175
+
176
+ if result_line
177
+ json_data = result_line.sub("PYTHON_EVAL_RESULT:", "").strip
178
+ result = JSON.parse(json_data)
179
+
180
+ # Add display formatting
181
+ if result["success"]
182
+ if result["output"].nil? || result["output"].empty?
183
+ result["display"] = result["result"].inspect
184
+ else
185
+ result_part = result["result"].nil? ? "" : "\n=> #{result["result"].inspect}"
186
+ result["display"] = result["output"] + result_part
187
+ end
188
+ end
189
+
190
+ # Convert string keys to symbols
191
+ result.transform_keys(&:to_sym)
192
+ else
193
+ {
194
+ error: "Failed to parse Python execution result",
195
+ raw_output: stdout,
196
+ success: false
197
+ }
198
+ end
199
+ rescue JSON::ParserError => e
200
+ {
201
+ error: "Failed to parse Python result as JSON: #{e.message}",
202
+ raw_output: stdout,
203
+ success: false
204
+ }
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module SharedTools
6
+ module Tools
7
+ module Eval
8
+ # @example
9
+ # tool = SharedTools::Tools::Eval::RubyEvalTool.new
10
+ # tool.execute(code: "puts 'Hello'; 2 + 2")
11
+ class RubyEvalTool < ::RubyLLM::Tool
12
+ def self.name = 'eval_ruby'
13
+
14
+ description <<~DESCRIPTION
15
+ Execute Ruby source code safely and return the result.
16
+
17
+ This tool evaluates Ruby code in a sandboxed context and returns
18
+ the result of the last expression or any output produced.
19
+
20
+ WARNING: This tool executes arbitrary Ruby code. Use with caution.
21
+ DESCRIPTION
22
+
23
+ params do
24
+ string :code, description: "The Ruby code to execute"
25
+ end
26
+
27
+ # @param logger [Logger] optional logger
28
+ def initialize(logger: nil)
29
+ @logger = logger || RubyLLM.logger
30
+ end
31
+
32
+ # @param code [String] Ruby code to execute
33
+ #
34
+ # @return [Hash] execution result
35
+ def execute(code:)
36
+ @logger.info("Requesting permission to execute Ruby code")
37
+
38
+ if code.strip.empty?
39
+ error_msg = "Ruby code cannot be empty"
40
+ @logger.error(error_msg)
41
+ return { error: error_msg }
42
+ end
43
+
44
+ # Show user the code and ask for confirmation
45
+ allowed = SharedTools.execute?(tool: self.class.to_s, stuff: code)
46
+
47
+ unless allowed
48
+ @logger.warn("User declined to execute the Ruby code")
49
+ return { error: "User declined to execute the Ruby code" }
50
+ end
51
+
52
+ @logger.info("Executing Ruby code")
53
+
54
+ # Capture both stdout and the result of evaluation
55
+ original_stdout = $stdout
56
+ captured_output = StringIO.new
57
+ $stdout = captured_output
58
+
59
+ begin
60
+ result = eval(code)
61
+ output = captured_output.string
62
+
63
+ @logger.debug("Ruby code execution completed successfully")
64
+
65
+ response = {
66
+ result: result,
67
+ output: output.empty? ? nil : output,
68
+ success: true
69
+ }
70
+
71
+ # Include both result and output in a readable format
72
+ if output.empty?
73
+ response[:display] = result.inspect
74
+ else
75
+ response[:display] = output + (result.nil? ? "" : "\n=> #{result.inspect}")
76
+ end
77
+
78
+ response
79
+ rescue SyntaxError, StandardError => e
80
+ @logger.error("Ruby code execution failed: #{e.message}")
81
+ {
82
+ error: e.message,
83
+ backtrace: e.backtrace&.first(5),
84
+ success: false
85
+ }
86
+ ensure
87
+ $stdout = original_stdout
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module SharedTools
5
+ module Tools
6
+ module Eval
7
+ # @example
8
+ # tool = SharedTools::Tools::Eval::ShellEvalTool.new
9
+ # tool.execute(command: "ls -la")
10
+ class ShellEvalTool < ::RubyLLM::Tool
11
+ def self.name = 'eval_shell'
12
+
13
+ description "Execute a shell command safely and return the result."
14
+
15
+ params do
16
+ string :command, description: "The shell command to execute"
17
+ end
18
+
19
+ # @param logger [Logger] optional logger
20
+ def initialize(logger: nil)
21
+ @logger = logger || RubyLLM.logger
22
+ end
23
+
24
+ # @param command [String] shell command to execute
25
+ #
26
+ # @return [Hash] execution result
27
+ def execute(command:)
28
+ @logger.info("Requesting permission to execute command: '#{command}'")
29
+
30
+ if command.strip.empty?
31
+ error_msg = "Command cannot be empty"
32
+ @logger.error(error_msg)
33
+ return { error: error_msg }
34
+ end
35
+
36
+ # Show user the command and ask for confirmation
37
+ allowed = SharedTools.execute?(tool: self.class.to_s, stuff: command)
38
+
39
+ unless allowed
40
+ @logger.warn("User declined to execute the command: '#{command}'")
41
+ return { error: "User declined to execute the command" }
42
+ end
43
+
44
+ @logger.info("Executing command: '#{command}'")
45
+
46
+ # Use Open3 for safer command execution with proper error handling
47
+ require "open3"
48
+ stdout, stderr, status = Open3.capture3(command)
49
+
50
+ if status.success?
51
+ @logger.debug("Command execution completed successfully with #{stdout.bytesize} bytes of output")
52
+ { stdout: stdout, exit_status: status.exitstatus }
53
+ else
54
+ @logger.warn("Command execution failed with exit code #{status.exitstatus}: #{stderr}")
55
+ { error: "Command failed with exit code #{status.exitstatus}", stderr: stderr, exit_status: status.exitstatus }
56
+ end
57
+ rescue => e
58
+ @logger.error("Command execution failed: #{e.message}")
59
+ { error: e.message }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Collection loader for all eval tools
4
+ # Usage: require 'shared_tools/tools/eval'
5
+
6
+ require 'shared_tools'
7
+
8
+ require_relative 'eval/python_eval_tool'
9
+ require_relative 'eval/ruby_eval_tool'
10
+ require_relative 'eval/shell_eval_tool'
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../shared_tools'
4
+
5
+ module SharedTools
6
+ module Tools
7
+ # A tool for evaluating code in different programming languages
8
+ class EvalTool < ::RubyLLM::Tool
9
+ def self.name = 'eval_tool'
10
+
11
+ module Action
12
+ RUBY = "ruby"
13
+ PYTHON = "python"
14
+ SHELL = "shell"
15
+ end
16
+
17
+ ACTIONS = [
18
+ Action::RUBY,
19
+ Action::PYTHON,
20
+ Action::SHELL,
21
+ ].freeze
22
+
23
+ description <<~TEXT
24
+ Execute code in various programming languages (Ruby, Python, Shell).
25
+
26
+ **WARNING**: This tool executes arbitrary code. All code execution requires user authorization for security.
27
+
28
+ ## Actions:
29
+
30
+ 1. `#{Action::RUBY}` - Execute Ruby code
31
+ Required: "action": "ruby", "code": "[Ruby code to execute]"
32
+
33
+ 2. `#{Action::PYTHON}` - Execute Python code
34
+ Required: "action": "python", "code": "[Python code to execute]"
35
+ Note: Requires python3 in system PATH
36
+
37
+ 3. `#{Action::SHELL}` - Execute shell commands
38
+ Required: "action": "shell", "command": "[Shell command to execute]"
39
+
40
+ ## Examples:
41
+
42
+ Execute Ruby code
43
+ {"action": "#{Action::RUBY}", "code": "puts 'Hello from Ruby'; 2 + 2"}
44
+
45
+ Execute Python code
46
+ {"action": "#{Action::PYTHON}", "code": "print('Hello from Python')\\nresult = 5 * 5\\nprint(result)"}
47
+
48
+ Execute shell command
49
+ {"action": "#{Action::SHELL}", "command": "ls -la"}
50
+
51
+ Calculate with Ruby
52
+ {"action": "#{Action::RUBY}", "code": "[1, 2, 3, 4, 5].sum"}
53
+
54
+ Data processing with Python
55
+ {"action": "#{Action::PYTHON}", "code": "import json\\ndata = {'name': 'test', 'value': 42}\\nprint(json.dumps(data))"}
56
+
57
+ System info with shell
58
+ {"action": "#{Action::SHELL}", "command": "uname -a"}
59
+ TEXT
60
+
61
+ params do
62
+ string :action, description: <<~TEXT.strip
63
+ The evaluation action to perform. Options:
64
+ * `#{Action::RUBY}`: Execute Ruby code
65
+ * `#{Action::PYTHON}`: Execute Python code (requires python3)
66
+ * `#{Action::SHELL}`: Execute shell commands
67
+ TEXT
68
+
69
+ string :code, description: <<~TEXT.strip, required: false
70
+ The code to execute. Required for the following actions:
71
+ * `#{Action::RUBY}`
72
+ * `#{Action::PYTHON}`
73
+ TEXT
74
+
75
+ string :command, description: <<~TEXT.strip, required: false
76
+ The shell command to execute. Required for the following actions:
77
+ * `#{Action::SHELL}`
78
+ TEXT
79
+ end
80
+
81
+ # @param logger [Logger] optional logger
82
+ def initialize(logger: nil)
83
+ @logger = logger || RubyLLM.logger
84
+ end
85
+
86
+ # @param action [String] the action to perform
87
+ # @param code [String, nil] code to execute (for ruby/python)
88
+ # @param command [String, nil] shell command to execute
89
+ #
90
+ # @return [Hash, String] execution result
91
+ def execute(action:, code: nil, command: nil)
92
+ @logger.info("EvalTool#execute action=#{action}")
93
+
94
+ case action.to_s.downcase
95
+ when Action::RUBY
96
+ require_param!(:code, code)
97
+ ruby_eval_tool.execute(code: code)
98
+ when Action::PYTHON
99
+ require_param!(:code, code)
100
+ python_eval_tool.execute(code: code)
101
+ when Action::SHELL
102
+ require_param!(:command, command)
103
+ shell_eval_tool.execute(command: command)
104
+ else
105
+ { error: "Unsupported action: #{action}. Supported actions are: #{ACTIONS.join(', ')}" }
106
+ end
107
+ rescue StandardError => e
108
+ @logger.error("EvalTool execution failed: #{e.message}")
109
+ { error: e.message }
110
+ end
111
+
112
+ private
113
+
114
+ # @param name [Symbol]
115
+ # @param value [Object]
116
+ #
117
+ # @raise [ArgumentError]
118
+ # @return [void]
119
+ def require_param!(name, value)
120
+ raise ArgumentError, "#{name} param is required for this action" if value.nil?
121
+ end
122
+
123
+ # @return [Eval::RubyEvalTool]
124
+ def ruby_eval_tool
125
+ @ruby_eval_tool ||= Eval::RubyEvalTool.new(logger: @logger)
126
+ end
127
+
128
+ # @return [Eval::PythonEvalTool]
129
+ def python_eval_tool
130
+ @python_eval_tool ||= Eval::PythonEvalTool.new(logger: @logger)
131
+ end
132
+
133
+ # @return [Eval::ShellEvalTool]
134
+ def shell_eval_tool
135
+ @shell_eval_tool ||= Eval::ShellEvalTool.new(logger: @logger)
136
+ end
137
+ end
138
+ end
139
+ end