Commit 7e576c2
Changed files (6)
lib
elelem
states
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",