Commit 8d74a93

mo khan <mo@mokhan.ca>
2025-08-12 17:53:52
refactor: extract processing state and tui
1 parent a3d9d28
Changed files (2)
lib/elelem/agent.rb
@@ -13,17 +13,54 @@ module Elelem
       agent.quit if input.nil? || input.empty? || input == "exit"
 
       configuration.conversation.add(role: "user", content: input)
+      agent.transition_to(ProcessingInput.new(configuration))
     end
   end
 
-  class Agent
+  class ProcessingInput
     attr_reader :configuration, :conversation, :tools
-    attr_reader :current_state
 
     def initialize(configuration)
       @configuration = configuration
       @conversation = configuration.conversation
       @tools = configuration.tools
+    end
+
+    def run(agent)
+      done = false
+
+      loop do
+        configuration.api.chat(conversation.history, tools) do |chunk|
+          response = JSON.parse(chunk)
+          done = response["done"]
+          message = response["message"] || {}
+
+          if message["thinking"]
+            configuration.tui.say(message["thinking"], colour: :gray, newline: false)
+          elsif message["tool_calls"]&.any?
+            message["tool_calls"].each do |t|
+              conversation.add(role: "tool", content: tools.execute(t))
+            end
+            done = false
+          elsif message["content"].to_s.strip
+            configuration.tui.say(message["content"], colour: :default, newline: false)
+          else
+            raise chunk.inspect
+          end
+        end
+
+        break if done
+      end
+
+      agent.transition_to(Idle.new(configuration))
+    end
+  end
+
+  class Agent
+    attr_reader :configuration, :current_state
+
+    def initialize(configuration)
+      @configuration = configuration
       transition_to(Idle.new(configuration))
     end
 
@@ -32,8 +69,7 @@ module Elelem
     end
 
     def prompt(message)
-      print(message)
-      $stdin.gets&.chomp
+      configuration.tui.prompt(message)
     end
 
     def quit
@@ -43,31 +79,6 @@ module Elelem
     def repl
       loop do
         current_state.run(self)
-
-        done = false
-        loop do
-          configuration.api.chat(conversation.history, tools) do |chunk|
-            response = JSON.parse(chunk)
-            done = response["done"]
-            message = response["message"] || {}
-
-            if message["thinking"]
-              print message["thinking"]
-            elsif message["tool_calls"]&.any?
-              message["tool_calls"].each do |t|
-                conversation.add(role: "tool", content: tools.execute(t))
-              end
-              done = false
-            elsif message["content"].to_s.strip
-              print message["content"]
-            else
-              raise chunk.inspect
-            end
-            $stdout.flush
-          end
-
-          break if done
-        end
       end
     end
   end
lib/elelem/configuration.rb
@@ -1,6 +1,41 @@
 # frozen_string_literal: true
 
 module Elelem
+  class TUI
+    attr_reader :stdin, :stdout
+
+    def initialize(stdin = $stdin, stdout = $stdout)
+      @stdin = stdin
+      @stdout = stdout
+    end
+
+    def prompt(message)
+      say(message)
+      stdin.gets&.chomp
+    end
+
+    def say(message, colour: :default, newline: false)
+      formatted_message = colourize(message, colour: colour)
+      if newline
+        stdout.puts(formatted_message)
+      else
+        stdout.print(formatted_message)
+      end
+      stdout.flush
+    end
+
+    private
+
+    def colourize(text, colour: :default)
+      case colour
+      when :gray
+        "\e[90m#{text}\e[0m"
+      else
+        text
+      end
+    end
+  end
+
   class Configuration
     attr_reader :host, :model, :token, :debug
 
@@ -18,6 +53,10 @@ module Elelem
       end
     end
 
+    def tui
+      @tui ||= TUI.new
+    end
+
     def api
       @api ||= Api.new(self)
     end