Commit 7e576c2

mo khan <mo@mokhan.ca>
2025-08-14 21:22:45
refactor: move states to different files
1 parent b157106
lib/elelem/states/working/state.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module Elelem
+  module States
+    module Working
+      class State
+        attr_reader :agent
+
+        def initialize(agent, icon, colour)
+          @agent = agent
+
+          agent.logger.debug("#{display_name}...")
+          agent.tui.show_progress("#{display_name}...", icon, colour: colour)
+        end
+
+        def display_name
+          self.class.name.split("::").last
+        end
+      end
+
+      class Waiting < State
+        def initialize(agent)
+          super(agent, ".", :cyan)
+        end
+
+        def process(message)
+          state_for(message)&.process(message)
+        end
+
+        private
+
+        def state_for(message)
+          if message["thinking"] && !message["thinking"].empty?
+            Thinking.new(agent, "*", :yellow)
+          elsif message["tool_calls"]&.any?
+            Executing.new(agent, ">", :magenta)
+          elsif message["content"] && !message["content"].empty?
+            Talking.new(agent, "~", :white)
+          end
+        end
+      end
+
+      class Thinking < State
+        def process(message)
+          if message["thinking"] && !message["thinking"]&.empty?
+            agent.tui.say(message["thinking"], colour: :gray, newline: false)
+            self
+          else
+            Waiting.new(agent).process(message)
+          end
+        end
+      end
+
+      class Executing < State
+        def process(message)
+          if message["tool_calls"]&.any?
+            message["tool_calls"].each do |tool_call|
+              agent.conversation.add(role: :tool, content: agent.execute(tool_call))
+            end
+          end
+
+          Waiting.new(agent)
+        end
+      end
+
+      class Error < State
+        def initialize(agent, error_message)
+          super(agent, "X", :red)
+          @error_message = error_message
+        end
+
+        def process(_message)
+          agent.tui.say("\nTool execution failed: #{@error_message}", colour: :red)
+          Waiting.new(agent)
+        end
+      end
+
+      class Talking < State
+        def process(message)
+          if message["content"] && !message["content"]&.empty?
+            agent.conversation.add(role: message["role"], content: message["content"])
+            agent.tui.say(message["content"], colour: :default, newline: false)
+            self
+          else
+            Waiting.new(agent).process(message)
+          end
+        end
+      end
+    end
+  end
+end
lib/elelem/states/idle.rb
@@ -10,7 +10,7 @@ module Elelem
         agent.quit if input.nil? || input.empty? || input == "exit" || input == "quit"
 
         agent.conversation.add(role: :user, content: input)
-        agent.transition_to(Working.new)
+        agent.transition_to(Working)
       end
 
       private
lib/elelem/states/working.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Elelem
+  module States
+    module Working
+      class << self
+        def run(agent)
+          state = Waiting.new(agent)
+          done = false
+
+          loop do
+            agent.api.chat(agent.conversation.history) do |chunk|
+              response = JSON.parse(chunk)
+              message = normalize(response["message"] || {})
+              done = response["done"]
+
+              agent.logger.debug("#{state.display_name}: #{message}")
+              state = state.process(message)
+            end
+
+            break if state.nil?
+            break if done && agent.conversation.history.last[:role] != :tool
+          end
+
+          agent.transition_to(States::Idle.new)
+        end
+
+        def normalize(message)
+          message.reject { |_key, value| value.empty? }
+        end
+      end
+    end
+  end
+end
lib/elelem/state.rb
@@ -1,116 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  class Working
-    class State
-      attr_reader :agent
-
-      def initialize(agent, icon, colour)
-        @agent = agent
-
-        agent.logger.debug("#{display_name}...")
-        agent.tui.show_progress("#{display_name}...", icon, colour: colour)
-      end
-
-      def display_name
-        self.class.name.split("::").last
-      end
-    end
-
-    class Waiting < State
-      def initialize(agent)
-        super(agent, ".", :cyan)
-      end
-
-      def process(message)
-        state_for(message)&.process(message)
-      end
-
-      private
-
-      def state_for(message)
-        if message["thinking"] && !message["thinking"].empty?
-          Thinking.new(agent, "*", :yellow)
-        elsif message["tool_calls"]&.any?
-          Executing.new(agent, ">", :magenta)
-        elsif message["content"] && !message["content"].empty?
-          Talking.new(agent, "~", :white)
-        end
-      end
-    end
-
-    class Thinking < State
-      def process(message)
-        if message["thinking"] && !message["thinking"]&.empty?
-          agent.tui.say(message["thinking"], colour: :gray, newline: false)
-          self
-        else
-          Waiting.new(agent).process(message)
-        end
-      end
-    end
-
-    class Executing < State
-      def process(message)
-        if message["tool_calls"]&.any?
-          message["tool_calls"].each do |tool_call|
-            agent.conversation.add(role: :tool, content: agent.execute(tool_call))
-          end
-        end
-
-        Waiting.new(agent)
-      end
-    end
-
-    class Error < State
-      def initialize(agent, error_message)
-        super(agent, "X", :red)
-        @error_message = error_message
-      end
-
-      def process(_message)
-        agent.tui.say("\nTool execution failed: #{@error_message}", colour: :red)
-        Waiting.new(agent)
-      end
-    end
-
-    class Talking < State
-      def process(message)
-        if message["content"] && !message["content"]&.empty?
-          agent.conversation.add(role: message["role"], content: message["content"])
-          agent.tui.say(message["content"], colour: :default, newline: false)
-          self
-        else
-          Waiting.new(agent).process(message)
-        end
-      end
-    end
-
-    def run(agent)
-      state = Waiting.new(agent)
-      done = false
-
-      loop do
-        agent.api.chat(agent.conversation.history) do |chunk|
-          response = JSON.parse(chunk)
-          message = normalize(response["message"] || {})
-          done = response["done"]
-
-          agent.logger.debug("#{state.display_name}: #{message}")
-          state = state.process(message)
-        end
-
-        break if state.nil?
-        break if done && agent.conversation.history.last[:role] != :tool
-      end
-
-      agent.transition_to(States::Idle.new)
-    end
-
-    private
-
-    def normalize(message)
-      message.reject { |_key, value| value.empty? }
-    end
-  end
-end
lib/elelem.rb
@@ -16,8 +16,9 @@ require_relative "elelem/application"
 require_relative "elelem/configuration"
 require_relative "elelem/conversation"
 require_relative "elelem/mcp_client"
-require_relative "elelem/state"
 require_relative "elelem/states/idle"
+require_relative "elelem/states/working"
+require_relative "elelem/states/working/state"
 require_relative "elelem/tool"
 require_relative "elelem/toolbox/bash"
 require_relative "elelem/toolbox/mcp"
elelem.gemspec
@@ -40,8 +40,9 @@ Gem::Specification.new do |spec|
     "lib/elelem/configuration.rb",
     "lib/elelem/conversation.rb",
     "lib/elelem/mcp_client.rb",
-    "lib/elelem/state.rb",
     "lib/elelem/states/idle.rb",
+    "lib/elelem/states/working.rb",
+    "lib/elelem/states/working/state.rb",
     "lib/elelem/system_prompt.erb",
     "lib/elelem/tool.rb",
     "lib/elelem/toolbox/bash.rb",