Commit 074912e

mo khan <mo@mokhan.ca>
2025-11-07 16:56:22
refactor: extract a Tool.build method
1 parent 389115b
Changed files (2)
lib/elelem/tool.rb
@@ -8,11 +8,37 @@ module Elelem
     end
 
     def call(args)
+      return ArgumentError.new(args) unless valid?(args)
+
       @block.call(args)
     end
 
+    def valid?(args)
+      # TODO:: Use JSON Schema Validator
+      true
+    end
+
     def to_h
-      @schema
+      @schema&.to_h
+    end
+
+    class << self
+      def build(name, description, properties, required = [])
+        new({
+          type: "function",
+          function: {
+            name: name,
+            description: description,
+            parameters: {
+              type: "object",
+              properties: properties,
+              required: required
+            }
+          }
+        }) do |args|
+          yield args
+        end
+      end
     end
   end
 end
lib/elelem/toolbox.rb
@@ -33,12 +33,19 @@ module Elelem
 
     private
 
-    def expand_path(path)
-      Pathname.new(path).expand_path
+    def run_exec(command, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
+      cmd = command.is_a?(Array) ? command.first : command
+      cmd_args = command.is_a?(Array) ? command[1..] + args : args
+      stdout, stderr, status = Open3.capture3(env, cmd, *cmd_args, chdir: cwd, stdin_data: stdin)
+      {
+        "exit_status" => status.exitstatus,
+        "stdout" => stdout.to_s,
+        "stderr" => stderr.to_s
+      }
     end
 
     def exec_tool
-      @exec_tools ||= Tool.new(build_tool(
+      @exec_tools ||= Tool.build(
         "execute",
         "Execute shell commands directly. Commands run in a shell context. Examples: 'date', 'git status'.",
         {
@@ -49,7 +56,7 @@ module Elelem
           stdin: { type: "string" }
         },
         ["cmd"]
-      )) do |args|
+      ) do |args|
         run_exec(
           args["cmd"],
           args: args["args"] || [],
@@ -60,95 +67,61 @@ module Elelem
       end
     end
 
-    def run_exec(command, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
-      cmd = command.is_a?(Array) ? command.first : command
-      cmd_args = command.is_a?(Array) ? command[1..] + args : args
-      stdout, stderr, status = Open3.capture3(env, cmd, *cmd_args, chdir: cwd, stdin_data: stdin)
-      {
-        "exit_status" => status.exitstatus,
-        "stdout" => stdout.to_s,
-        "stderr" => stderr.to_s
-      }
-    end
-
     def grep_tool
-      @grep_tool ||= Tool.new(build_tool(
+      @grep_tool ||= Tool.build(
         "grep",
         "Search all git-tracked files using git grep. Returns file paths with matching line numbers.",
         { query: { type: "string" } },
         ["query"]
-      )) do |args|
+      ) do |args|
         run_exec("git", args: ["grep", "-nI", args["query"]])
       end
     end
 
     def list_tool
-      @list_tool ||= Tool.new(build_tool(
+      @list_tool ||= Tool.build(
         "list",
         "List all git-tracked files in the repository, optionally filtered by path.",
         { path: { type: "string" } }
-      )) do |args|
+      ) do |args|
         run_exec("git", args: args["path"] ? ["ls-files", "--", args["path"]] : ["ls-files"])
       end
     end
 
     def patch_tool
-      @patch_tool ||= Tool.new(build_tool(
+      @patch_tool ||= Tool.build(
         "patch",
         "Apply a unified diff patch via 'git apply'. Use for surgical edits to existing files.",
         { diff: { type: "string" } },
         ["diff"]
-      )) do |args|
+      ) do |args|
         run_exec("git", args: ["apply", "--index", "--whitespace=nowarn", "-p1"], stdin: args["diff"])
       end
     end
 
     def read_tool
-      @read_tool ||= Tool.new(build_tool(
+      @read_tool ||= Tool.build(
         "read",
         "Read complete contents of a file. Requires exact file path.",
         { path: { type: "string" } },
         ["path"]
-      )) do |args|
-        read_file(args["path"])
+      ) do |args|
+        full_path = Pathname.new(args["path"]).expand_path
+        full_path.exist? ? { content: full_path.read } : { error: "File not found: #{path}" }
       end
     end
 
-    def read_file(path)
-      full_path = expand_path(path)
-      full_path.exist? ? { content: full_path.read } : { error: "File not found: #{path}" }
-    end
-
     def write_tool
-      @write_tool ||= Tool.new(build_tool(
+      @write_tool ||= Tool.build(
         "write",
         "Write complete file contents (overwrites existing files). Creates parent directories automatically.",
         { path: { type: "string" }, content: { type: "string" } },
         ["path", "content"]
-      )) do |args|
-        write_file(args["path"], args["content"])
+      ) do |args|
+        full_path = Pathname.new(args["path"]).expand_path
+        FileUtils.mkdir_p(full_path.dirname)
+        { bytes_written: full_path.write(args["content"]) }
       end
     end
-
-    def write_file(path, content)
-      full_path = expand_path(path)
-      FileUtils.mkdir_p(full_path.dirname)
-      { bytes_written: full_path.write(content) }
-    end
-
-    def build_tool(name, description, properties, required = [])
-      {
-        type: "function",
-        function: {
-          name: name,
-          description: description,
-          parameters: {
-            type: "object",
-            properties: properties,
-            required: required
-          }
-        }
-      }
-    end
   end
 end