elelem 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/elelem/tools.rb CHANGED
@@ -12,14 +12,15 @@ module Elelem
12
12
  end
13
13
 
14
14
  def execute(tool_call)
15
- name = tool_call.dig("function", "name")
16
- args = tool_call.dig("function", "arguments")
15
+ name, args = parse(tool_call)
17
16
 
18
17
  tool = tools.find { |tool| tool.name == name }
19
18
  return "Invalid function name: #{name}" if tool.nil?
20
19
  return "Invalid function arguments: #{args}" unless tool.valid?(args)
21
20
 
22
- tool.call(args)
21
+ CLI::UI::Frame.open(name) do
22
+ tool.call(args)
23
+ end
23
24
  end
24
25
 
25
26
  def to_h
@@ -29,5 +30,12 @@ module Elelem
29
30
  private
30
31
 
31
32
  attr_reader :configuration, :tools
33
+
34
+ def parse(tool_call)
35
+ name = tool_call.dig("function", "name")
36
+ arguments = tool_call.dig("function", "arguments")
37
+
38
+ [name, arguments.is_a?(String) ? JSON.parse(arguments) : arguments]
39
+ end
32
40
  end
33
41
  end
data/lib/elelem/tui.rb CHANGED
@@ -14,41 +14,30 @@ module Elelem
14
14
  end
15
15
 
16
16
  def say(message, colour: :default, newline: false)
17
- formatted_message = colourize(message, colour: colour)
18
17
  if newline
19
- stdout.puts(formatted_message)
18
+ stdout.puts(colourize(message, colour: colour))
20
19
  else
21
- stdout.print(formatted_message)
20
+ stdout.print(colourize(message, colour: colour))
22
21
  end
23
22
  stdout.flush
24
23
  end
25
24
 
26
- def show_progress(message, prefix = "[.]", colour: :gray)
27
- timestamp = current_time_string
28
- formatted_message = colourize("#{prefix} #{timestamp} #{message}", colour: colour)
29
- stdout.print(formatted_message)
30
- stdout.flush
25
+ def show_progress(message, icon = ".", colour: :gray)
26
+ timestamp = Time.now.strftime("%H:%M:%S")
27
+ say("\n[#{icon}] #{timestamp} #{message}", colour: colour, newline: true)
31
28
  end
32
29
 
33
30
  def clear_line
34
- stdout.print("\r#{" " * 80}\r")
35
- stdout.flush
31
+ say("\r#{" " * 80}\r", newline: false)
36
32
  end
37
33
 
38
34
  def complete_progress(message = "Completed")
39
35
  clear_line
40
- timestamp = current_time_string
41
- formatted_message = colourize("[✓] #{timestamp} #{message}", colour: :green)
42
- stdout.puts(formatted_message)
43
- stdout.flush
36
+ show_progress(message, "✓", colour: :green)
44
37
  end
45
38
 
46
39
  private
47
40
 
48
- def current_time_string
49
- Time.now.strftime("%H:%M:%S")
50
- end
51
-
52
41
  def colourize(text, colour: :default)
53
42
  case colour
54
43
  when :black
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Elelem
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/elelem.rb CHANGED
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cli/ui"
3
4
  require "erb"
4
5
  require "json"
5
6
  require "json-schema"
6
7
  require "logger"
7
- require "net/http"
8
+ require "net/llm"
8
9
  require "open3"
9
10
  require "reline"
10
11
  require "thor"
11
- require "uri"
12
+ require "timeout"
12
13
 
13
14
  require_relative "elelem/agent"
14
15
  require_relative "elelem/api"
@@ -16,12 +17,24 @@ require_relative "elelem/application"
16
17
  require_relative "elelem/configuration"
17
18
  require_relative "elelem/conversation"
18
19
  require_relative "elelem/mcp_client"
19
- require_relative "elelem/state"
20
+ require_relative "elelem/states/idle"
21
+ require_relative "elelem/states/working"
22
+ require_relative "elelem/states/working/state"
23
+ require_relative "elelem/states/working/error"
24
+ require_relative "elelem/states/working/executing"
25
+ require_relative "elelem/states/working/talking"
26
+ require_relative "elelem/states/working/thinking"
27
+ require_relative "elelem/states/working/waiting"
20
28
  require_relative "elelem/tool"
29
+ require_relative "elelem/toolbox"
21
30
  require_relative "elelem/tools"
22
31
  require_relative "elelem/tui"
23
32
  require_relative "elelem/version"
24
33
 
34
+ CLI::UI::StdoutRouter.enable
35
+ Reline.input = $stdin
36
+ Reline.output = $stdout
37
+
25
38
  module Elelem
26
39
  class Error < StandardError; end
27
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elelem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: cli-ui
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: erb
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +80,7 @@ dependencies:
66
80
  - !ruby/object:Gem::Version
67
81
  version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
- name: net-http
83
+ name: net-llm
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - ">="
@@ -122,7 +136,7 @@ dependencies:
122
136
  - !ruby/object:Gem::Version
123
137
  version: '0'
124
138
  - !ruby/object:Gem::Dependency
125
- name: uri
139
+ name: timeout
126
140
  requirement: !ruby/object:Gem::Requirement
127
141
  requirements:
128
142
  - - ">="
@@ -155,13 +169,26 @@ files:
155
169
  - lib/elelem/configuration.rb
156
170
  - lib/elelem/conversation.rb
157
171
  - lib/elelem/mcp_client.rb
158
- - lib/elelem/state.rb
172
+ - lib/elelem/states/idle.rb
173
+ - lib/elelem/states/working.rb
174
+ - lib/elelem/states/working/error.rb
175
+ - lib/elelem/states/working/executing.rb
176
+ - lib/elelem/states/working/state.rb
177
+ - lib/elelem/states/working/talking.rb
178
+ - lib/elelem/states/working/thinking.rb
179
+ - lib/elelem/states/working/waiting.rb
159
180
  - lib/elelem/system_prompt.erb
160
181
  - lib/elelem/tool.rb
182
+ - lib/elelem/toolbox.rb
183
+ - lib/elelem/toolbox/exec.rb
184
+ - lib/elelem/toolbox/file.rb
185
+ - lib/elelem/toolbox/mcp.rb
186
+ - lib/elelem/toolbox/memory.rb
187
+ - lib/elelem/toolbox/prompt.rb
188
+ - lib/elelem/toolbox/web.rb
161
189
  - lib/elelem/tools.rb
162
190
  - lib/elelem/tui.rb
163
191
  - lib/elelem/version.rb
164
- - sig/elelem.rbs
165
192
  homepage: https://www.mokhan.ca
166
193
  licenses:
167
194
  - MIT
@@ -184,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
211
  - !ruby/object:Gem::Version
185
212
  version: 3.3.11
186
213
  requirements: []
187
- rubygems_version: 3.6.9
214
+ rubygems_version: 3.7.2
188
215
  specification_version: 4
189
216
  summary: A REPL for Ollama.
190
217
  test_files: []
data/lib/elelem/state.rb DELETED
@@ -1,162 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Elelem
4
- class Idle
5
- def run(agent)
6
- agent.logger.debug("Idling...")
7
- agent.say("#{Dir.pwd} (#{agent.model}) [#{git_branch}]", colour: :magenta, newline: true)
8
- input = agent.prompt("モ ")
9
- agent.quit if input.nil? || input.empty? || input == "exit" || input == "quit"
10
-
11
- agent.conversation.add(role: :user, content: input)
12
- agent.transition_to(Working.new)
13
- end
14
-
15
- private
16
-
17
- def git_branch
18
- `git branch --no-color --show-current --no-abbrev`.strip
19
- end
20
- end
21
-
22
- class Working
23
- class State
24
- attr_reader :agent
25
-
26
- def initialize(agent)
27
- @agent = agent
28
- end
29
-
30
- def display_name
31
- self.class.name.split("::").last
32
- end
33
- end
34
-
35
- class Waiting < State
36
- def process(message)
37
- state_for(message)&.process(message)
38
- end
39
-
40
- private
41
-
42
- def state_for(message)
43
- if message["thinking"] && !message["thinking"].empty?
44
- Thinking.new(agent)
45
- elsif message["tool_calls"]&.any?
46
- Executing.new(agent)
47
- elsif message["content"] && !message["content"].empty?
48
- Talking.new(agent)
49
- end
50
- end
51
- end
52
-
53
- class Thinking < State
54
- def initialize(agent)
55
- super(agent)
56
- @progress_shown = false
57
- end
58
-
59
- def process(message)
60
- if message["thinking"] && !message["thinking"]&.empty?
61
- unless @progress_shown
62
- agent.show_progress("Thinking...", "[*]", colour: :yellow)
63
- agent.say("\n\n", newline: false)
64
- @progress_shown = true
65
- end
66
- agent.say(message["thinking"], colour: :gray, newline: false)
67
- self
68
- else
69
- agent.say("\n\n", newline: false)
70
- Waiting.new(agent).process(message)
71
- end
72
- end
73
- end
74
-
75
- class Executing < State
76
- def process(message)
77
- if message["tool_calls"]&.any?
78
- message["tool_calls"].each do |tool_call|
79
- tool_name = tool_call.dig("function", "name") || "unknown"
80
- agent.show_progress(tool_name, "[>]", colour: :magenta)
81
- agent.say("\n\n", newline: false)
82
-
83
- output = agent.execute(tool_call)
84
- agent.conversation.add(role: :tool, content: output)
85
-
86
- agent.say("\n", newline: false)
87
- agent.complete_progress("#{tool_name} completed")
88
- end
89
- end
90
-
91
- Waiting.new(agent)
92
- end
93
- end
94
-
95
- class Error < State
96
- def initialize(agent, error_message)
97
- super(agent)
98
- @error_message = error_message
99
- end
100
-
101
- def process(_message)
102
- agent.say("\nTool execution failed: #{@error_message}", colour: :red)
103
- agent.say("Returning to idle state.\n\n", colour: :yellow)
104
- Waiting.new(agent)
105
- end
106
- end
107
-
108
- class Talking < State
109
- def initialize(agent)
110
- super(agent)
111
- @progress_shown = false
112
- end
113
-
114
- def process(message)
115
- if message["content"] && !message["content"]&.empty?
116
- unless @progress_shown
117
- agent.show_progress("Responding...", "[~]", colour: :white)
118
- agent.say("\n", newline: false)
119
- @progress_shown = true
120
- end
121
- agent.conversation.add(role: message["role"], content: message["content"])
122
- agent.say(message["content"], colour: :default, newline: false)
123
- self
124
- else
125
- agent.say("\n\n", newline: false)
126
- Waiting.new(agent).process(message)
127
- end
128
- end
129
- end
130
-
131
- def run(agent)
132
- agent.logger.debug("Working...")
133
- agent.show_progress("Processing...", "[.]", colour: :cyan)
134
- agent.say("\n\n", newline: false)
135
-
136
- state = Waiting.new(agent)
137
- done = false
138
-
139
- loop do
140
- agent.api.chat(agent.conversation.history) do |chunk|
141
- response = JSON.parse(chunk)
142
- message = normalize(response["message"] || {})
143
- done = response["done"]
144
-
145
- agent.logger.debug("#{state.display_name}: #{message}")
146
- state = state.process(message)
147
- end
148
-
149
- break if state.nil?
150
- break if done && agent.conversation.history.last[:role] != :tool
151
- end
152
-
153
- agent.transition_to(Idle.new)
154
- end
155
-
156
- private
157
-
158
- def normalize(message)
159
- message.reject { |_key, value| value.empty? }
160
- end
161
- end
162
- end
data/sig/elelem.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Elelem
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end