Commit a954505

mo khan <mo@mokhan.ca>
2025-09-01 18:05:54
refactor: sort of useful
1 parent a2547ec
lib/elelem/states/working.rb
@@ -8,18 +8,38 @@ module Elelem
           state = Waiting.new(agent)
 
           loop do
-            done = false
+            streaming_done = false
+            finish_reason = nil
+
             agent.api.chat(agent.conversation.history) do |message|
               if message["done"]
-                done = true
+                streaming_done = true
                 next
               end
 
-              agent.logger.debug("#{state.display_name}: #{message}")
-              state = state.run(message)
+              if message["finish_reason"]
+                finish_reason = message["finish_reason"]
+                agent.logger.debug("Working: finish_reason = #{finish_reason}")
+              end
+
+              new_state = state.run(message)
+              if new_state.class != state.class
+                agent.logger.info("STATE: #{state.display_name} -> #{new_state.display_name}")
+              end
+              state = new_state
             end
 
-            break if state.nil? || done
+            # Only exit when task is actually complete, not just streaming done
+            if finish_reason == "stop"
+              agent.logger.debug("Working: Task complete, exiting to Idle")
+              break
+            elsif finish_reason == "tool_calls"
+              agent.logger.debug("Working: Tool calls finished, continuing conversation")
+              # Continue loop to process tool results
+            elsif streaming_done && finish_reason.nil?
+              agent.logger.debug("Working: Streaming done but no finish_reason, continuing")
+              # Continue for cases where finish_reason comes in separate chunk
+            end
           end
 
           agent.transition_to(States::Idle.new)
lib/elelem/toolbox/file.rb
@@ -2,47 +2,62 @@
 
 module Elelem
   module Toolbox
-    class File < ::Elelem::Tool
-      attr_reader :tui
-
+    class File < Tool
       def initialize(configuration)
-        @tui = configuration.tui
-        super("file", "Read/write files in project directory", {
-          type: "object",
-          properties: {
-            action: {
-              type: "string",
-              enum: ["read", "write", "append"],
-              description: "File operation to perform"
-            },
-            path: {
-              type: "string",
-              description: "Relative path to file from project root"
+        super(
+          "file",
+          "Read and write files",
+          {
+            type: :object,
+            properties: {
+              action: {
+                type: :string,
+                enum: ["read", "write"],
+                description: "Action to perform: read or write"
+              },
+              path: {
+                type: :string,
+                description: "File path"
+              },
+              content: {
+                type: :string,
+                description: "Content to write (only for write action)"
+              }
             },
-            content: {
-              type: "string",
-              description: "Content to write/apppend (only for write/append actions)"
-            }
-          },
-          required: ["action", "path"]
-        })
+            required: [:action, :path]
+          }
+        )
       end
 
       def call(args)
-        path = Pathname.pwd.join(args["path"])
-        case args["action"]
+        action = args["action"]
+        path = args["path"]
+        content = args["content"]
+
+        case action
         when "read"
-          path.read
+          read_file(path)
         when "write"
-          path.write(args["content"])
-          "File written successfully"
-        when "append"
-          path.open("a") { |f| f << args["content"] }
-          "Content appended successfully"
+          write_file(path, content)
+        else
+          "Invalid action: #{action}"
         end
+      end
+
+      private
+
+      def read_file(path)
+        ::File.read(path)
+      rescue => e
+        "Error reading file: #{e.message}"
+      end
+
+      def write_file(path, content)
+        ::File.write(path, content)
+        "File written successfully"
       rescue => e
-        e.message
+        "Error writing file: #{e.message}"
       end
     end
   end
-end
+end
\ No newline at end of file
lib/elelem/toolbox/git.rb
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Toolbox
-    class Git < ::Elelem::Tool
-      def initialize(configuration)
-        @configuration = configuration
-        super("git", "Perform git operations on repository", {
-          type: "object",
-          properties: {
-            action: {
-              type: "string",
-              enum: ["commit", "diff", "log"],
-              description: "Git operation to perform"
-            },
-            message: {
-              type: "string",
-              description: "Commit message (required for commit action)"
-            }
-          },
-          required: ["action"]
-        })
-      end
-
-      def call(args)
-        case args["action"]
-        when "commit"
-          `git add . && git commit -m "#{args["message"]}"`
-          "Committed changes: #{args["message"]}"
-        when "diff"
-          `git diff HEAD`
-        when "log"
-          `git log --oneline -n 10`
-        end
-      rescue => e
-        e.message
-      end
-    end
-  end
-end
lib/elelem/toolbox/search.rb
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Toolbox
-    class Search < ::Elelem::Tool
-      def initialize(configuration)
-        @configuration = configuration
-        super("search", "Search files in project directory", {
-          type: "object",
-          properties: {
-            pattern: {
-              type: "string",
-              description: "Search pattern (grep compatible)"
-            },
-            path: {
-              type: "string",
-              description: "Directory path to search from (default: project root)"
-            }
-          },
-          required: ["pattern"]
-        })
-      end
-
-      def call(args)
-        path = args["path"] || "."
-        pattern = args["pattern"]
-
-        # Build search command - use git grep if in git repo, otherwise regular grep
-        if ::File.directory?(".git") || `git rev-parse --git-dir 2>/dev/null`.strip != ""
-          # Limit results: -m 3 = max 3 matches per file, head -20 = max 20 total lines
-          command = "git grep -n -m 3 '#{pattern}'"
-          command += " -- #{path}" unless path == "."
-          command += " | head -20"
-        else
-          # For regular grep, also limit results
-          command = "grep -rnw '#{pattern}' #{path} | head -20"
-        end
-
-        # Delegate to exec tool for consistent logging and streaming
-        exec_tool = Elelem::Toolbox::Exec.new(@configuration)
-        exec_tool.call({ "command" => command })
-      end
-    end
-  end
-end
lib/elelem/agent.rb
@@ -25,23 +25,28 @@ module Elelem
     end
 
     def transition_to(next_state)
-      logger.debug("Transition to: #{next_state.class.name}")
+      if @current_state
+        logger.info("AGENT: #{@current_state.class.name.split('::').last} -> #{next_state.class.name.split('::').last}")
+      else
+        logger.info("AGENT: Starting in #{next_state.class.name.split('::').last}")
+      end
       @current_state = next_state
     end
 
     def execute(tool_call)
-      logger.debug("Execute: #{tool_call}")
-      configuration.tools.execute(tool_call)
+      tool_name = tool_call.dig("function", "name")
+      logger.debug("TOOL: Full call - #{tool_call}")
+      result = configuration.tools.execute(tool_call)
+      logger.debug("TOOL: Result (#{result.length} chars)") if result
+      result
     end
 
     def quit
-      logger.debug("Exiting...")
       cleanup
       exit
     end
 
     def cleanup
-      logger.debug("Cleaning up agent...")
       configuration.cleanup
     end
 
lib/elelem/api.rb
@@ -13,13 +13,14 @@ module Elelem
       Net::HTTP.start(uri.hostname, uri.port, http_options) do |http|
         http.request(build_request(messages)) do |response|
           if !response.is_a?(Net::HTTPSuccess)
-            configuration.logger.error(response.inspect)
+            configuration.logger.error("API: HTTP error - #{response.code} #{response.message}")
             raise response.inspect
           end
 
           buffer = ""
+          chunk_count = 0
+
           response.read_body do |chunk|
-            configuration.logger.debug(chunk)
             buffer += chunk
 
             while (message = extract_sse_message(buffer))
@@ -30,9 +31,18 @@ module Elelem
                 break
               end
 
-              configuration.logger.debug(message)
               json = JSON.parse(message)
-              block.call(normalize(json.dig("choices", 0, "delta")))
+              delta = json.dig("choices", 0, "delta")
+              finish_reason = json.dig("choices", 0, "finish_reason")
+
+              if finish_reason
+                block.call({ "finish_reason" => finish_reason })
+              end
+
+              if delta
+                chunk_count += 1
+                block.call(normalize(delta))
+              end
             end
           end
         end
@@ -103,7 +113,7 @@ module Elelem
         temperature: 0.1,
         tools: configuration.tools.to_h
       }.tap do |payload|
-        configuration.logger.debug(JSON.pretty_generate(payload))
+        configuration.logger.debug(JSON.pretty_generate(payload)) if configuration.debug
       end
     end
   end
lib/elelem/configuration.rb
@@ -26,7 +26,10 @@ module Elelem
         else
           logger.level = ENV.fetch("LOG_LEVEL", "warn")
         end
-        logger.formatter = ->(_, _, _, message) { "#{message.to_s.strip}\n" }
+        logger.formatter = ->(severity, datetime, progname, message) {
+          timestamp = datetime.strftime("%H:%M:%S.%3N")
+          "[#{timestamp}] #{severity.ljust(5)} #{message.to_s.strip}\n"
+        }
       end
     end
 
@@ -45,10 +48,8 @@ module Elelem
       @tools ||= Tools.new(self,
         [
           Toolbox::Exec.new(self),
-          # Toolbox::File.new(self),
-          # Toolbox::Git.new(self),
-          # Toolbox::Prompt.new(self),
-          Toolbox::Search.new(self),
+          Toolbox::File.new(self),
+          Toolbox::Prompt.new(self),
         ] + mcp_tools
       )
     end
lib/elelem/toolbox.rb
@@ -2,7 +2,5 @@
 
 require_relative "toolbox/exec"
 require_relative "toolbox/file"
-require_relative "toolbox/git"
 require_relative "toolbox/mcp"
 require_relative "toolbox/prompt"
-require_relative "toolbox/search"
lib/elelem/tools.rb
@@ -19,7 +19,6 @@ module Elelem
       return "Invalid function arguments: #{args}" unless tool.valid?(args)
 
       CLI::UI::Frame.open(name) do
-        configuration.logger.debug("Calling tool: #{tool} with #{args}")
         tool.call(args)
       end
     end
elelem.gemspec
@@ -53,10 +53,8 @@ Gem::Specification.new do |spec|
     "lib/elelem/toolbox.rb",
     "lib/elelem/toolbox/exec.rb",
     "lib/elelem/toolbox/file.rb",
-    "lib/elelem/toolbox/git.rb",
     "lib/elelem/toolbox/mcp.rb",
     "lib/elelem/toolbox/prompt.rb",
-    "lib/elelem/toolbox/search.rb",
     "lib/elelem/tools.rb",
     "lib/elelem/tui.rb",
     "lib/elelem/version.rb",