elelem 0.9.2 → 0.11.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.
- checksums.yaml +4 -4
- data/README.md +8 -118
- data/Rakefile +0 -11
- data/exe/elelem +8 -79
- data/lib/elelem/agent.rb +51 -142
- data/lib/elelem/cli.rb +76 -0
- data/lib/elelem/commands.rb +49 -0
- data/lib/elelem/config.rb +17 -0
- data/lib/elelem/conversation.rb +25 -0
- data/lib/elelem/input.rb +53 -0
- data/lib/elelem/null_input.rb +8 -0
- data/lib/elelem/null_output.rb +7 -0
- data/lib/elelem/null_tool.rb +18 -0
- data/lib/elelem/output.rb +57 -0
- data/lib/elelem/plugins.rb +10 -21
- data/lib/elelem/prompts/default.erb +3 -0
- data/lib/elelem/registration.rb +37 -0
- data/lib/elelem/registry.rb +69 -0
- data/lib/elelem/result.rb +26 -0
- data/lib/elelem/slash_command.rb +18 -0
- data/lib/elelem/stub_provider.rb +15 -0
- data/lib/elelem/system_prompt.rb +4 -42
- data/lib/elelem/tool.rb +6 -14
- data/lib/elelem/toolbox.rb +23 -23
- data/lib/elelem/version.rb +2 -2
- data/lib/elelem.rb +29 -27
- metadata +49 -65
- data/CHANGELOG.md +0 -280
- data/lib/elelem/mcp.rb +0 -96
- data/lib/elelem/net/claude.rb +0 -200
- data/lib/elelem/net/ollama.rb +0 -78
- data/lib/elelem/net/openai.rb +0 -86
- data/lib/elelem/net.rb +0 -16
- data/lib/elelem/plugins/confirm.rb +0 -12
- data/lib/elelem/plugins/edit.rb +0 -15
- data/lib/elelem/plugins/eval.rb +0 -20
- data/lib/elelem/plugins/execute.rb +0 -18
- data/lib/elelem/plugins/mcp.rb +0 -14
- data/lib/elelem/plugins/read.rb +0 -21
- data/lib/elelem/plugins/verify.rb +0 -47
- data/lib/elelem/plugins/write.rb +0 -23
- data/lib/elelem/templates/system_prompt.erb +0 -53
- data/lib/elelem/terminal.rb +0 -97
data/lib/elelem/input.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "reline"
|
|
4
|
+
|
|
5
|
+
module Elelem
|
|
6
|
+
class Input
|
|
7
|
+
def initialize(commands: [])
|
|
8
|
+
@commands = commands
|
|
9
|
+
Reline.autocompletion = true
|
|
10
|
+
Reline.completion_proc = ->(target, preposing) { complete(target, preposing) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ask(prompt)
|
|
14
|
+
Reline.readline(prompt, true)&.strip
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def interactive?
|
|
18
|
+
$stdin.tty?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def complete(target, preposing)
|
|
24
|
+
line = "#{preposing}#{target}"
|
|
25
|
+
|
|
26
|
+
if line.start_with?("/") && !preposing.include?(" ")
|
|
27
|
+
return command_names.select { |c| c.start_with?(line) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if preposing.start_with?("/") && preposing.include?(" ")
|
|
31
|
+
cmd_name = preposing.delete_prefix("/").split(" ", 2).first
|
|
32
|
+
return complete_command_args(cmd_name, target)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
complete_files(target)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def command_names
|
|
39
|
+
@commands.respond_to?(:names) ? @commands.names : @commands
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def complete_command_args(cmd_name, partial)
|
|
43
|
+
return [] unless @commands.respond_to?(:completions_for)
|
|
44
|
+
|
|
45
|
+
@commands.completions_for(cmd_name, partial)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def complete_files(target)
|
|
49
|
+
output, = Open3.capture2("bash", "-c", "compgen -f #{Shellwords.escape(target)}")
|
|
50
|
+
output.lines.map(&:strip).first(20)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class NullTool
|
|
5
|
+
def initialize(name, available: [])
|
|
6
|
+
@name = name
|
|
7
|
+
@available = available
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
"#{@name}?"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate(_args)
|
|
15
|
+
["unknown tool: #{@name.inspect}. Available: #{@available.join(", ")}."]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class Output
|
|
5
|
+
def initialize(stream: $stdout, **)
|
|
6
|
+
@stream = stream
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def say(text, as: nil)
|
|
10
|
+
return if blank?(text)
|
|
11
|
+
|
|
12
|
+
write "#{render(text, as)}\n"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def print(text)
|
|
16
|
+
return if blank?(text)
|
|
17
|
+
|
|
18
|
+
write text
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def thinking(text)
|
|
22
|
+
print("\e[2;3m#{text}\e[0m") unless blank?(text)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def doing(name, args, state: "+")
|
|
26
|
+
say "#{state} \e[36m#{name}\e[0m(#{args})"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def waiting
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def display_file(path, fallback: nil)
|
|
33
|
+
say(fallback || path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def render(text, as)
|
|
39
|
+
case as
|
|
40
|
+
when :error
|
|
41
|
+
return "error: #{text}"
|
|
42
|
+
when :markdown
|
|
43
|
+
"```\n" + text + "\n```\n"
|
|
44
|
+
else
|
|
45
|
+
text
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def write(text)
|
|
50
|
+
@stream.print text
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def blank?(text)
|
|
54
|
+
text.nil? || text.to_s.strip.empty?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/elelem/plugins.rb
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Elelem
|
|
4
|
-
|
|
4
|
+
class Plugins
|
|
5
5
|
LOAD_PATHS = [
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
".elelem/plugins"
|
|
6
|
+
"~/.agents/plugins",
|
|
7
|
+
".agents/plugins"
|
|
9
8
|
].freeze
|
|
10
9
|
|
|
11
|
-
def
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
def initialize(load_paths: LOAD_PATHS)
|
|
11
|
+
@loaded = false
|
|
12
|
+
@load_paths = load_paths
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
def
|
|
17
|
-
@
|
|
18
|
-
load_plugins
|
|
19
|
-
registry.each_value { |plugin| plugin.call(toolbox) }
|
|
20
|
-
end
|
|
15
|
+
def load!(force: false)
|
|
16
|
+
return if @loaded && !force
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
LOAD_PATHS.each do |path|
|
|
18
|
+
@load_paths.each do |path|
|
|
24
19
|
dir = File.expand_path(path)
|
|
25
20
|
next unless File.directory?(dir)
|
|
26
21
|
|
|
@@ -30,14 +25,8 @@ module Elelem
|
|
|
30
25
|
warn "elelem: failed to load plugin #{file}: #{e.message}"
|
|
31
26
|
end
|
|
32
27
|
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def self.register(name, &block)
|
|
36
|
-
(@registry ||= {})[name] = block
|
|
37
|
-
end
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
@registry ||= {}
|
|
29
|
+
@loaded = true
|
|
41
30
|
end
|
|
42
31
|
end
|
|
43
32
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class Registration
|
|
5
|
+
attr_reader :providers, :setups, :factories
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@providers = {}
|
|
9
|
+
@setups = {}
|
|
10
|
+
@factories = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def provider(name, &factory)
|
|
14
|
+
@providers[name.to_s] = factory
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def setup(name, &block)
|
|
19
|
+
@setups[name.to_s] = block
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def input(&factory)
|
|
24
|
+
@factories[:input] = factory
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output(&factory)
|
|
29
|
+
@factories[:output] = factory
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def clear_setups!
|
|
34
|
+
@setups.clear
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class Registry
|
|
5
|
+
def initialize(plugins: Plugins.new)
|
|
6
|
+
@registration = Registration.new
|
|
7
|
+
@plugins = plugins
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def configure
|
|
11
|
+
yield @registration
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def build_provider(name)
|
|
16
|
+
@plugins.load!
|
|
17
|
+
@registration.providers.fetch(name.to_s) { require_gem(name) }.call
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def apply(agent)
|
|
21
|
+
@plugins.load!
|
|
22
|
+
@registration.setups.each_value { |block| block.call(agent) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reload(agent)
|
|
26
|
+
@registration.clear_setups!
|
|
27
|
+
agent.toolbox.clear!
|
|
28
|
+
agent.commands.clear!
|
|
29
|
+
@plugins.load!(force: true)
|
|
30
|
+
apply(agent)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def names
|
|
34
|
+
@registration.providers.keys
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_agent(provider, toolbox: Toolbox.new, input: nil, output: nil)
|
|
38
|
+
commands = Commands.new
|
|
39
|
+
agent = Agent.new(
|
|
40
|
+
build_provider(provider),
|
|
41
|
+
toolbox: toolbox,
|
|
42
|
+
commands: commands,
|
|
43
|
+
input: input || build_input(commands: commands),
|
|
44
|
+
output: output || build_output,
|
|
45
|
+
)
|
|
46
|
+
apply(agent)
|
|
47
|
+
agent
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_input(**opts)
|
|
53
|
+
@plugins.load!
|
|
54
|
+
(@registration.factories[:input] || ->(**o) { Input.new(**o) }).call(**opts)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_output(**opts)
|
|
58
|
+
@plugins.load!
|
|
59
|
+
(@registration.factories[:output] || ->(**o) { Output.new(**o) }).call(**opts)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def require_gem(name)
|
|
63
|
+
require "elelem/#{name}"
|
|
64
|
+
@registration.providers.fetch(name.to_s) { raise "elelem-#{name} did not register a provider named #{name.inspect}" }
|
|
65
|
+
rescue LoadError
|
|
66
|
+
raise "unknown provider: #{name.inspect}. Available: #{names.join(", ")}. Run: gem install elelem-#{name}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module Elelem
|
|
6
|
+
class Result
|
|
7
|
+
extend Forwardable
|
|
8
|
+
def_delegators :@payload, :[], :each, :to_json, :to_h, :key?, :merge
|
|
9
|
+
|
|
10
|
+
def self.success(payload)
|
|
11
|
+
new(payload.merge(ok: true))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.failure(payload)
|
|
15
|
+
new(payload.merge(ok: false))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(payload)
|
|
19
|
+
@payload = payload
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ok? = !!@payload[:ok]
|
|
23
|
+
def error? = !!@payload[:error]
|
|
24
|
+
def error = @payload[:error]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class SlashCommand
|
|
5
|
+
attr_reader :name, :description, :completions
|
|
6
|
+
|
|
7
|
+
def initialize(name, description = "", completions = nil, &handler)
|
|
8
|
+
@name = name
|
|
9
|
+
@description = description
|
|
10
|
+
@completions = completions
|
|
11
|
+
@handler = handler
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(args)
|
|
15
|
+
@handler.call(args)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
class StubProvider
|
|
5
|
+
def fetch(messages, tools = [], &block)
|
|
6
|
+
block.call(type: "thinking", text: "considering the request")
|
|
7
|
+
return block.call(type: "saying", text: "done.") if messages.last[:role] == "tool"
|
|
8
|
+
|
|
9
|
+
block.call(type: "saying", text: "let me check.")
|
|
10
|
+
return unless (tool = tools.first)
|
|
11
|
+
|
|
12
|
+
block.call(type: "doing", id: "stub-1", name: tool[:function][:name], arguments: {})
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/elelem/system_prompt.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Elelem
|
|
4
4
|
class SystemPrompt
|
|
5
|
-
|
|
5
|
+
DEFAULT = File.read(File.expand_path("prompts/default.erb", __dir__)).freeze
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
attr_accessor :template
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
9
|
+
def initialize(template = nil)
|
|
10
|
+
@template = template || DEFAULT
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def render
|
|
@@ -16,44 +16,6 @@ module Elelem
|
|
|
16
16
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
|
-
def template
|
|
20
|
-
File.read(TEMPLATE_PATH)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def pwd
|
|
24
|
-
Dir.pwd
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def elelem_source
|
|
28
|
-
File.expand_path("../..", __dir__)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def platform
|
|
32
|
-
RUBY_PLATFORM.split("-").last
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def date
|
|
36
|
-
Date.today
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def git_branch
|
|
40
|
-
return unless File.exist?(".git")
|
|
41
|
-
|
|
42
|
-
"branch: #{`git branch --show-current`.strip}"
|
|
43
|
-
rescue
|
|
44
|
-
nil
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def repo_map
|
|
48
|
-
`ctags -x --sort=no --languages=Ruby,Python,JavaScript,TypeScript,Go,Rust -R . 2>/dev/null`
|
|
49
|
-
.lines
|
|
50
|
-
.reject { |l| l.include?("vendor/") || l.include?("node_modules/") || l.include?("spec/") }
|
|
51
|
-
.first(100)
|
|
52
|
-
.join
|
|
53
|
-
rescue
|
|
54
|
-
""
|
|
55
|
-
end
|
|
56
|
-
|
|
57
19
|
def agents_md
|
|
58
20
|
Pathname.pwd.ascend.each do |dir|
|
|
59
21
|
file = dir / "AGENTS.md"
|
data/lib/elelem/tool.rb
CHANGED
|
@@ -7,11 +7,12 @@ module Elelem
|
|
|
7
7
|
def initialize(name, description:, params: {}, required: [], aliases: [], &fn)
|
|
8
8
|
@name = name
|
|
9
9
|
@description = description
|
|
10
|
-
@params = params
|
|
11
|
-
@required = required
|
|
12
|
-
@aliases = aliases
|
|
10
|
+
@params = params.freeze
|
|
11
|
+
@required = required.freeze
|
|
12
|
+
@aliases = aliases.freeze
|
|
13
13
|
@fn = fn
|
|
14
|
-
@
|
|
14
|
+
@schema_hash = { type: "object", properties: @params, required: @required }.freeze
|
|
15
|
+
@schema = JSONSchemer.schema(@schema_hash)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def call(args)
|
|
@@ -30,19 +31,10 @@ module Elelem
|
|
|
30
31
|
function: {
|
|
31
32
|
name: name,
|
|
32
33
|
description: description,
|
|
33
|
-
parameters: schema_hash
|
|
34
|
+
parameters: @schema_hash
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def schema_hash
|
|
41
|
-
{
|
|
42
|
-
type: "object",
|
|
43
|
-
properties: params,
|
|
44
|
-
required: required
|
|
45
|
-
}
|
|
46
|
-
end
|
|
47
39
|
end
|
|
48
40
|
end
|
data/lib/elelem/toolbox.rb
CHANGED
|
@@ -10,38 +10,41 @@ module Elelem
|
|
|
10
10
|
@hooks = { before: Hash.new { |h, k| h[k] = [] }, after: Hash.new { |h, k| h[k] = [] } }
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def clear!
|
|
14
|
+
@tools.clear
|
|
15
|
+
@aliases.clear
|
|
16
|
+
@hooks[:before].clear
|
|
17
|
+
@hooks[:after].clear
|
|
18
|
+
end
|
|
19
|
+
|
|
13
20
|
def add(name, description:, params: {}, required: [], aliases: [], &fn)
|
|
14
21
|
tool = Tool.new(name, description: description, params: params, required: required, aliases: aliases, &fn)
|
|
15
22
|
@tools[name] = tool
|
|
16
23
|
tool.aliases.each { |a| @aliases[a] = name }
|
|
17
24
|
end
|
|
18
25
|
|
|
19
|
-
def before(tool_name
|
|
26
|
+
def before(tool_name = :*, &block)
|
|
20
27
|
@hooks[:before][tool_name] << block
|
|
21
28
|
end
|
|
22
29
|
|
|
23
|
-
def after(tool_name
|
|
30
|
+
def after(tool_name = :*, &block)
|
|
24
31
|
@hooks[:after][tool_name] << block
|
|
25
32
|
end
|
|
26
33
|
|
|
27
|
-
def
|
|
28
|
-
name
|
|
29
|
-
"\n#{state} #{name}(#{args})"
|
|
34
|
+
def tool_for(name)
|
|
35
|
+
tools.fetch(@aliases.fetch(name, name)) { NullTool.new(name, available: tools.keys) }
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def run(name, args)
|
|
33
39
|
tool = tool_for(name)
|
|
34
|
-
return failure(error: "unknown tool: #{name}. Use 'execute' to run shell commands like rg, fd, git.", tools: to_a) unless tool
|
|
35
|
-
|
|
36
40
|
errors = tool.validate(args)
|
|
37
|
-
return failure(error: errors.join(", ")) if errors.any?
|
|
41
|
+
return Result.failure(error: errors.join(", ")) if errors.any?
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
result
|
|
41
|
-
@hooks[:after][tool.name].each { |h| h.call(args, result) }
|
|
42
|
-
result[:error] ? failure(result) : success(result)
|
|
43
|
+
result = dispatch(tool, args)
|
|
44
|
+
result[:error] ? Result.failure(result) : Result.success(result)
|
|
43
45
|
rescue => e
|
|
44
|
-
|
|
46
|
+
Elelem.logger.warn("toolbox: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
47
|
+
Result.failure(error: e.message, name: name, args: args)
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def to_a
|
|
@@ -50,16 +53,13 @@ module Elelem
|
|
|
50
53
|
|
|
51
54
|
private
|
|
52
55
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def failure(payload)
|
|
62
|
-
payload.merge(ok: false)
|
|
56
|
+
def dispatch(tool, args)
|
|
57
|
+
@hooks[:before][:*].each { |h| h.call(args, tool_name: tool.name) }
|
|
58
|
+
@hooks[:before][tool.name].each { |h| h.call(args) }
|
|
59
|
+
result = tool.call(args)
|
|
60
|
+
@hooks[:after][:*].each { |h| h.call(args, result, tool_name: tool.name) }
|
|
61
|
+
@hooks[:after][tool.name].each { |h| h.call(args, result) }
|
|
62
|
+
result
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
data/lib/elelem/version.rb
CHANGED
data/lib/elelem.rb
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "date"
|
|
4
3
|
require "erb"
|
|
5
|
-
require "
|
|
4
|
+
require "forwardable"
|
|
6
5
|
require "json"
|
|
7
6
|
require "json_schemer"
|
|
7
|
+
require "logger"
|
|
8
8
|
require "open3"
|
|
9
9
|
require "pathname"
|
|
10
|
-
require "
|
|
11
|
-
require "stringio"
|
|
12
|
-
require "tempfile"
|
|
10
|
+
require "shellwords"
|
|
13
11
|
|
|
14
12
|
require_relative "elelem/agent"
|
|
15
|
-
require_relative "elelem/
|
|
16
|
-
require_relative "elelem/
|
|
13
|
+
require_relative "elelem/slash_command"
|
|
14
|
+
require_relative "elelem/commands"
|
|
15
|
+
require_relative "elelem/conversation"
|
|
16
|
+
require_relative "elelem/input"
|
|
17
|
+
require_relative "elelem/null_input"
|
|
18
|
+
require_relative "elelem/output"
|
|
19
|
+
require_relative "elelem/null_output"
|
|
17
20
|
require_relative "elelem/plugins"
|
|
21
|
+
require_relative "elelem/registration"
|
|
22
|
+
require_relative "elelem/registry"
|
|
23
|
+
require_relative "elelem/config"
|
|
24
|
+
require_relative "elelem/result"
|
|
25
|
+
require_relative "elelem/stub_provider"
|
|
18
26
|
require_relative "elelem/system_prompt"
|
|
19
|
-
require_relative "elelem/terminal"
|
|
20
27
|
require_relative "elelem/tool"
|
|
28
|
+
require_relative "elelem/null_tool"
|
|
21
29
|
require_relative "elelem/toolbox"
|
|
22
30
|
require_relative "elelem/version"
|
|
23
31
|
|
|
24
32
|
module Elelem
|
|
25
|
-
def self.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Open3.popen2e(env, cmd, *args, chdir: cwd) do |stdin, out, wait_thr|
|
|
29
|
-
stdin.close
|
|
30
|
-
out.each_line do |line|
|
|
31
|
-
yield line if block_given?
|
|
32
|
-
output.write(line)
|
|
33
|
-
end
|
|
33
|
+
def self.start(provider: "stub", toolbox: Toolbox.new)
|
|
34
|
+
self.build(provider: provider, toolbox: toolbox).repl
|
|
35
|
+
end
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
def self.ask(prompt, provider: "stub", toolbox: Toolbox.new, output: NullOutput.new)
|
|
38
|
+
self.build(provider: provider, toolbox: toolbox, input: NullInput.new, output: output).turn(prompt)
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def self.
|
|
40
|
-
|
|
41
|
-
Agent.new(client, toolbox).repl
|
|
41
|
+
def self.build(provider: "stub", toolbox: Toolbox.new, input: nil, output: nil)
|
|
42
|
+
Config.build_agent(provider, toolbox: toolbox, input: input, output: output)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
def self.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
agent.history.last[:content]
|
|
45
|
+
def self.logger
|
|
46
|
+
@logger ||= Logger.new($stderr).tap do |log|
|
|
47
|
+
log.level = Logger.const_get(ENV.fetch("ELELEM_LOG_LEVEL", "warn").upcase)
|
|
48
|
+
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
|
+
|
|
52
|
+
Elelem.configure { |config| config.provider(:stub) { Elelem::StubProvider.new } }
|