Commit a253374
Changed files (8)
lib/elelem/application.rb
@@ -35,10 +35,10 @@ module Elelem
host: options[:host],
model: options[:model],
token: options[:token],
- debug: options[:debug]
+ debug: options[:debug],
)
- say "Ollama Agent (#{configuration.model})", :green
- say "Tools:\n #{configuration.tools.banner}", :green
+ say "Agent (#{configuration.model})", :green
+ say "#{configuration.tools.banner}", :green
agent = Agent.new(configuration)
agent.repl
lib/elelem/configuration.rb
@@ -28,7 +28,7 @@ module Elelem
def logger
@logger ||= Logger.new(debug ? "elelem.log" : "/dev/null").tap do |logger|
- logger.formatter = ->(_, _, _, message) { message.strip + "\n" }
+ logger.formatter = ->(_, _, _, message) { message.to_s.strip + "\n" }
end
end
lib/elelem/mcp_client.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require "json"
+require "open3"
+
+module Elelem
+ class MCPClient
+ attr_reader :tools
+
+ def initialize(configuration)
+ @configuration = configuration
+ @stdin, @stdout, @stderr, @worker_thread = Open3.popen3(*serena_command, pgroup: true)
+ send_request(
+ method: "initialize",
+ params: {
+ protocolVersion: "2024-11-05",
+ capabilities: {
+ tools: {}
+ },
+ clientInfo: {
+ name: "Elelem",
+ version: Elelem::VERSION
+ }
+ }
+ )
+ @tools = send_request(method: "tools/list")&.dig("tools") || []
+ end
+
+ def connected?
+ @worker_thread&.alive? && @stdin && !@stdin.closed?
+ end
+
+ def call_tool(name, arguments = {})
+ send_request(
+ method: "tools/call",
+ params: {
+ name: name,
+ arguments: arguments
+ }
+ )
+ end
+
+ private
+
+ attr_reader :stdin, :stdout, :stderr, :worker_thread
+ attr_reader :configuration
+
+ def serena_command
+ [
+ "uvx",
+ "--from",
+ "git+https://github.com/oraios/serena",
+ "serena",
+ "start-mcp-server",
+ "--transport", "stdio",
+ "--context", "ide-assistant",
+ "--project", Dir.pwd,
+ ]
+ end
+
+ def send_request(method:, params: {})
+ request = {
+ jsonrpc: "2.0",
+ id: Time.now.to_i,
+ method: method,
+ }
+ request[:params] = params unless params.empty?
+ configuration.logger.debug(JSON.pretty_generate(request))
+ @stdin.puts(JSON.generate(request))
+ @stdin.flush
+
+ response = JSON.parse(@stdout.gets.strip)
+ configuration.logger.debug(JSON.pretty_generate(response))
+ if response["error"]
+ configuration.logger.error(response["error"])
+ {}
+ else
+ response["result"]
+ end
+ end
+ end
+end
lib/elelem/state.rb
@@ -53,7 +53,7 @@ module Elelem
if message["thinking"] && !message["thinking"]&.empty?
unless @progress_shown
agent.show_progress("Thinking...", "[*]", colour: :yellow)
- agent.say("\n", newline: false)
+ agent.say("\n\n", newline: false)
@progress_shown = true
end
agent.say(message["thinking"], colour: :gray, newline: false)
@@ -72,13 +72,12 @@ module Elelem
tool_name = tool_call.dig("function", "name") || "unknown"
agent.show_progress(tool_name, "[>]", colour: :magenta)
agent.say("\n\n", newline: false)
-
+
result = agent.execute(tool_call)
agent.conversation.add(role: :tool, content: result)
-
- agent.say("\n", newline: false)
- agent.complete_progress("Tool completed")
+
agent.say("\n", newline: false)
+ agent.complete_progress("#{tool_name} completed")
end
end
@@ -103,7 +102,7 @@ module Elelem
agent.say(message["content"], colour: :default, newline: false)
self
else
- agent.say("\n", newline: true)
+ agent.say("\n\n", newline: false)
Waiting.new(agent).process(message)
end
end
@@ -113,7 +112,7 @@ module Elelem
agent.logger.debug("Working...")
agent.show_progress("Processing...", "[.]", colour: :cyan)
agent.say("\n\n", newline: false)
-
+
state = Waiting.new(agent)
done = false
lib/elelem/tools.rb
@@ -19,17 +19,34 @@ module Elelem
handler: lambda { |args|
stdout, stderr, _status = Open3.capture3("/bin/sh", "-c", args["command"])
stdout + stderr
- }
+ },
},
]
def initialize(configuration, tools = DEFAULT_TOOLS)
@configuration = configuration
- @tools = tools
+ client = MCPClient.new(configuration)
+ @tools = tools + client.tools.map do |tool|
+ configuration.logger.debug(tool)
+ {
+ type: "function",
+ function: {
+ name: tool["name"],
+ description: tool["description"],
+ parameters: tool["inputSchema"] || {}
+ },
+ handler: lambda { |args|
+ result = client.call_tool(tool["name"], args)
+ output = result.dig("content", 0, "text") || result.to_s
+ configuration.tui.say(output)
+ return output
+ },
+ }
+ end
end
def banner
- @tools.map do |h|
+ tools.map do |h|
[
h.dig(:function, :name),
h.dig(:function, :description)
@@ -41,16 +58,14 @@ module Elelem
name = tool_call.dig("function", "name")
args = tool_call.dig("function", "arguments")
- tool = @tools.find do |tool|
- tool.dig(:function, :name) == name
- end
- tool&.fetch(:handler)&.call(args).tap do |result|
+ tool = tools.find { |tool| tool.dig(:function, :name) == name }
+ tool.fetch(:handler).call(args).tap do |result|
configuration.tui.say(result)
end
end
def to_h
- @tools.map do |tool|
+ tools.map do |tool|
{
type: tool[:type],
function: {
@@ -64,6 +79,6 @@ module Elelem
private
- attr_reader :configuration
+ attr_reader :configuration, :tools
end
end
lib/elelem.rb
@@ -4,6 +4,7 @@ require "json"
require "logger"
require "net/http"
require "open3"
+require "securerandom"
require "thor"
require "uri"
@@ -12,6 +13,7 @@ require_relative "elelem/api"
require_relative "elelem/application"
require_relative "elelem/configuration"
require_relative "elelem/conversation"
+require_relative "elelem/mcp_client"
require_relative "elelem/state"
require_relative "elelem/tools"
require_relative "elelem/tui"
elelem.gemspec
@@ -14,9 +14,7 @@ Gem::Specification.new do |spec|
spec.license = "MIT"
spec.required_ruby_version = ">= 3.1.0"
spec.required_rubygems_version = ">= 3.3.11"
-
spec.metadata["allowed_push_host"] = "https://rubygems.org"
-
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://gitlab.com/mokhax/elelem"
spec.metadata["changelog_uri"] = "https://gitlab.com/mokhax/elelem/-/blob/main/CHANGELOG.md"
@@ -37,6 +35,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "logger"
spec.add_dependency "net-http"
spec.add_dependency "open3"
+ spec.add_dependency "securerandom"
spec.add_dependency "thor"
spec.add_dependency "uri"
end
Gemfile.lock
@@ -6,6 +6,7 @@ PATH
logger
net-http
open3
+ securerandom
thor
uri
@@ -76,6 +77,7 @@ GEM
parser (>= 3.3.7.2)
prism (~> 1.4)
ruby-progressbar (1.13.0)
+ securerandom (0.4.1)
stringio (3.1.7)
thor (1.3.2)
unicode-display_width (3.1.4)